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

16 KiB

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.phpApp\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:

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):

    return redirect()->back()->with('success', __('toast.stationsuccess'));
    return redirect('/dashboard')->with('error', 'Unauthorized Access');
    
  2. Try/catch with ValidationException handling (AdminController):

    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):

    if (!$request->username || !$request->password) {
        return response()->json([
            'error' => true,
            'message' => 'Required field are missing'
        ]);
    }
    
  4. Early return for no data (RainfallController graphData):

    if (!$latest) {
        return response()->json([
            'labels' => [],
            'data' => []
        ]);
    }
    
  5. Back with error array (auth scaffold):

    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:

    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():

$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:

// 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():

$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:

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):

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:

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:

$pdf = Pdf::loadView('pdf.sirenhistory', compact('sirenHistory'))
    ->setPaper('a4', 'potrait');
return $pdf->download('Station_Siren_History.pdf', [], ['Content-Type' => 'application/pdf']);

Excel downloads:

return Excel::download(
    new HourlyRainfallExport($stationid, $startDate, $endDate),
    "Hourly Rainfall {$stationid} {$startDate2} - {$endDate2}.xlsx"
);

Pagination:

$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:

/**
 * Display the login view.
 */
public function create(): View

Custom code (RainfallController, AdminController, etc.): Minimal to no PHPDoc:

// Function Retrieve Historical Rainfall Data
public function historicalRainfall(Request $request)

Models: PHPDoc on properties using @var list<string> syntax:

/**
 * 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