Files
rspade_system/docs/coding-conventions.md
root f6fac6c4bc Fix bin/publish: copy docs.dist from project root
Fix bin/publish: use correct .env path for rspade_system
Fix bin/publish script: prevent grep exit code 1 from terminating script

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 02:08:33 +00:00

13 KiB
Executable File

Coding Conventions

This document outlines the coding standards and conventions used in RSpade and RSX. Following these conventions ensures consistency across the codebase and compatibility with the framework.

PHP Conventions

Naming Conventions

CRITICAL: This project uses underscore_case for all variables and functions, NOT camelCase.

// CORRECT - Use underscore_case
$user_name = 'John';
$is_active = true;
function get_user_profile() { }
function calculate_total_price() { }

// WRONG - Do not use camelCase
$userName = 'John';  // ❌
$isActive = true;    // ❌
function getUserProfile() { }     // ❌
function calculateTotalPrice() { } // ❌

Class and File Naming

  • Classes: Use PascalCase
  • Files: Match class name exactly with .php extension
  • Constants: Use UPPERCASE_WITH_UNDERSCORES
// Class naming
class UserController { }           // ✓ PascalCase
class BlogPostManager { }           // ✓ PascalCase
class Rsx_Controller { }           // ✓ Legacy RSX base classes use underscore

// Constants
const MAX_ATTEMPTS = 5;            // ✓
const DEFAULT_CACHE_TTL = 3600;    // ✓

Database Conventions

  • Tables: Use plural snake_case (e.g., users, blog_posts)
  • Columns: Use snake_case (e.g., created_at, user_id)
  • Foreign Keys: Use {singular_table}_id (e.g., user_id, post_id)
// Model with database fields
class BlogPost extends Model
{
    protected $table = 'blog_posts';  // plural snake_case
    
    protected $fillable = [
        'title',
        'content',
        'user_id',         // foreign key
        'published_at',    // snake_case
        'view_count'       // snake_case
    ];
}

Code Style

RSpade follows PSR-12 with these specific requirements:

namespace App\Models;

use App\RSpade\Core\Database\Models\Rsx_Model_Abstract;

/**
 * Blog post model
 * 
 * @property int $id
 * @property string $title
 * @property string $content
 * @property int $user_id
 * @property Carbon $created_at
 */
class BlogPost extends Rsx_Model_Abstract
{
    protected $fillable = [
        'title',
        'content',
        'user_id',
        'status'
    ];
    
    /**
     * Get the author of the post
     * 
     * @return BelongsTo
     */
    public function author()
    {
        return $this->belongsTo(User::class, 'user_id');
    }
    
    /**
     * Mark post as published
     * 
     * @return bool
     */
    public function mark_as_published()  // underscore_case method
    {
        $this->status = 'published';
        $this->published_at = now();
        return $this->save();
    }
    
    /**
     * Get formatted title
     * 
     * @return string
     */
    public function get_formatted_title()  // underscore_case method
    {
        $max_length = 50;
        $title_text = $this->title;
        
        if (strlen($title_text) > $max_length) {
            return substr($title_text, 0, $max_length) . '...';
        }
        
        return $title_text;
    }
}

RSX Attribute Conventions

Attribute Usage

Use PHP 8 attributes for routing, caching, and other decorators:

use App\RSpade\Core\Attributes\{Route, Cache, RateLimit, Middleware};

class UserController extends Rsx_Controller
{
    #[Route('/users', methods: ['GET'])]
    #[Cache(ttl: 300, tags: ['users'])]
    #[RateLimit(max_attempts: 60)]
    public function list_users($params)
    {
        $page_number = $params['page'] ?? 1;
        $items_per_page = 20;
        
        return $this->paginate_results($page_number, $items_per_page);
    }
    
    #[Route('/users/:id', methods: ['GET'])]
    #[Middleware(['auth'])]
    public function get_user($params)
    {
        $user_id = $params['id'];
        $include_posts = $params['include_posts'] ?? false;
        
        return $this->load_user_data($user_id, $include_posts);
    }
}

