edhrendal-sf-tools / phpstan-edhrendal
PHPStan custom rules for Edhrendal projects
Package info
github.com/EdhrendalSfTools/phpstan-edhrendal
Type:phpstan-extension
pkg:composer/edhrendal-sf-tools/phpstan-edhrendal
Requires
- php: >=8.5
- ext-ctype: *
- phpstan/phpstan: ~2.1.0
Requires (Dev)
- phpstan/phpstan-phpunit: ~2.0.0
- phpunit/phpunit: ~11.5.0
README
PHPStan extension providing custom static analysis rules for Edhrendal projects.
Rules are opt-in — none are active by default. Include only the ones you need.
Requirements
| Version | |
|---|---|
| PHP | >= 8.5 |
phpstan/phpstan |
~2.1.0 |
Installation
Important — use a dedicated quality tools project
This package should not be installed as a dev dependency of your application. QA tools (PHPStan, PHP CS Fixer, etc.) belong in a separate Composer project, isolated from your application's dependency tree. This avoids version conflicts, keeps your application's
composer.lockclean, and makes tool upgrades independent from application releases.A common convention is a
tools/directory at the root of your repository with its owncomposer.json, or a dedicated repository shared across multiple projects.my-project/ ├── tools/ │ ├── composer.json ← QA tools live here │ └── composer.lock ├── src/ └── composer.json ← application dependencies only
# From your QA tools project
composer require edhrendal-sf-tools/phpstan-edhrendal
Usage
extension.neon defines the full configuration schema and default values for all rules. How it gets loaded depends on your setup.
Standard setup (PHPStan in the same vendor as the project)
PHPStan auto-loads extension.neon via extra.phpstan.includes in composer.json. You do not need to include it manually — just include the rules you want:
# phpstan.neon includes: - vendor/edhrendal-sf-tools/phpstan-edhrendal/rules/doctrine/no-repository-magic-method.neon - vendor/edhrendal-sf-tools/phpstan-edhrendal/rules/php/named-arguments.neon - vendor/edhrendal-sf-tools/phpstan-edhrendal/rules/symfony/controller-invokable.neon
Separate tools directory
When PHPStan is installed in a dedicated directory (e.g. tools/), its auto-discovery does not cover that vendor tree. You must include extension.neon explicitly, the same way you would for phpstan-symfony or phpstan-doctrine:
# phpstan.neon includes: - /tools/phpstan/vendor/phpstan/phpstan-symfony/extension.neon - /tools/phpstan/vendor/phpstan/phpstan-doctrine/extension.neon - /tools/phpstan/vendor/edhrendal-sf-tools/phpstan-edhrendal/extension.neon - /tools/phpstan/vendor/edhrendal-sf-tools/phpstan-edhrendal/rules/doctrine/no-repository-magic-method.neon - /tools/phpstan/vendor/edhrendal-sf-tools/phpstan-edhrendal/rules/php/named-arguments.neon - /tools/phpstan/vendor/edhrendal-sf-tools/phpstan-edhrendal/rules/symfony/controller-invokable.neon
All parameters have sensible defaults. Override only what differs from the defaults:
# phpstan.neon includes: - vendor/edhrendal-sf-tools/phpstan-edhrendal/rules/doctrine/no-repository-magic-method.neon - vendor/edhrendal-sf-tools/phpstan-edhrendal/rules/symfony/controller-invokable.neon parameters: edhrendal: doctrine: repository: strict: true symfony: controller: rootAllowedDomains: - index - home - dashboard
Available Rules
NoRepositoryMagicMethodRule — Doctrine
Forbids the use of generic and magic Doctrine repository methods in favor of explicit, named methods declared directly in the repository class.
Always reported:
| Call | Reason |
|---|---|
$repo->findBy(['field' => $value]) |
Generic criteria array — create a named method |
$repo->findOneBy(['field' => $value]) |
Generic criteria array — create a named method |
$repo->findByFoo(…) |
Magic method via __call, not declared in the class |
$repo->findOneByFoo(…) |
Magic method via __call, not declared in the class |
Also reported in strict mode:
| Call |
|---|
$repo->find($id) |
$repo->findAll() |
Include:
# phpstan.neon includes: - vendor/edhrendal-sf-tools/phpstan-edhrendal/rules/doctrine/no-repository-magic-method.neon
Strict mode (optional):
# phpstan.neon includes: - vendor/edhrendal-sf-tools/phpstan-edhrendal/rules/doctrine/no-repository-magic-method.neon parameters: edhrendal: doctrine: repository: strict: true # default: false
Example — before / after:
// ❌ reported $this->userRepository->findBy(['active' => true]); $this->userRepository->findByEmail($email); // ✅ ok $this->userRepository->findActive(); $this->userRepository->findOneByEmail($email); // if declared in the class
ControllerInvokableRule — Symfony
Enforces structural and naming conventions on Symfony controllers.
Always reported:
| Violation | Description |
|---|---|
| Bad naming | Class name does not follow {Domain}{HttpMethod}Controller (e.g. PeriodeCalculGetController) |
Missing __invoke() |
Controller does not declare a __invoke() method |
| Public non-magic method | Controller declares a public method other than magic ones (those starting with __) |
| Root placement | Controller sits directly under the Controller namespace segment without a sub-namespace |
Recognised HTTP methods: Get, Post, Put, Patch, Delete, Head, Options, Connect, Trace, Any.
Include:
# phpstan.neon includes: - vendor/edhrendal-sf-tools/phpstan-edhrendal/rules/symfony/controller-invokable.neon
Parameters (optional):
# phpstan.neon includes: - vendor/edhrendal-sf-tools/phpstan-edhrendal/rules/symfony/controller-invokable.neon parameters: edhrendal: symfony: controller: rootAllowedDomains: # default: [index, home] — case-insensitive - index - home - dashboard # add any domain used for your index page excludedClassesFile: null # path to a PHP file returning string[] of FQCNs
The excludedClassesFile is a PHP file that returns an array of fully-qualified class names to skip entirely. Changes (additions, removals) are picked up on the next PHPStan run without recompilation:
// config/phpstan/excluded_controllers.php <?php return [ App\Controller\Legacy\SomeLegacyController::class, ];
parameters: edhrendal: symfony: controller: excludedClassesFile: '%rootDir%/config/phpstan/excluded_controllers.php'
Example — before / after:
// ❌ reported — missing HTTP method in name, no __invoke, public non-magic method class UserController { public function index(): Response { … } public function show(int $id): Response { … } } // ✅ ok class UserGetController { public function __invoke(int $id): Response { … } }
NamedArgumentsRule — PHP
Enforces named argument conventions on all function, method, static-method, and constructor calls.
Always reported:
| Violation | Description |
|---|---|
| Wrong order | Named arguments are not in the same order as declared in the signature |
| Missing name | An argument is passed positionally when named syntax is required |
The order error includes both the expected and actual orderings:
Named arguments are not in the same order as declared in the function signature
(expected: $search, $replace, $subject; got: $subject, $search, $replace).
The missing-name error is reported at the argument's line and names the corresponding parameter:
Argument $subject must be passed as a named argument.
Calls to functions or methods that do not support named arguments (some C-extension functions) are silently skipped.
Include:
# phpstan.neon includes: - vendor/edhrendal-sf-tools/phpstan-edhrendal/rules/php/named-arguments.neon
Parameters (optional):
# phpstan.neon includes: - vendor/edhrendal-sf-tools/phpstan-edhrendal/rules/php/named-arguments.neon parameters: edhrendal: php: namedArguments: forceNamed: true # default: true forceSingleRequired: false # default: false
| Parameter | Default | Description |
|---|---|---|
forceNamed |
true |
Require named arguments on all calls. When false, only the order check is active. |
forceSingleRequired |
false |
When true, removes the exemption for callables that have only one required parameter (e.g. setTitle('foo') must become setTitle(title: 'foo')). |
Example — before / after:
// ❌ reported — wrong order str_replace(subject: $html, search: '<br>', replace: "\n"); // ❌ reported — positional arguments (forceNamed: true) str_replace('<br>', "\n", $html); // ✅ ok str_replace(search: '<br>', replace: "\n", subject: $html); // ✅ ok — single required parameter, exempt by default strtolower($string);