gboquizosanchez / phpstan-code-limits
PHPStan rules that enforce size, layout and expression limits on PHP code
Package info
github.com/gboquizosanchez/phpstan-code-limits
Type:phpstan-extension
pkg:composer/gboquizosanchez/phpstan-code-limits
Requires
- php: ^8.4
- phpstan/phpstan: ^2.1
- tomasvotruba/cognitive-complexity: ^1.3
Requires (Dev)
- laravel/pint: ^1.0
- pestphp/pest: ^5.3
- rector/rector: ^2.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
gboquizosanchez/phpstan-code-limits
Size, layout and expression limits for PHP code, as PHPStan rules
Too many parameters, a 120-character signature, a non-static closure?
composer test fails until it is fixed.
Why code limits?
Style rules written in a guide get skipped, and reviewing them by hand is slow. This extension turns each limit into a regular PHPStan error with its own identifier, so the gate fails and whoever wrote the code fixes it before review.
| Problem | Without code limits | With code limits |
|---|---|---|
| A method grows to 6 parameters | ❌ Spotted in review, maybe | ✅ codeLimits.params fails the analysis |
| A long signature on a single line | ❌ Reformatted by hand | ✅ codeLimits.signature points at it |
A closure that could be static |
❌ Nobody notices | ✅ codeLimits.closureStatic |
| A function nobody can follow anymore | ❌ Grows until it breaks | ✅ Cognitive complexity capped at 15 |
| One rule does not fit a folder | ❌ Ad hoc exceptions | ✅ PHPStan's own ignoreErrors |
Version compatibility
| Package | PHPStan | PHP |
|---|---|---|
| 1.x | 2.x | ^8.4 |
🚀 Features
- One identifier per limit: every error is
codeLimits.<rule>, so each limit can be ignored on its own. - Configurable numbers: parameters, returns, methods and line length are PHPStan parameters.
- Cognitive complexity included: ships
tomasvotruba/cognitive-complexitywith a function limit of 15. - One line of setup:
code-limits-installwrites a single include into yourphpstan.neon, with or withoutphpstan/extension-installer. - Laravel preset: skips
config/and the skeleton's path concatenations out of the box. - Dev only:
--devdependency, nothing runs in production.
📦 Installation
composer require --dev gboquizosanchez/phpstan-code-limits
Then add the configuration to your phpstan.neon:
vendor/bin/code-limits-install
It writes the one line your phpstan.neon needs from this package, chosen for your project:
| Project | Included file |
|---|---|
With phpstan/extension-installer |
complexity.neon |
With phpstan/extension-installer, Laravel |
laravel.neon |
Without phpstan/extension-installer |
standalone.neon |
Without phpstan/extension-installer, Laravel |
standalone-laravel.neon |
A project is Laravel when it has an artisan file. Lines written by older versions are replaced, and
running it again changes nothing.
The cognitive complexity limit has to come from that line: tomasvotruba/cognitive-complexity loads
its own defaults (function 9, class 40) after this extension, and with
phpstan/extension-installer the extensions are
already loaded, so including them again would fail.
Laravel preset
The Laravel entry points also skip the files the framework writes for you:
config/*: every limit, since Laravel andvendor:publishwrite these files.bootstrap/app.phpandpublic/index.php:codeLimits.concat, for the skeleton's__DIR__.'/../...'paths.
Those paths are resolved from the current working directory, so run PHPStan from the project root,
as composer test and IDEs do.
📋 Limits
| Identifier | Limit |
|---|---|
codeLimits.params |
At most 3 parameters per function or method. Promoted constructor parameters of a readonly class do not count. |
codeLimits.returns |
At most 3 return statements per function or method. |
codeLimits.methods |
At most 20 methods per class-like. |
codeLimits.signature |
A signature longer than 80 characters has one parameter per line with a trailing comma; a shorter one stays on one line. |
codeLimits.promotedCtor |
A constructor with promoted properties has one parameter per line with a trailing comma. |
codeLimits.throw |
throw_if() / throw_unless() on one line when it fits in 80 characters, otherwise one argument per line; condition and message stay inline. |
codeLimits.matchArm |
A match arm with several conditions has one condition per line with a trailing comma. |
codeLimits.tuple |
No returning an array just to destructure it with [$a, $b] = .... |
codeLimits.foreach |
foreach iterates over a variable, a property or a single call, and its header fits in 80 characters. |
codeLimits.nestedArgs |
No two or more nested call levels in arguments, unless the statement fits in 80 characters. |
codeLimits.closureStatic |
Closures and arrow functions are static unless they use $this or are bound by the framework. |
codeLimits.closureUseOrder |
use (...) variables are ordered by first usage in the body. |
codeLimits.concat |
String interpolation or sprintf() instead of . concatenation. |
codeLimits.ellipsis |
Three ASCII dots ... instead of the single ellipsis character (U+2026). |
complexity.functionLike |
Cognitive complexity of a function or method at most 15. |
⚙️ Configuration
The numbers are configurable:
parameters: codeLimits: maxParameters: 3 maxReturns: 3 maxMethods: 20 maxLineLength: 80 cognitive_complexity: function: 15 # after the code-limits include
Turn a limit off, everywhere or for some paths, with PHPStan's own ignoreErrors:
parameters: ignoreErrors: - identifier: codeLimits.concat paths: - config/*
🧪 Testing
composer test
Contributing
Contributions are welcome!
- 🐛 Report bugs via GitHub Issues
- 💡 Suggest features or improvements
- 🔧 Submit pull requests with fixes or enhancements
Credits
- Author: Germán Boquizo Sánchez
📄 License
This package is open-source software licensed under the MIT License.