Attribute Placement

  • Class-level: Applies to all methods in the class
  • Method-level: Applies to specific method only
  • Multiple attributes: Stack vertically for readability
#[Cors(allowed_origins: ['https://app.example.com'])]  // Class-level
#[ApiVersion('v2')]                                    // Class-level
class ApiController extends Rsx_Api
{
    #[Route('/api/data', methods: ['GET'])]           // Method-level
    #[Cache(ttl: 600)]                                // Method-level
    #[RateLimit(max_attempts: 100)]                   // Method-level
    public function get_data($params)
    {
        // Implementation
    }
}

JavaScript Conventions

ES6+ Standards

Use modern JavaScript with ES6+ features:

// Use const/let, arrow functions, destructuring
const process_user_data = (user_data) => {
    const { first_name, last_name, email } = user_data;
    const full_name = `${first_name} ${last_name}`;
    
    return {
        full_name,
        email,
        display_name: full_name.toLowerCase()
    };
};

// Class with static methods for namespacing
class UserManager {
    static async load_user(user_id) {
        const response = await fetch(`/api/users/${user_id}`);
        const user_data = await response.json();
        return user_data;
    }
    
    static format_user_name(first_name, last_name) {
        return `${first_name} ${last_name}`.trim();
    }
}

jQuery Usage

Use jQuery for DOM manipulation where appropriate:

$(document).ready(() => {
    // Use underscore_case for functions and variables
    const init_user_form = () => {
        const $form = $('#user_form');
        const $submit_button = $form.find('.submit_button');
        
        $submit_button.on('click', async (e) => {
            e.preventDefault();
            const form_data = get_form_data($form);
            await submit_form_data(form_data);
        });
    };
    
    const get_form_data = ($form) => {
        const form_values = {};
        $form.find('input, select, textarea').each(function() {
            const field_name = $(this).attr('name');
            const field_value = $(this).val();
            form_values[field_name] = field_value;
        });
        return form_values;
    };
    
    init_user_form();
});

SCSS/CSS Conventions

File Organization

// resources/sass/app.scss
@import 'variables';     // Custom variables
@import 'bootstrap';     // Bootstrap framework
@import 'layout';        // Layout styles
@import 'components';    // Component styles
@import 'pages';        // Page-specific styles

Naming Conventions

Use BEM-style naming with underscores:

// Component naming
.user_card {
    padding: 1rem;
    border: 1px solid $border_color;
    
    &__header {
        font-size: 1.2rem;
        margin_bottom: 0.5rem;
    }
    
    &__content {
        color: $text_color;
    }
    
    &--featured {
        border_color: $primary_color;
        background: $featured_background;
    }
}

// Page-specific styles
.page_is_user_profile {
    .profile_section {
        margin_bottom: 2rem;
    }
    
    .profile_stats {
        display: flex;
        gap: 1rem;
    }
}

Database Migration Conventions

Naming

Use descriptive names with timestamps:

// Good migration names
2024_01_15_000001_create_users_table.php
2024_01_15_000002_add_avatar_to_users_table.php
2024_01_15_000003_create_blog_posts_table.php

Structure

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateBlogPostsTable extends Migration
{
    public function up()
    {
        Schema::create('blog_posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('content');
            $table->unsignedBigInteger('user_id');
            $table->enum('status', ['draft', 'published', 'archived']);
            $table->datetime('published_at')->nullable();
            
            // Standard tracking columns
            $table->unsignedBigInteger('created_by')->nullable();
            $table->unsignedBigInteger('updated_by')->nullable();
            $table->unsignedBigInteger('deleted_by')->nullable();
            $table->timestamps();
            $table->softDeletes();
            
            // Indexes
            $table->index('user_id');
            $table->index('status');
            $table->index('published_at');
            
            // Foreign keys
            $table->foreign('user_id')->references('id')->on('users');
        });
    }
    
    public function down()
    {
        Schema::dropIfExists('blog_posts');
    }
}

