378 lines
16 KiB
Markdown
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*
|