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 (ESLint + Prettier)
ESLint uses flat config in eslint.config.js (there is no .eslintrc.cjs), with typescript-eslint, eslint-plugin-vue, eslint-plugin-import-x and eslint-plugin-jsonc.
- Indentation: 4 spaces (
indentfor TS,vue/script-indent+vue/html-indentfor SFCs — the two are mutually disabled so they never fight). - Semicolons required.
- Relative imports must include their file extension (
import-x/extensions:always,ignorePackages: true) — so@/Plugins/i18n.ts, but plainvue. - Component names in templates must use kebab-case (
registeredComponentsOnly: false— this applies to every component). import-x/ordergroups imports with blank lines between groups.- Unused variables flagged except those prefixed with
_. - Max line length not enforced by ESLint (Prettier handles it at 120).
- JSON & JSONC files linted with 4-space indentation.
Vue-specific rules:
- HTML indentation: 4 spaces.
- Max attributes per line: 1 (
vue/max-attributes-per-linesingleline: 1) — this matches Prettier'ssingleAttributePerLine.
Prettier
.prettierrc:
tabWidth: 4,singleQuote: true,semi: true,printWidth: 120proseWrap: "never"vueIndentScriptAndStyle: true—<script>content is indented inside the SFCsingleAttributePerLine: true- Plugins:
organize-imports,tailwindcss(class sorting),classnames(long class-list wrapping)
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: the tenant is a Shop (not "Garage" — that term survives only in prose). 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 → ESLint → 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) |
| ESLint | TS/Vue lint | npm run lint / npm run lint:fix |
| Prettier | Formatting, import & Tailwind class sort | npm run prettier |
| vue-tsc | TS/Vue type checking | npm 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.