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

556 lines
13 KiB
Markdown
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.
```php
// 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
```php
// 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`)
```php
// 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](https://www.php-fig.org/psr/psr-12/) with these specific requirements:
```php
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:
```php
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
```php
#[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:
```javascript
// 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:
```javascript
$(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
```scss
// 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:
```scss
// 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:
```php
// 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
```php
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
```php
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
```php
/**
* 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:
```php
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:
```env
# 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:
```php
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