Appearance
Coding Standards & Conventions
This document codifies style requirements, formatting rules, and architectural conventions enforced across the Vroum Solutions codebase.
PHP (Pint Configuration)
Formatter: Laravel Pint (pint.json). Preset: laravel with custom rules.
Key enforced rules:
declare_strict_types: All PHP files MUST start withdeclare(strict_types=1);.fully_qualified_strict_types: Enforces fully-qualified references for built-in types where needed.- Class element ordering (
ordered_class_elements):- Traits (
usestatements) - Enum cases
- Constants (public → protected → private)
- Properties (public → protected → private)
- Constructor / Destructor
- Magic methods (
__*) - PHPUnit methods
- Other methods by visibility (public static → public → protected static → protected → private static → private)
- Traits (
- Visibility required: Every property & method must declare visibility.
- Convert protected to private (
protected_to_private) when feasible. - Enforce
strict_comparison(===,!==). date_time_immutable: date/time type hints and returns useDateTimeImmutable.- No superfluous
elseif/ uselesselseblocks. - Disallow multiple statements per line.
- Global namespace imports for classes, constants, and functions.
- Use modern type casting (
modernize_types_casting) andarray_pushover$a[] =chains. concat_space: one space around..- Traits and interfaces sorted (
ordered_traits,ordered_interfaces);self_accessor/self_static_accessorenforced. - Class attribute separation: one blank line between methods; properties, constants and trait imports kept ungapped.
new_with_parentheses: false— writenew Foowithout parentheses when there are no arguments.- MB string functions enforced (
mb_str_functions). tests/TestCase.phpis excluded from Pint (notPath).
Example template:
php
declare(strict_types=1);
namespace App\Services;
use DateTimeImmutable;
use function array_filter;
final class InvoiceReportGenerator
{
public function __construct(
private readonly PdfClient $pdf,
) {}
public function generate(WorkOrder $workOrder, DateTimeImmutable $asOf): Report
{
// ...
}
}TypeScript / Vue (Oxlint + Oxfmt via vp check)
Lint and format are configured in the lint and fmt blocks of vite.config.ts; there is no ESLint or Prettier config. vp check runs both plus type-aware lint (typeAware / typeCheck on). eslint-plugin-jsonc is loaded as an Oxlint JS plugin for the JSON rules.
- Relative imports must include their file extension (
import/extensions:always,ignorePackages: true) — so@/Plugins/i18n.ts, but plainvue. - Unused variables flagged except those prefixed with
_. - Type-aware rules (
typescript/await-thenable,no-misused-promises,no-floating-promises,return-awaitin try/catch) apply to.tsand.vue. - JSON & JSONC files linted with 4-space indentation.
- Component names in templates use kebab-case; one attribute per line is enforced by the formatter.
Oxfmt
fmt block in vite.config.ts. The IDE's Oxfmt plugin reads the same block in automatic mode, so vp fmt and format-on-save agree.
tabWidth: 4,singleQuote: true,semi: true,printWidth: 120proseWrap: "never"vueIndentScriptAndStyle: true—<script>content is indented inside the SFCsingleAttributePerLine: truesortTailwindcsssorts class lists; long class strings stay on one line (there is no equivalent ofprettier-plugin-classnames)htmlWhitespaceSensitivity: "ignore"— a template interpolation too long for the line breaks the surrounding tag, instead of splitting theitself
File & Naming Conventions
| Category | Convention |
|---|---|
| Actions | VerbNoun (e.g., CreateUser, UpdateShop) grouped by domain subfolder |
| Models | Singular noun (e.g., Car, WorkOrder) |
| Data DTOs | <Context><Entity>Data or <Verb><Entity>Data (e.g., UserWorkspaceData) |
| Enums | Noun or qualified variant (e.g., TirePosition, WorkOrderStatus) — placed in app/Entities |
| Services | Noun + Service or a clear domain concept |
| Form Requests | <Action><Entity>Request |
| Policies | <Entity>Policy |
| Jobs | Imperative (SyncExternalPrices) |
| Events | Past tense or domain change (CarSaved, VehicleCreated) |
| Listeners | HandleCarSaved, SendInvoiceEmail |
| Vue Components | PascalCase filenames, kebab-case usage in templates |
| Stores | <Entity>Store.ts |
Domain vocabulary: Shop is the model and the tenant; Garage is that same shop seen from the client portal, and it is code vocabulary there — GarageData, Client\GarageController, Pages/Client/Garage/, the client.garages.* routes. Shop-side code says Shop; portal-side code says Garage. Car is a customer vehicle tied to a client and work orders; Vehicle is the catalog/spec record behind makes, models and images. They are separate models — check which one you need.
Architectural Conventions
- Controllers remain thin: delegate to Actions or Services immediately.
- Favor immutable Value Objects (
final+ private constructor static constructors) for domain invariants. - Avoid primitive obsession: wrap complex structured arrays in DTOs or Value Objects.
- Limit static access; prefer dependency injection.
- Use queued jobs for I/O heavy or long-running operations.
Testing Standards
- Prefer Feature tests (simulate realistic HTTP flows) in
tests/Feature. - Use factories for model setup; avoid manual attribute arrays.
- Keep one logical expectation group per test (readability).
- Extend
Tests\DatabaseTestCase(RefreshDatabase+WithFaker) for anything touching the DB, andTests\Unit\Actions\ActionTestCasefor tenant-aware Action tests. - Use the
#[Test]attribute withsnake_casemethod names describing behavior —store_validates_notes_max_length(). Never thetest_prefix. - Every bug fix ships with a new or updated test covering it.
Git & Branching
mainholds the stable release.- Feature branches:
feature/<short-description>. - Run the full validation pipeline before opening a PR: Pint → PHPUnit →
vp check→ type-check → Vitest → Build.
Commit Messages
Structure: <type>(scope): subject Types: feat, fix, docs, style, refactor, test, chore, perf.
- Subject: imperative mood, max 50 characters, no trailing period.
- Simple changes: one line. Complex changes: add a body (wrapped at 72 chars) explaining what and why.
- Keep commits atomic; split unrelated concerns.
Example: feat(tire): add storage assignment action.
Documentation Standards
- Place developer docs in
docs/(this folder). - Include code fences with explicit language tags.
- Provide copyable shell commands (one per line).
Enforcement Summary
| Tool | Scope | Command |
|---|---|---|
| Pint | PHP formatting | ./vendor/bin/sail pint (--dirty for uncommitted files only) |
Oxlint (vp lint) | TS/Vue lint, type-aware | vp lint resources/js / vp lint resources/js --fix |
Oxfmt (vp fmt) | Formatting & Tailwind class sort | vp fmt (--check to verify) |
vp check | Format + lint + type-aware lint in one pass | vp check / vp check --fix |
| vue-tsc | TS/Vue type checking (CI) | vp run type-check |
| Rector | Optional refactoring | ./vendor/bin/sail php vendor/bin/rector process |
| Type Generation | DTOs & Enums → TS | composer run transform-types |
See development.md for workflow details.