Testing Conventions

Test Naming

namespace Tests\Unit;

use Tests\TestCase;

class UserServiceTest extends TestCase
{
    // Use descriptive test names with underscores
    public function test_user_can_be_created()
    {
        $user_data = [
            'name' => 'John Doe',
            'email' => 'john@example.com'
        ];
        
        $user = $this->create_test_user($user_data);
        
        $this->assertNotNull($user->id);
        $this->assertEquals('John Doe', $user->name);
    }
    
    public function test_user_email_must_be_unique()
    {
        $existing_user = User::factory()->create([
            'email' => 'test@example.com'
        ]);
        
        $this->expectException(ValidationException::class);
        
        $this->create_user_with_email('test@example.com');
    }
}

Documentation Conventions

PHPDoc Comments

/**
 * Process user registration
 * 
 * Handles the complete user registration flow including validation,
 * user creation, email verification, and initial setup.
 * 
 * @param array $user_data User registration data
 * @param bool $send_email Whether to send welcome email
 * @return User The created user instance
 * @throws ValidationException If validation fails
 * @throws RegistrationException If registration cannot be completed
 */
public function process_registration(array $user_data, bool $send_email = true): User
{
    // Implementation
}

Inline Comments

Use inline comments sparingly for complex logic:

public function calculate_discount($order_total, $user)
{
    $discount_amount = 0;
    
    // Apply loyalty discount for users with 10+ orders
    if ($user->order_count >= 10) {
        $discount_amount += $order_total * 0.1;
    }
    
    // Apply seasonal discount during December
    if (date('n') == 12) {
        $discount_amount += $order_total * 0.05;
    }
    
    // Cap discount at 25% of total
    $max_discount = $order_total * 0.25;
    return min($discount_amount, $max_discount);
}

Git Commit Conventions

Commit Message Format

<type>: <subject>

<body>

<footer>

Types

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation changes
  • style: Code style changes (formatting, missing semicolons, etc.)
  • refactor: Code refactoring
  • test: Test additions or changes
  • chore: Maintenance tasks

Examples

feat: Add user profile image upload

Implemented file upload functionality for user avatars with
automatic resizing and format conversion to WebP.

Closes #123
fix: Correct discount calculation for bulk orders

The previous calculation was not applying the bulk discount
correctly when orders exceeded 100 items.

Environment Configuration

Environment Variables

Use descriptive names in SCREAMING_SNAKE_CASE:

# Application
APP_NAME="RSpade"
APP_ENV=local
APP_DEBUG=true
APP_URL=https://rspade.example.com

# Database
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=rspade
DB_USERNAME=root
DB_PASSWORD=secret

# Cache
CACHE_DRIVER=redis
CACHE_PREFIX=rspade_cache

# RSX Framework
RSX_CACHE_ENABLED=true
RSX_CACHE_TTL=3600
RSX_AUTO_BUILD=true

Error Handling

Exception Messages

Use clear, actionable error messages:

public function process_payment($payment_data)
{
    if (!isset($payment_data['amount'])) {
        throw new PaymentException(
            'Payment amount is required. Please provide a valid amount in the payment_data array.'
        );
    }
    
    if ($payment_data['amount'] <= 0) {
        throw new PaymentException(
            sprintf(
                'Payment amount must be greater than zero. Received: %s',
                $payment_data['amount']
            )
        );
    }
    
    // Process payment...
}

Summary

Key points to remember:

  1. Always use underscore_case for variables and functions
  2. Use PascalCase for class names
  3. Use SCREAMING_SNAKE_CASE for constants
  4. Follow PSR-12 for general PHP style
  5. Use PHP 8 attributes for RSX features
  6. Document complex logic with clear comments
  7. Write descriptive test names with underscores
  8. Keep code readable over clever
  9. Be consistent throughout the codebase