Custom PHPStan rules used across IWF projects to enforce coding standards, security practices, and architectural conventions.
- PHP 8.3 or higher
- PHPStan ^2.1
composer require --dev iwf-web/phpstan-rulesInclude the rule set in your phpstan.neon or phpstan.neon.dist:
includes:
- vendor/iwf-web/phpstan-rules/rules.neonSeveral rules require or accept configuration parameters under the iwf key.
parameters:
iwfWeb:
controller:
controllerNamespace: 'App\Controller'
excludedNamespaces: []
excludedControllers:
- 'App\Controller\Api\Security\LoginController'Enforce that specific namespaces are always imported with a defined alias:
parameters:
iwfWeb:
requiredUseAlias:
aliasDefinitions:
- { namespace: 'Doctrine\ORM\Mapping', alias: 'ORM' }
- { namespace: 'Symfony\Component\Validator\Constraints', alias: 'Assert' }Enforce that certain attributes may only appear alongside required companion attributes:
parameters:
iwfWeb:
attributeRequirements:
attributeDefinitions:
-
attribute: 'Symfony\Component\Routing\Attribute\Route'
requires:
- 'OpenApi\Attributes\Tag'
- 'Symfony\Component\Security\Http\Attribute\IsGranted'parameters:
iwfWeb:
forceDateProvider:
allowedFormats:
- 'Y-m-d'
- 'Y-m-d H:i:s'
- 'Y-m-d\TH:i:s'
- 'Y-m-d\TH:i:sP'
- 'U'parameters:
iwfWeb:
handleBusTrait:
handleBusTraitMappings:
queryBus: 'Coala\MessengerBundle\Messenger\HandleQueryBusTrait'
handleBusTraitNamespaces:
- 'App\Controller'parameters:
iwfWeb:
requireInvalidDataTestGroup:
requireInvalidDataTestGroupNamespaces:
- 'App\Tests'Flags calls to string functions that have a multibyte-safe counterpart and may produce incorrect results when the input contains multibyte characters (e.g. UTF-8).
Affected functions: chr, ord, parse_str, str_pad, str_split, stripos, stristr, strlen, strpos, strrchr, strripos, strrpos, strstr, strtolower, strtoupper, substr, substr_count.
// ❌ flagged
$len = strlen($userInput);
// ✅ correct
$len = mb_strlen($userInput);Prevents using classes from the legacy Symfony\...\Annotation\ namespace as PHP 8 attributes. Symfony has migrated all annotations to Symfony\...\Attribute\.
// ❌ flagged
#[Symfony\Component\Routing\Annotation\Route('/foo')]
// ✅ correct
#[Symfony\Component\Routing\Attribute\Route('/foo')]Enforces that configured namespaces are always imported under a specific alias. Applies to both regular use statements and group use statements.
// ❌ flagged — missing alias
use Doctrine\ORM\Mapping;
// ✅ correct
use Doctrine\ORM\Mapping as ORM;Ensures that when a trigger attribute is present on a method, all configured companion attributes are also present.
// ❌ flagged — #[Route] without #[IsGranted]
#[Route('/admin/users')]
public function list(): object { ... }
// ✅ correct
#[Route('/admin/users')]
#[IsGranted('ROLE_ADMIN')]
public function list(): object { ... }In controllers that use Symfony's HandleTrait, actions returning $this->handle(...) must declare their return type as object (or mixed). A more specific type causes a TypeError when the message bus returns an unexpected response such as an ErrorResponse.
// ❌ flagged — too specific, will TypeError on error responses
public function __invoke(Request $request): RecordsResponse
{
return $this->handle(new GetRecordsQuery());
}
// ✅ correct
public function __invoke(Request $request): object
{
return $this->handle(new GetRecordsQuery());
}Every public controller method carrying a #[Route] attribute must also carry a #[IsGranted] attribute — either on the method itself or on the class. Excludes abstract classes and any configured namespaces or class names.
// ❌ flagged
#[Route('/api/users')]
public function index(): object { ... }
// ✅ correct
#[Route('/api/users')]
#[IsGranted('ROLE_USER')]
public function index(): object { ... }These rules are only active when
Coala\DateProviderBundleis present in the project.
Disallows creating DateTime/DateTimeImmutable without an absolute date string argument, calling time-sensitive functions (time(), date(), etc.), or using static factory methods that produce the current time. Enforces the use of DateProviderInterface instead, which enables deterministic time in tests.
// ❌ flagged
$now = new DateTimeImmutable();
$ts = time();
// ✅ correct
$now = $this->dateProvider->now();This rule is only active when
Coala\MessengerBundleis present in the project.
In configured namespaces, if a class defines a setter method that corresponds to a configured handle-bus trait (e.g. setQueryBus()), it must use the matching trait instead of defining the setter manually.
This rule is only active when
Coala\TestingBundleis present in the project.
Test methods that call assertFailingValidation() must carry #[Group('invalid-data-test')]. This allows the test suite to run invalid-data tests in isolation.
// ❌ flagged
public function testInvalidEmail(): void
{
$this->assertFailingValidation(...);
}
// ✅ correct
#[Group('invalid-data-test')]
public function testInvalidEmail(): void
{
$this->assertFailingValidation(...);
}- Docker with Compose
After cloning the repository, run the install script to set up the vendor directory and extract PHPStan source files for IDE indexing:
bin/install.shThis is equivalent to running composer install — the PHPStan extraction is triggered automatically as a post-install hook and requires no additional IDE configuration.
bin/test.shRuns PHPStan and PHPUnit against all configured Docker services sequentially.
To target a specific PHP version:
bin/test.sh 8.3bin/lint.shRuns PHP CS Fixer and applies fixes in place. To check without modifying files (as CI does), run:
composer lint:checkbin/composer.sh <args>Runs Composer inside the default Docker container. Examples:
bin/composer.sh install
bin/composer.sh require --dev some/packageXdebug is included in the local Docker images and configured with start_with_request=trigger. To activate it, set the XDEBUG_TRIGGER environment variable:
XDEBUG_TRIGGER=1 bin/test.shOr configure your IDE to listen on port 9003 and set XDEBUG_TRIGGER=1 in the Docker run environment.
Please read CONTRIBUTING.md for details on our code of conduct and the process for submitting pull requests.
This project uses Conventional Commits for automated releases and changelog generation.
We use SemVer for versioning. For available versions, see the tags on this repository.
- Oliver - Oliver-Zieschang
See also the full list of contributors who participated in this project.
We're currently looking for contributions for the following:
- Bug fixes
- Translations
- etc...
For more information, please refer to our CONTRIBUTING.md guide.
This project is licensed under the MIT License - see the LICENSE.txt file for details.
This project currently uses no third-party libraries or copied code.