Spec: Strict Type System
Issue: MAR-43 Date: 2026-04-23 Status: In Review
Overview
Shipper enforces strict coding standards across the entire codebase. Every PHP file declares strict_types=1, all classes are final by default, return types are mandatory, no mixed types are permitted, and static analysis (PHPStan level 9) plus code style enforcement (Laravel Pint) run in CI.
Current Implementation
Key Files
| File | Role |
|---|---|
phpstan.neon |
PHPStan config, level 9, Larastan extension included |
pint.json |
Laravel Pint ruleset (strict, final classes, native functions) |
phpunit.xml |
Test suite config with failOnRisky="true" and failOnWarning="true" |
composer.json |
Scripts: composer analyse, composer format, composer format:check, composer test |
docs/STRICT_STANDARDS.md |
Full documentation of all strict rules |
docs/PHPSTAN_FIXES.md |
Historical fix log for phpstan configuration issues |
phpstan.neon (critical settings)
includes:
- vendor/larastan/larastan/extension.neon
- vendor/pestphp/pest/extension.neon
parameters:
paths:
- app
- config
- routes
- bootstrap
- tests
level: 9
checkExplicitMixed: true
checkUninitializedProperties: true
checkDynamicProperties: true
phpVersion: 80300
excludePaths:
- vendor
- tests/Pest.php
pint.json (critical rules)
{
"preset": "laravel",
"rules": {
"declare_strict_types": true,
"strict_comparison": true,
"strict_param": true,
"final_class": true,
"void_return": true,
"array_syntax": { "syntax": "short" },
"native_function_invocation": { "include": ["@all"], "scope": "all", "strict": true },
"no_unused_imports": true,
"ordered_imports": { "sort_algorithm": "alpha" }
}
}
Functional Requirements
FR-001 — All PHP files declare strict_types
Every .php file must begin with <?php\n\ndeclare(strict_types=1); — enforced by Pint rule declare_strict_types.
FR-002 — All concrete classes are final
Pint rule final_class: true — all classes must be final unless explicitly designed for inheritance. No exceptions without team consensus.
FR-003 — All method parameters have explicit types
No parameter type may be omitted. strict_param: true in Pint. PHPStan checkMissingCallableSignature: true.
FR-004 — All method return types are explicit
Every method must declare a return type (: void, : string, : array, etc.). void_return: true in Pint. PHPStan level 9 enforces this.
FR-005 — No mixed type anywhere
PHPStan checkExplicitMixed: true — the type mixed is prohibited. All properties, parameters, and return values must have concrete types.
FR-006 — Strict comparisons only
Pint strict_comparison: true — == and != are forbidden; === and !== must be used always.
FR-007 — Native function calls use backslash
Pint native_function_invocation: { scope: "all", strict: true } — all built-in PHP functions must be called with a leading backslash to avoid namespace resolution overhead.
FR-008 — CI enforces all rules GitHub Actions workflow runs: Pint format check, PHPStan level 9 analysis, Pest tests. All three must pass for any PR to be merged.
FR-009 — Tests fail on risky or warning
phpunit.xml sets failOnRisky="true" and failOnWarning="true" — test suite fails if a test does not assert anything or emits a PHPUnit warning.
PHP Version
PHP 8.3+ required. PHPStan phpVersion: 80300. Current composer.json requires "php": "^8.3".
Tool Usage
# Check code style (does not modify)
composer format:check
composer format
# Run static analysis
composer analyse
# Run tests
composer test
Edge Cases
- Abstract classes:
final_class: truedoes not apply — abstract classes cannot be final by PHP semantics.final_internal_class: falseandfinal_public_method_for_abstract_class: falsein Pint config allow non-final public methods on abstract classes. - Interfaces: Not affected by
final_classrule — interfaces remain interface types. - Exceptions: Must still be
finalclasses per the rule. Inner exception classes may extend\Exceptionbut must befinal. - Test files: Excluded from PHPStan (
excludePaths: - tests/Pest.php) but included in Pint analysis viatests/path in Pint config.
Acceptance Criteria
-
composer formatproduces no diff on clean codebase -
composer analyseexits with code 0 (no PHPStan errors at level 9) -
composer testexits with code 0 (all Pest tests pass, no risky/warning) - No
mixedtypes anywhere inapp/orconfig/ - No
==or!=comparisons inapp/orconfig/ - All classes in
app/arefinal - All functions called with leading backslash in
app/andconfig/