# 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`:** ```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`:** ```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: ```php /** * 計算會費金額 * * @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 `use`d 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`: ```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*