Files
usher-manage-stack/.planning/codebase/CONVENTIONS.md
2026-02-13 10:34:18 +08:00

7.9 KiB

Coding Conventions

Analysis Date: 2026-02-13

Naming Patterns

Files:

  • Classes: PascalCase (e.g., FinanceDocument.php, MembershipFeeCalculator.php)
  • Controllers: PascalCase with Controller suffix (e.g., MemberPaymentController.php)
  • Traits: PascalCase with descriptive names (e.g., HasApprovalWorkflow.php, HasAccountingEntries.php)
  • Models: PascalCase singular (e.g., Member.php, FinanceDocument.php)
  • Services: PascalCase with Service suffix (e.g., MembershipFeeCalculator.php, SettingsService.php)
  • Requests: PascalCase with action + Request suffix (e.g., StoreMemberRequest.php, UpdateIssueRequest.php)
  • Database: Migration files use timestamp prefix with snake_case (Laravel convention)

Functions:

  • Methods: camelCase (e.g., createMember(), getBaseAmount(), isSelfApproval())
  • Helper functions: camelCase, wrapped in function_exists check (e.g., settings() in app/helpers.php)
  • Test methods: camelCase, prefixed with test_ or use /** @test */ doc comment (see tests/Unit/MembershipPaymentTest.php)
  • Model query scopes: camelCase (Laravel convention)

Variables:

  • Local variables: camelCase (e.g., $baseAmount, $feeDetails, $discountRate)
  • Model attributes: snake_case in database, accessed as camelCase properties (Laravel convention)
  • Constants: UPPER_SNAKE_CASE (e.g., STATUS_PENDING, AMOUNT_TIER_SMALL, IDENTITY_PATIENT)
  • Protected/private properties: camelCase with prefix (e.g., $settings, $feeCalculator)

Types:

  • Model classes: Use User, Member, FinanceDocument (singular)
  • Collections: Arrays or Collection type hints
  • Enums: Not used; status values stored as class constants (e.g., in FinanceDocument.php lines 15-59)
  • Nullable types: Use ?Type (e.g., ?User $user)
  • Return type hints: Always specified (e.g., public function calculate(...): array)

Code Style

Formatting:

  • Tool: Laravel Pint (PHP code style formatter)
  • Run command: ./vendor/bin/pint
  • Indentation: 4 spaces (configured in .editorconfig)
  • Line endings: LF (configured in .editorconfig)
  • Final newline: Required (configured in .editorconfig)
  • Trailing whitespace: Trimmed (configured in .editorconfig)

Language versions:

  • PHP: 8.1+ (required in composer.json)
  • Laravel: 10.10+ (required in composer.json)

Linting:

  • Tool: PHPStan (static analysis)
  • Run command: ./vendor/bin/phpstan analyse
  • No ESLint/Prettier for frontend (basic Tailwind + Alpine.js)

Import Organization

Order:

  1. PHP core/standard library classes (use Illuminate\*, use App\*)
  2. Third-party library classes (use Spatie\*, use Barryvdh\*)
  3. Application namespaces (use App\Models\*, use App\Services\*, use App\Traits\*)
  4. Facades and static imports last (use Illuminate\Support\Facades\*)

Example from MemberPaymentController.php:

use App\Mail\PaymentSubmittedMail;
use App\Models\Member;
use App\Models\MembershipPayment;
use App\Models\User;
use App\Services\MembershipFeeCalculator;
use App\Support\AuditLogger;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;
use Illuminate\Validation\Rule;

Path Aliases:

  • No custom aliases configured; use standard PSR-4 autoloading
  • Namespace hierarchy: App\Models\, App\Services\, App\Http\Controllers\, App\Http\Requests\, App\Traits\, App\Support\

Error Handling

Patterns:

  • Use try-catch blocks for complex operations with side effects (see BankReconciliationController.php lines 75-81)
  • Catch generic \Exception when handling database/file operations
  • Use DB transactions for multi-step operations requiring rollback
  • Authorization: Use $this->authorize('permission-name') in controllers (Gate authorization)
  • Validation: Use Form Request classes (StoreMemberRequest, UpdateMemberRequest) in controller methods
  • Permission checks: Use Spatie Laravel Permission with roles and permissions (see SeedsRolesAndPermissions trait)

