demirkartal / eaglephp-code-quality
Unified PHPStan Level 10 ruleset, Pint configuration, and code quality suite for EaglePHP
Package info
github.com/demirkartal/eaglephp-code-quality
Type:phpstan-extension
pkg:composer/demirkartal/eaglephp-code-quality
Requires
- php: ^8.4 || ^8.5
- laravel/pint: ^1.30
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.2
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- tomasvotruba/type-coverage: ^2.3
README
Shared PHPStan Level 10 ruleset and Laravel Pint configuration for EaglePHP ecosystem projects.
Monorepo checkout: ../STACK.md (dev tooling row). Cursor agent rules: .cursor/rules/eaglephp-code-quality.mdc.
Installation
composer require --dev demirkartal/eaglephp-code-quality
Enable the extension installer plugin in your project composer.json:
{
"config": {
"allow-plugins": {
"phpstan/extension-installer": true
}
}
}
The installer is a transitive dependency of this package; allow-plugins must be set in the consumer project.
Usage
PHPStan
After installation, phpstan/extension-installer registers this package and all bundled extensions automatically. Your project root phpstan.neon should only define paths, optional baseline, and project-specific overrides:
- Do not manually
includevendor/demirkartal/eaglephp-code-quality/phpstan.neon— the extension installer loads the base ruleset. - Do not set
customRulesetUsed: truein the consumer — it is declared in this package'sphpstan.neon. - With
customRulesetUsed: true, built-in level rules are loaded viaconfig.level10.neonin this package; setting onlyparameters.levelin a consumer config does not register those rules.
# phpstan.neon (consumer project) includes: - phpstan-baseline.neon parameters: paths: - src - tests
./vendor/bin/phpstan analyse
PHPStan next (PHP 8.5 target)
Optional forward-compat check against phpVersion: 80500. Include your project phpstan.neon (paths/baseline) plus the vendor next profile — phpstan-next.neon only overrides phpVersion, so the base ruleset is not loaded twice:
# phpstan.next.neon (consumer project) includes: - phpstan.neon - vendor/demirkartal/eaglephp-code-quality/phpstan-next.neon
./vendor/bin/phpstan analyse -c phpstan.next.neon
Laravel Pint
Pint is not auto-wired; pass the shared config explicitly:
./vendor/bin/pint --config vendor/demirkartal/eaglephp-code-quality/pint.json
Preset and standards
pint.json uses preset: per — PHP Evolving Recommendation (PER-CS), which builds on PSR-12 and PSR-1. A separate psr12 preset is not needed.
Explicit rules extend PER with:
- Strict types —
declare_strict_types,void_return, import ordering - PHPDoc (PHPStan Level 10 aligned) — native types preferred; when PHPDoc remains, use modern notation (
array<T>,list<T>), remove deprecated tags (@access,@package), enforce param order and summary punctuation - Layout — trailing commas, blank lines before statements, class attribute separation
Rules not enabled (conflict with native-type / PHPStan policy): phpdoc_add_missing_param_annotation, phpdoc_to_param_type, phpdoc_to_return_type.
After composer update, run composer format in consumer repos — PHPDoc diffs (T[] → array<T> / list<T>) are expected.
Example composer.json scripts:
{
"scripts": {
"format": "pint --config vendor/demirkartal/eaglephp-code-quality/pint.json",
"format:check": "pint --config vendor/demirkartal/eaglephp-code-quality/pint.json --test",
"phpstan": "phpstan analyse --memory-limit=512M"
}
}
Features
- PHPStan Level 10 — strict static analysis for PHP
^8.4 || ^8.5(lowest target:80400). - 100% type coverage — native types enforced for constants, properties, parameters, and return types.
- Type Perfect rules — disallows
empty()/isset()on objects and parameter type removal in child classes. - Auto-wired PHPStan extensions — via
phpstan/extension-installeroncomposer install. - Unified Pint profile —
perpreset (PER > PSR-12), modern PHPDoc types, strict imports
Core stack
| Tool | Constraint | Resolves to (stable) |
|---|---|---|
| PHP | ^8.4 || ^8.5 |
— |
| PHPStan | ^2.2 |
2.2.x (e.g. 2.2.11) |
| Laravel Pint | ^1.30 |
1.30.x (e.g. 1.30.5) |
| extension-installer | ^1.4 |
1.4.x |
| phpstan-strict-rules | ^2.0 |
2.0.x |
| phpstan-phpunit | ^2.0 |
2.0.x |
| phpstan-deprecation-rules | ^2.0 |
2.0.x |
| type-coverage | ^2.3 |
2.3.x |
Active config: phpstan.neon — bleedingEdge, config.level10.neon, phpVersion: 80400.
Alignment with PHPStan “Want to go further?”
This package implements the PHPStan “Want to go further?” recommendations out of the box:
| Recommendation | How this package provides it |
|---|---|
| phpstan-strict-rules | Bundled; auto-loaded via extension-installer |
| Bleeding Edge | bleedingEdge.neon included in phpstan.neon |
| Level 10 | config.level10.neon chain (not just parameters.level) |
| Extra strict parameters | checkUninitializedProperties, checkBenevolentUnionTypes, rememberPossiblyImpureFunctionValues: false, array offset checks, reportUnsafeArrayStringKeyCasting: detect, exception checks — see table below |
| Framework extensions | Consumer responsibility — install Symfony/Doctrine/Laravel PHPStan extensions in your project if needed |
Consumers do not need to duplicate vendor includes, customRulesetUsed, or parameters.level in their own phpstan.neon.
PHPStan rule levels
Levels are cumulative (level 6 includes 0–6).
Source: PHPStan Rule Levels (PHPStan 2.x → 0–10).
| Level | Focus | Checks |
|---|---|---|
| 0 | Basics | Unknown classes/functions/methods on $this, wrong argument counts, always-undefined variables |
| 1 | Variables & magic | Possibly undefined variables; unknown access on classes with __call / __get |
| 2 | Expressions & PHPDoc | Unknown methods on any expression; PHPDoc validation |
| 3 | Return & property types | Return types; types assigned to properties |
| 4 | Dead code | Always-false instanceof / type checks, dead else, unreachable code after return |
| 5 | Argument types | Argument type compatibility for calls |
| 6 | Missing typehints | Missing parameter, return, and property type declarations |
| 7 | Union members | Calling methods/properties that exist on only some union members |
| 8 | Null safety | Method/property access on nullable (T|null) values without a null check |
| 9 | Explicit mixed |
Almost no operations on explicitly typed mixed (pass-through only) |
| 10 | Implicit mixed |
Untyped values treated as strictly as explicit mixed (PHPStan 2.0+) |
Level 10 does not replace level 9: it also treats missing types as mixed and forbids the same unsafe operations.
Extensions & rule packages
phpstan/extension-installer automatically loads and activates:
| Package | Role |
|---|---|
bleedingEdge |
Upcoming PHPStan rule tightening and cutting-edge checks |
phpstan-strict-rules |
Strict ===, type casting, bans weak PHP practices |
phpstan-phpunit |
Test case inference, mock returns, assertion narrowing |
phpstan-deprecation-rules |
Deprecated classes, methods, and functions |
type-coverage |
100% native type coverage (tomasvotruba/type-coverage) |
type-perfect |
Narrowing rules: no empty()/isset() on objects, no param type removal |
Strict analysis parameters
These flags extend beyond Level 10:
| Parameter | Purpose |
|---|---|
customRulesetUsed |
true — required when the base ruleset is loaded via extension-installer (PHPStan 2.x); paired with config.level10.neon to register built-in level rules |
Type coverage (100%) |
constant, declare, param_type, property_type, return_type |
| Type Perfect | no_empty_on_object, no_isset_on_object, no_param_type_removal |
checkArgumentsPassedByReference |
Types of variables passed by reference |
checkBenevolentUnionTypes |
No lenient union assumptions; explicit narrowing required |
checkClassCaseSensitivity |
Exact case for class names and namespaces |
checkMissingCallableSignature |
Rejects bare callable / Closure; requires full signature |
checkMissingOverrideMethodAttribute |
Requires #[\Override] on overridden methods |
checkTooWideParameterOutInProtectedAndPublicMethods |
@param-out broader than actual assignments |
checkTooWideReturnTypesInProtectedAndPublicMethods |
Return type broader than actual returns |
checkTooWideThrowTypesInProtectedAndPublicMethods |
@throws broader than thrown exceptions |
checkUninitializedProperties |
Typed properties must be initialized before access |
reportAlwaysTrueInLastCondition |
Unreachable branches in match / if chains |
reportAnyTypeWideningInVarTag |
No widening via @var beyond inferred type |
reportIgnoresWithoutComments |
Ignored errors require a descriptive comment |
reportPossiblyNonexistentConstantArrayOffset |
Array access with possibly missing constant keys |
reportPossiblyNonexistentGeneralArrayOffset |
Dynamic array keys not statically proven |
reportUnmatchedIgnoredErrors |
Stale ignoreErrors baseline entries fail CI |
reportUnsafeArrayStringKeyCasting |
detect — narrows array key types when decimal-int strings may be cast to int at runtime (PHPStan 2.2+) |
rememberPossiblyImpureFunctionValues |
false — no assuming identical non-pure call results |
treatPhpDocTypesAsCertain |
false — defensive checks even when PHPDoc is present |
exceptions.reportUncheckedExceptionDeadCatch |
catch for exceptions never thrown in try |
exceptions.uncheckedExceptionClasses |
LogicException, RuntimeException exempt from checked flow |
exceptions.check.throwTypeCovariance |
Override may only throw same or narrower exceptions |
exceptions.check.tooWideImplicitThrowType |
Broad implicit throws without documentation |
phpstan-strict-rules (via rules.neon, not duplicated in phpstan.neon) also enables: checkDynamicProperties, checkExplicitMixedMissingReturn, reportMaybesInMethodSignatures, reportMaybesInPropertyPhpDocTypes, reportWrongPhpDocTypeInVarTag, reportNonIntStringArrayKey (with bleeding edge), polluteScopeWithLoopInitialAssignments: false, and related scope-pollution guards.
Optional consumer overrides
Not included in the shared ruleset (opt-in per project when ready):
# Consumer phpstan.neon — example: enforce @throws for checked exceptions parameters: exceptions: check: missingCheckedExceptionInThrows: true checkedExceptionClasses: - 'YourApp\DomainException'
exceptions.check.missingCheckedExceptionInThrows requires defining checkedExceptionClasses or checkedExceptionRegexes. EaglePHP enables this as a project override; add it only when the codebase is ready.
PHPStan does not require PHPDoc when native types suffice; missing type info is enforced at level 6+ and via type coverage.
License
MIT — see LICENSE.