# Code Style ## PHP - PHP 8.2+ features: constructor property promotion, readonly properties, enums, match expressions - Type declarations on all method parameters and return types — no untyped signatures - Named arguments for clarity on calls with 3+ parameters - Enums over string constants: `FuelType::E10`, `Recommendation::Wait`, `NotificationChannel::WhatsApp` - `final` classes for Services and Jobs — they are not designed for extension ## Laravel conventions - Route model binding over manual `findOrFail()` in controllers - Form Requests for any validated input - `config()` helper over `env()` outside of config files — never call `env()` in app code - Events + Listeners for side effects (e.g. `PricesUpdatedEvent` triggers scoring) - Do not use `DB::transaction()` manually — wrap in Service methods where needed ## Naming - Services: `{Noun}Service` — e.g. `AlertScoringService` - Jobs: `{Verb}{Noun}Job` — e.g. `SendFuelAlertJob`, `PollFuelPricesJob` - Events: `{Noun}{PastTense}Event` — e.g. `PricesUpdatedEvent` - Livewire components: `{Context}/{Feature}` — e.g. `Dashboard/FuelRecommendation` - Migrations: descriptive, e.g. `create_station_prices_table`, `add_whatsapp_fields_to_users_table` ## Formatting - 4-space indentation (PSR-12) - Max line length: 120 characters - Run `./vendor/bin/pint` before committing (Laravel Pint, default ruleset) - No commented-out code committed — use git to recover old code ## Security - Never store raw passwords — Bcrypt via Laravel's Hash facade only - Validate and sanitise all user input — postcodes, phone numbers, fuel type selections - Phone numbers: strip non-numeric characters, enforce UK format (07xxx or +447xxx) - OTP codes: 6 digits, expire in 10 minutes, single use only - All Stripe webhook payloads verified via Cashier's built-in signature check ## Environment - Local: Laravel Valet or `php artisan serve` - Production: IONOS VPS, Nginx + PHP-FPM, MySQL, Redis - Never commit `.env` — `.env.example` must stay up to date with all required keys - Queue driver: `redis` in production, `sync` in testing only