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>
556 lines
13 KiB
Markdown
Executable File
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 |