# 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 - `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` syntax: ```php /** * The attributes that are mass assignable. * * @var list */ 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*