Example from BankReconciliationController.php:

DB::beginTransaction();
try {
    // Handle bank statement file upload
    $statementPath = null;
    if ($request->hasFile('bank_statement_file')) {
        $statementPath = $request->file('bank_statement_file')->store('bank-statements', 'local');
    }
    // ... process data
    DB::commit();
} catch (\Exception $e) {
    DB::rollBack();
    return redirect()->back()->with('error', 'Error message');
}

Logging

Framework: None configured; uses Laravel's default logging or direct logging via Storage facades.

Patterns:

  • Use AuditLogger static class for business logic audit trail (see app/Support/AuditLogger.php)
  • Call pattern: AuditLogger::log($action, $auditable, $metadata)
  • Example usage in MemberPaymentController.php and other controllers
  • All member/finance/permission changes should be logged via AuditLogger

Comments

When to Comment:

  • Trait usage instructions (see HasApprovalWorkflow.php lines 7-14)
  • Complex business logic requiring explanation (e.g., multi-tier approval, fee calculations)
  • Prevent: Obvious comments describing what code does (let code be self-documenting)
  • Include: Comments explaining WHY, not WHAT

JSDoc/TSDoc:

  • Use PHPDoc for public methods (see MembershipFeeCalculator.php lines 17-23)
  • Parameter types documented with @param Type $variable
  • Return types documented with @return Type
  • Example from calculate() method:
/**
 * 計算會費金額
 *
 * @param Member $member 會員
 * @param string $feeType 會費類型 (entrance_fee | annual_fee)
 * @return array{base_amount: float, discount_amount: float, final_amount: float, disability_discount: bool, fee_type: string}
 */

Chinese Comments:

  • UI-facing text and business logic comments may use Traditional Chinese
  • Code comments default to English for consistency
  • No specific rule enforced; follow existing pattern in related files

Function Design

Size: Keep under 30-40 lines per method

  • Controllers: Each action method typically 20-30 lines
  • Services: Business logic methods 15-25 lines
  • Traits: Utility methods 5-15 lines

Parameters:

  • Maximum 5 parameters per method
  • Use dependency injection for services (constructor or method parameter)
  • Use Form Request classes for validation parameters (not raw Request)
  • Optional parameters use ?Type or default values

Return Values:

  • Always include return type hint (e.g., : array, : bool, : void)
  • Use arrays for multiple values needing structure (see MembershipFeeCalculator.php line 24)
  • Use model instances for Eloquent operations
  • Return null explicitly when appropriate (use ?Type hint)

Module Design

Exports:

  • Laravel models are auto-discovered via namespace
  • Controllers are auto-discovered via App\Http\Controllers namespace
  • Requests are auto-discovered via App\Http\Requests namespace
  • Traits are explicitly used in consuming classes

Barrel Files:

  • Not used; each file has single responsibility
  • Use explicit imports rather than wildcard imports

Status Constants Pattern:

  • All statuses defined as class constants, never magic strings or enums
  • Grouped by category (approval status, disbursement status, recording status)
  • Examples from FinanceDocument.php:
public const STATUS_PENDING = 'pending';
public const STATUS_APPROVED_SECRETARY = 'approved_secretary';
public const DISBURSEMENT_PENDING = 'pending';
public const DISBURSEMENT_REQUESTER_CONFIRMED = 'requester_confirmed';

Service Injection Pattern:

  • Constructor injection for services (see MemberPaymentController.__construct())
  • Store in protected property for method access
  • Use type hints for IDE support

Trait Usage Pattern:

  • Mixed concern traits (e.g., HasApprovalWorkflow, HasAccountingEntries) used in models
  • Test helper traits (e.g., SeedsRolesAndPermissions, CreatesFinanceData) used in test classes
  • Traits documented at top with usage instructions

Convention analysis: 2026-02-13