Files
sides/.planning/codebase/CONVENTIONS.md
2026-05-28 16:25:22 +08:00

378 lines
16 KiB
Markdown

# Coding Conventions
**Analysis Date:** 2026-05-28
## Naming Patterns
**Controllers:**
- PascalCase singular nouns: `RainfallController`, `WaterLevelController`, `AdminController`, `SirenController`, `MapController`, `LocaleController`, `ProfileController`, `cctvController`
- **Inconsistency:** `cctvController.php` is lowercase `c`, should be `CctvController.php`
- API controllers namespaced under `App\Http\Controllers\Api\`: `StationController`, `AuthController`, `AlertController`
- Auth controllers namespaced under `App\Http\Controllers\Auth\`: `AuthenticatedSessionController`, `RegisteredUserController`, `PasswordController`, `NewPasswordController`, `PasswordResetLinkController`, `ConfirmablePasswordController`, `EmailVerificationPromptController`, `EmailVerificationNotificationController`, `VerifyEmailController`
**Controller Methods:**
- camelCase, descriptive verbs: `index()`, `storeStation()`, `updateStation()`, `deleteStation()`, `storeUser()`, `updateUsers()`, `updatePassword()`, `deleteUser()`, `stationDisplay()`, `userDisplay()`, `rainfallGraph()`, `rainfallSum()`, `historicalRainfall()`, `graphData()`, `graphPage()`, `getStations()`, `getCurrentData()`, `exportHourlyRainfallExcel()`, `exportHistoricalWl()`, `SirenHistory()`, `rainfallNotification()`, `wlNotification()`, `sirenNotification()`, `rfHistory()`, `wlHistory()`
- **Inconsistency:** `SirenHistory()` starts with uppercase S; `stationDisplay()` inconsistent with rest (`stationDisplay` vs `displayStations`)
- Auth scaffold methods follow Laravel Breeze conventions: `create()`, `store()`, `edit()`, `update()`, `destroy()`, `show()`
**Models:**
- Only one model: `User.php``App\Models\User` extends `Authenticatable`
- Singular PascalCase: `User`
**Routes:**
- kebab-case URLs: `/rainfall/historical`, `/waterlevel/graph/{stationid}`, `/station/current`, `/station/rainfall`
- Snake-case named routes with dot-notation: `profile.edit`, `profile.update`, `profile.destroy`, `rainfall.graph`, `waterlevel.graph`, `historicalwl`, `historicalwlexport`, `stationmanagement.store`, `usermgmt.updatePassword`, `export.historysiren.pdf`
- Route parameters use camelCase: `{stationid}`, `{dates}`, `{userid}`, `{locale}`
- No resource route definitions — all routes manually defined
**Views/Directories:**
- Blade templates under `resources/views/layout/` organized by feature:
- `layout/rainfall.blade.php`, `layout/waterlevel.blade.php`, `layout/cctv.blade.php`
- `layout/admin/stationmgmt.blade.php`, `layout/admin/usermgmt.blade.php`
- `layout/notification/rainfall.blade.php`, `layout/notification/history/rainfall.blade.php`
- `layout/siren/home.blade.php`, `layout/siren/history.blade.php`
- `layout/graph/rainfall.blade.php`
- View path names match controller method return values: `view('layout.rainfall', ...)`, `view('layout.admin.stationmgmt', ...)`
- Views rendered via dot-notation: `layout.rainfall`, `layout.admin.stationmgmt`, `pdf.sirenhistory`, `auth.register`
**Database:**
- Table names: lowercase snake-case plural: `users`, `station`, `rainfall`, `waterlevel`, `siren`, `notification` (mixed — `station` is singular, `users` is plural)
- Migration filename: `YYYY_MM_DD_HHMMSS_create_{table}_table.php`
- Columns: snake_case: `stationid`, `access_level`, `login_attempts`, `is_blocked`, `cctv_link`, `mainriverbasin`, `subriverbasin`, `email_verified_at`, `remember_token`
- Index: `$table->id()`, `$table->string('name')`, `$table->timestamps()`
## Code Style
**PSR-12 Compliance:**
- Namespace declarations: `namespace App\Http\Controllers;`
- Class brace on new line
- Method brace on new line
- `<?php` opening tag on line 1
- Use statements grouped by namespace
**Indentation Issues:**
- Mix of consistent 4-space indentation in most files
- Some files have inconsistent indentation: `RainfallController.php` lines 26, 92-94, 209-222
- `WaterLevelController.php` has extra blank lines and inconsistent spacing
- `AdminController.php` try/catch blocks have widely varying indentation depth
**Line Length:**
- Some SQL strings in controllers exceed 120 characters (e.g., `RainfallController.php` line 39-90, lines 161-174)
- No line-length enforcement visible; no `.editorconfig` or `.php-cs-fixer` config detected
**Semicolons:**
- Consistently used — no omissions detected
**Cast/Type Declarations:**
- Controllers use PHP 8+ typed return values only in ProfileController: `public function edit(Request $request): View`
- Breeze-generated auth controllers consistently use return type hints: `: View`, `: RedirectResponse`, `: RedirectResponse|View`
- Custom controllers (Rainfall, WaterLevel, Siren, etc.) omit return type declarations entirely
- `User` model uses typed property declaration: `protected function casts(): array`
**Strict Types:**
- No `declare(strict_types=1)` in any file — strict types not used
## Import Organization
**Order:**
1. `Illuminate` / Laravel facades and classes (alphabetical by full path)
2. `App` local namespaces
3. Third-party packages (Carbon, Maatwebsite, Barryvdh, Google\Auth)
4. No blank-line grouping — imports are listed in a single flat block
**Examples:**
```php
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\LoginRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
```
**Path Aliases:**
- No custom `use` path aliases detected — only default namespace imports
- Fully qualified class names used in routes: `[App\Http\Controllers\RainfallController::class, 'index']`
- `use App\Exports\HourlyRainfallExport;` and `use Maatwebsite\Excel\Facades\Excel;` are separated with comment blocks explaining why
## Error Handling
**Patterns:**
1. **Redirect back with flash messages** (most common):
```php
return redirect()->back()->with('success', __('toast.stationsuccess'));
return redirect('/dashboard')->with('error', 'Unauthorized Access');
```
2. **Try/catch with ValidationException handling** (AdminController):
```php
try {
$validated = $request->validate([...]);
DB::table('users')->insert([...]);
return redirect()->back()->with('success', __('toast.usersuccess'));
} catch (ValidationException $e) {
$errorMessage = collect($e->errors())->flatten()->first();
return redirect()->back()->with('error', $errorMessage);
} catch (\Exception $e) {
return redirect()->back()->with('error', $e->getMessage());
}
```
- Catches `ValidationException` first, then generic `\Exception`
- Error messages extracted from first validation error
3. **Manual validation checks** (Api/AuthController):
```php
if (!$request->username || !$request->password) {
return response()->json([
'error' => true,
'message' => 'Required field are missing'
]);
}
```
4. **Early return for no data** (RainfallController `graphData`):
```php
if (!$latest) {
return response()->json([
'labels' => [],
'data' => []
]);
}
```
5. **Back with error array** (auth scaffold):
```php
return back()->withErrors(['email' => __($status)]);
return back()->with(['error' => __('auth.failed')]);
```
6. **`with()` helper key conventions:**
- Success: `'success'` key
- Error: `'error'` key
- Status: `'status'` key (Breeze default)
- Error bag: `'userDeletion'` error bag for profile deletion
7. **Exceptions thrown:**
```php
throw new \Exception("Failed to get access token from Firebase credentials."); // FcmService
throw ValidationException::withMessages([...]); // LoginRequest, ConfirmablePasswordController
```
## Validation Approach
**Three patterns detected:**
**Pattern 1 — Inline `$request->validate()`:**
```php
$validated = $request->validate([
'stationid' => 'required|string|max:20|unique:station,stationid',
'stationname' => 'required|string|max:255',
'longitude' => 'required|numeric',
'latitude' => 'required|numeric',
]);
```
- Used in `AdminController` (storeStation, updateStation, storeUser, updateUsers, updatePassword)
- Also in `AuthenticatedSessionController::store()` and `RegisteredUserController::store()`
- Rule syntax: pipe-delimited strings (`'required|string|max:255'`)
- Mixed with array syntax in some places (`['required', 'string', 'max:255']`)
**Pattern 2 — Form Request classes:**
```php
// App\Http\Requests\ProfileUpdateRequest
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'lowercase', 'email', 'max:255',
Rule::unique(User::class)->ignore($this->user()->id)],
];
}
```
- Used for profile updates and auth login
- `LoginRequest` includes custom `authenticate()`, `ensureIsNotRateLimited()`, `throttleKey()` methods
- LoginRequest uses array-syntax validation rules with `Rule` class
**Pattern 3 — `validateWithBag()`:**
```php
$validated = $request->validateWithBag('updatePassword', [
'current_password' => ['required', 'current_password'],
'password' => ['required', Password::defaults(), 'confirmed'],
]);
```
- Used in `PasswordController::update()`
**Validation rule patterns observed:**
- `'required'`, `'string'`, `'max:255'` most common
- `'numeric'` for coordinates
- `'confirmed'` for password confirmation
- `'email'` for email format
- `'unique:table,column'` for uniqueness
- `Rule::unique(User::class)->ignore($this->user()->id)` for unique-with-exception
- `Password::defaults()` for password strength rules
- `'current_password'` for current password verification
## Response Formatting
**Web controllers:**
```php
return view('layout.rainfall', compact('rainfallData', 'lastupdate', 'dates', 'stations', 'displayDate'));
return view('layout.admin.stationmgmt', compact('stations', 'rainfallCount', 'waterLevelCount', 'sirenCount', 'stationsCount'));
```
- Always use `view()` helper with `compact()` for variable passing
- No use of `with()` method on views — only `compact()`
**JSON responses (API):**
```php
return response()->json($graphData);
return response()->json([
'error' => false,
'id' => $user->id,
'username' => $user->name,
]);
```
- API error responses use `'error' => true/false` boolean convention
- Success response directly returns data or object
**Redirect responses:**
```php
return redirect()->back()->with('success', __('toast.stationsuccess'));
return redirect()->intended('/')->with('success', __('auth.success'). $loggedInUser->name . '.');
return Redirect::route('profile.edit')->with('status', 'profile-updated');
```
- Back with flash message
- Intended redirect for auth
- Named route redirect for profile
**PDF downloads:**
```php
$pdf = Pdf::loadView('pdf.sirenhistory', compact('sirenHistory'))
->setPaper('a4', 'potrait');
return $pdf->download('Station_Siren_History.pdf', [], ['Content-Type' => 'application/pdf']);
```
**Excel downloads:**
```php
return Excel::download(
new HourlyRainfallExport($stationid, $startDate, $endDate),
"Hourly Rainfall {$stationid} {$startDate2} - {$endDate2}.xlsx"
);
```
**Pagination:**
```php
$stations = DB::table('station')->select('station.*')->orderBy('stationid')->paginate(5);
$historyData = DB::table('station')->join(...)->paginate(10);
```
## Localization
**Pattern:**
- `__('toast.stationsuccess')` — `__()` helper for PHP
- `@lang('messages.username')` — `@lang` directive for Blade
- Language files in `lang/{en,bm}/`:
- `messages.php` — UI labels
- `toast.php` — flash messages
- `auth.php` — auth-related messages
- `validation.php` — validation error messages
- `passwords.php` — password reset strings
- `pagination.php` — pagination labels
- Locale set via `LocaleController::setLocale($lang)` with Session storage
- `LocalizationMiddleware` reads locale from session
## Commented Code Patterns
**Extensive commented-out code blocks detected:**
- `RainfallController.php` lines 209-222 — entire alternative query commented out
- `cctvController.php` lines 13-26 — entire original SQL query commented out
- `RainfallController.php` line 287 — `$station = DB::table('station')->where('stationid',$stationid)->first();`
- `RainfallController.php` line 9 — `// Add This For Export Data In Excel`
- `AppServiceProvider.php` line 23 — `// URL::forceScheme('https');`
- `FcmService.php` lines 27-30 — commented-out refresh token code
- `AlertController.php` lines 23-27 — commented-out topic mapping logic
- `web.php` lines 6-8 — commented-out route
**Comment style:**
- Single-line PHP comments `//` used throughout custom code
- PHPDoc `/** ... */` used in Breeze scaffolded controllers and model
- Custom code uses minimal PHPDoc; most methods have `// Function ...` plain comments instead
## PHPDoc Usage
**Generated/Breeze scaffold code:** Consistent PHPDoc on all methods:
```php
/**
* Display the login view.
*/
public function create(): View
```
**Custom code (RainfallController, AdminController, etc.):** Minimal to no PHPDoc:
```php
// Function Retrieve Historical Rainfall Data
public function historicalRainfall(Request $request)
```
**Models:** PHPDoc on properties using `@var list<string>` syntax:
```php
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [...];
```
## Database Access Patterns
**Dominant pattern: Raw SQL with `DB::select()` and `DB::table()`:**
- `DB::select("SELECT ...", ['param' => $value])` — raw SQL with named bindings
- `DB::table('station')->select(...)->where(...)->get()` — query builder
- `DB::raw()` for SQL fragments: `DB::raw("TO_CHAR(timestamp, 'HH24:MI') as time")`
- `collect(DB::select(...))` wrapping raw results in collections
**Eloquent used only in specific cases:**
- `User` model: `User::where('name', $request->name)->first()`, `User::factory()->create()`
- Profile CRUD: `$request->user()->fill()`, `$request->user()->save()`, `$user->delete()`
- `$user->increment('login_attempts')`, `$user->update([...])`
- No Eloquent models for `station`, `rainfall`, `waterlevel`, `siren`, `notification` tables
## Middleware Patterns
**Two custom middleware:**
- `AdminMiddleware` — checks `Auth::check()` then `Auth::user()->access_level !== 1`, redirects with error flash
- `LocalizationMiddleware` — reads `Session::get('locale')` defaults to `'en'`, calls `App::setLocale()`
- Both follow standard Laravel middleware signature: `handle(Request $request, Closure $next): Response`
## View Patterns
**Blade templates:**
- Layout inheritance: `@extends('layout.app')` with `@section('content1')`
- Includes: `@include('nav.header')`, `@include('nav.navbar')`
- Localization: `@lang('messages.username')`
- Asset: `{{ asset('logo/logomalaysia.png') }}`
- Route: `{{ route('dashboard') }}`, `{{ route('password.request') }}`
- Bootstrap 5 CSS classes used (`container`, `row`, `col-md-3`, `btn btn-primary`, `modal`, `card`, `form-select`, `pagination`)
- Alpine.js used in some views: `x-data`, `@click`, `x-show`
- Chart.js or similar used for graph rendering (inferred from JSON data endpoints)
- `@auth` / `@endauth` directive for authenticated content
## Service Layer
**One service class:**
- `FcmService` — handles Firebase Cloud Messaging push notifications
- Constructor loads credentials from `env()` config
- Methods: `sendToTopic(string $topic, string $title, string $body)`
- Uses `Google\Auth\Credentials\ServiceAccountCredentials` for auth
- Uses `Illuminate\Support\Facades\Http` for HTTP calls
## Exceptions / Custom Features
- **Export classes** implement `Maatwebsite\Excel\Concerns\FromCollection`, `WithHeadings`, `ShouldAutoSize`
- **Notification class** (`ResetPasswordNotification`) extends Laravel `Notification`, uses `Queueable`, sends via `mail`
- **No custom artisan commands** besides the default `inspire`
---
*Convention analysis: 2026-05-28*