Search by

demirkartal / eaglephp-code-quality

CemDemirkartal

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

Statistics

Installs: 25

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.1.0 2026-09-20 07:51 UTC

This package is auto-updated.

Last update: 2026-09-20 07:51:31 UTC


README

Latest Stable Version Total Downloads License

Shared PHPStan Level 10 ruleset and Laravel Pint configuration for EaglePHP ecosystem projects.

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 include vendor/demirkartal/eaglephp-code-quality/phpstan.neon — the extension installer loads the base ruleset.
  • Do not set customRulesetUsed: true in the consumer — it is declared in this package's phpstan.neon.
  • With customRulesetUsed: true, built-in level rules are loaded via config.level10.neon in this package; setting only parameters.level in 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 typesdeclare_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-installer on composer install.
  • Unified Pint profileper preset (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.14)
Laravel Pint ^1.30 1.30.x+ (e.g. 1.32.1)
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.neonbleedingEdge, 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.missingCheckedExceptionInThrows Require @throws for checked exceptions thrown by functions/methods
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.

Extending uncheckedExceptionClasses

Indexed NEON lists merge across included configs (append). Shared defaults (LogicException, RuntimeException) stay active when a consumer adds more entries — you do not need to re-list them. To replace the list entirely, use the NEON override suffix:

# Consumer phpstan.neon — add project-specific unchecked types (lists merge)
parameters:
  exceptions:
    uncheckedExceptionClasses:
      - 'Psr\Container\ContainerExceptionInterface'
      - 'Psr\Container\NotFoundExceptionInterface'
# Replace the entire list (drops shared LogicException / RuntimeException)
parameters:
  exceptions:
    uncheckedExceptionClasses!:
      - 'App\Infrastructure\InfraException'

This merge behaviour is guarded by composer analyse:unchecked-merge (tests/assert-unchecked-exception-merge.php).

missingCheckedExceptionInThrows does not require checkedExceptionClasses / checkedExceptionRegexes when using the unchecked-list mode (everything checked except listed classes). Those keys switch to the inverse model (only listed types are checked).

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.