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.phpis lowercasec, should beCctvController.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 (stationDisplayvsdisplayStations) - Auth scaffold methods follow Laravel Breeze conventions:
create(),store(),edit(),update(),destroy(),show()
Models:
- Only one model:
User.php—App\Models\UserextendsAuthenticatable - 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.phplayout/admin/stationmgmt.blade.php,layout/admin/usermgmt.blade.phplayout/notification/rainfall.blade.php,layout/notification/history/rainfall.blade.phplayout/siren/home.blade.php,layout/siren/history.blade.phplayout/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 —stationis singular,usersis 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
<?phpopening 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.phplines 26, 92-94, 209-222 WaterLevelController.phphas extra blank lines and inconsistent spacingAdminController.phptry/catch blocks have widely varying indentation depth
Line Length:
- Some SQL strings in controllers exceed 120 characters (e.g.,
RainfallController.phpline 39-90, lines 161-174) - No line-length enforcement visible; no
.editorconfigor.php-cs-fixerconfig 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
Usermodel 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:
Illuminate/ Laravel facades and classes (alphabetical by full path)Applocal namespaces- Third-party packages (Carbon, Maatwebsite, Barryvdh, Google\Auth)
- 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
usepath aliases detected — only default namespace imports - Fully qualified class names used in routes:
[App\Http\Controllers\RainfallController::class, 'index'] use App\Exports\HourlyRainfallExport;anduse Maatwebsite\Excel\Facades\Excel;are separated with comment blocks explaining why
Error Handling
Patterns:
-
Redirect back with flash messages (most common):
return redirect()->back()->with('success', __('toast.stationsuccess')); return redirect('/dashboard')->with('error', 'Unauthorized Access'); -
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
ValidationExceptionfirst, then generic\Exception - Error messages extracted from first validation error
- Catches
-
Manual validation checks (Api/AuthController):
if (!$request->username || !$request->password) { return response()->json([ 'error' => true, 'message' => 'Required field are missing' ]); } -
Early return for no data (RainfallController
graphData):if (!$latest) { return response()->json([ 'labels' => [], 'data' => [] ]); } -
Back with error array (auth scaffold):
return back()->withErrors(['email' => __($status)]); return back()->with(['error' => __('auth.failed')]); -
with()helper key conventions:- Success:
'success'key - Error:
'error'key - Status:
'status'key (Breeze default) - Error bag:
'userDeletion'error bag for profile deletion
- Success:
-
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()andRegisteredUserController::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
LoginRequestincludes customauthenticate(),ensureIsNotRateLimited(),throttleKey()methods- LoginRequest uses array-syntax validation rules with
Ruleclass
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 uniquenessRule::unique(User::class)->ignore($this->user()->id)for unique-with-exceptionPassword::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 withcompact()for variable passing - No use of
with()method on views — onlycompact()
JSON responses (API):
return response()->json($graphData);
return response()->json([
'error' => false,
'id' => $user->id,
'username' => $user->name,
]);
- API error responses use
'error' => true/falseboolean 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')—@langdirective for Blade- Language files in
lang/{en,bm}/:messages.php— UI labelstoast.php— flash messagesauth.php— auth-related messagesvalidation.php— validation error messagespasswords.php— password reset stringspagination.php— pagination labels
- Locale set via
LocaleController::setLocale($lang)with Session storage LocalizationMiddlewarereads locale from session
Commented Code Patterns
Extensive commented-out code blocks detected:
RainfallController.phplines 209-222 — entire alternative query commented outcctvController.phplines 13-26 — entire original SQL query commented outRainfallController.phpline 287 —$station = DB::table('station')->where('stationid',$stationid)->first();RainfallController.phpline 9 —// Add This For Export Data In ExcelAppServiceProvider.phpline 23 —// URL::forceScheme('https');FcmService.phplines 27-30 — commented-out refresh token codeAlertController.phplines 23-27 — commented-out topic mapping logicweb.phplines 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 bindingsDB::table('station')->select(...)->where(...)->get()— query builderDB::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:
Usermodel: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,notificationtables
Middleware Patterns
Two custom middleware:
AdminMiddleware— checksAuth::check()thenAuth::user()->access_level !== 1, redirects with error flashLocalizationMiddleware— readsSession::get('locale')defaults to'en', callsApp::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/@endauthdirective 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\ServiceAccountCredentialsfor auth - Uses
Illuminate\Support\Facades\Httpfor HTTP calls
Exceptions / Custom Features
- Export classes implement
Maatwebsite\Excel\Concerns\FromCollection,WithHeadings,ShouldAutoSize - Notification class (
ResetPasswordNotification) extends LaravelNotification, usesQueueable, sends viamail - No custom artisan commands besides the default
inspire
Convention analysis: 2026-05-28