Download the PHP package amashukov/rector-php-rules without Composer
On this page you can find all versions of the php package amashukov/rector-php-rules. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download amashukov/rector-php-rules
More information about amashukov/rector-php-rules
Files in amashukov/rector-php-rules
Package rector-php-rules
Short Description Opinionated Rector rules for PHP — strict-assertion enforcement, no @phpstan-ignore, no env-checks in src, no superglobals, PSR Clock requirement, no direct DB / event-dispatch / assert() inside functional tests, and a comment-stripper that keeps only PHPStan tag blocks.
License MIT
Homepage https://github.com/AndreyMashukov/rector-php-rules
Informations about the package rector-php-rules
amashukov/rector-php-rules
Fifteen portable custom Rector rules for PHP 8.3+ — a static-analysis code-quality gate enforcing strict assertions, no @phpstan-ignore, no superglobals, no env-checks, no constructor fallbacks, no deferred TODO/FIXME markers, PSR Clock, clean functional tests, and comment-free YAML.
amashukov/rector-php-rules is a set of portable custom Rector rules for PHP 8.3+ that enforce a single, consistent quality bar across src/, bundles/, tests/, and your YAML config files: explicit failure modes in production code, deterministic and value-pinning assertions in tests, no environment branching, no superglobals, no @phpstan-ignore, no constructor fallbacks (?? new ...), no non-DI clock access, no deferred TODO/FIXME markers, and no narrative comments in PHP or YAML. Drop them into any project's rector.php and turn architectural conventions into an enforced, CI-gated coding standard. Fifteen rules in total.
Features
- Production-code guards — ban
assert(), superglobals +getenv(), runtime environment branching, and@phpstan-ignoresuppressions insrc/. - Strict-test enforcement — forbid type-only / existence-only / inline-array
assertContainsassertions, and conditional assertions insideif/matchbranches. - Functional-test purity — block direct DB mutations and direct event/message-bus dispatch in
tests/Functional/**, forcing tests through the public API. - PSR Clock requirement — ban
time()/new DateTimein production, mandating injectedPsr\Clock\ClockInterface. - No constructor fallbacks — flag the
$dep ?? new ClassName(...)pattern that hides a required dependency behind a default-construction trapdoor. - Comment hygiene — strip every comment outside interface method doc-blocks, with a PHPStan-tag carve-out.
- YAML comment hygiene — extends the same no-comments policy to
*.yaml/*.ymlfiles via a dedicated Rector rule that walks the configured paths, strips comments in place, and fails the run. - Detection-only by default for PHP — most PHP rules attach a
// RECTOR-BAN: …marker so a dry-run surfaces the violation in CI without silently rewriting domain semantics. The YAML rule strips and re-throws.
The full rule catalogue with BAD/GOOD examples is documented under Rule catalogue below.
Installation
Usage
Register the rules you want in your rector.php:
Most rules are detection-only: they attach a // RECTOR-BAN: … marker comment to the offending node so that rector process --dry-run surfaces the violation in CI and make rector blocks the commit, without auto-rewriting the code (which would silently destroy domain semantics). The marker also makes the dry-run output self-explanatory for the reviewer.
The rules apply path filtering by directory convention (/src/, /bundles/, /tests/, with skips for /migrations/, /.rector/, /var/, /vendor/, /bin/, /public/). If your layout differs you can additionally scope rules through RectorConfig::skip(...) in your rector.php.
Rule catalogue
1. NoCommentsOutsideInterfaceMethodDocBlockRector
Strips every comment outside interface method doc-blocks. Class-level prose, method-body inline comments, file headers, and class-level doc-blocks are removed. Only doc-blocks directly above methods declared inside an interface { … } block are preserved verbatim.
Why: code is self-documenting through naming, types, and tests. Prose comments rot — they drift away from the code they describe, become stale, and lie to the next reader. Genuine "why" lives in repository documentation (README, ADR), not next to the line. Interface method doc-blocks are the one exception because they form the public contract.
PHPStan-tag carve-out. Doc-blocks that contain @param / @return / @var / @template* / @phpstan-* / @psalm-* / @throws tags survive — the rule extracts only the tag lines and drops every prose line. Inline // @phpstan-ignore-next-line line comments survive for the same reason. This preserves type-narrowing annotations PHPStan depends on without leaving any prose behind.
2. NoPhpstanIgnoreRector
Forbids @phpstan-ignore, @phpstan-ignore-next-line, @psalm-suppress, and phpstan-baseline entries anywhere in PHP comments + doc-blocks.
Why: silencing a static-analyzer error is a debt instrument: the underlying type mismatch stays, the next reader can't tell whether it's a known false-positive or a real bug, and the suppression survives long after the actual issue is fixed. Fix the type, not the report. If the analyzer is genuinely wrong, narrow the type at the source (typed wrappers, instanceof guards, dedicated VOs) rather than at the suppression site.
3. NoSuperglobalAccessRector
Forbids every PHP superglobal access ($_ENV, $_SERVER, $_GET, $_POST, $_REQUEST, $_COOKIE, $_FILES, $_SESSION) and getenv() / putenv() calls anywhere under src/, bundles/, and tests/. Skips migrations/, .rector/, var/, vendor/, bin/, public/.
Why: runtime superglobal access bypasses the dependency-injection boundary. The class becomes untestable in isolation (the test has to mutate process-wide state via putenv and remember to restore it), config drift becomes invisible (you can't see which classes read which env vars from a wiring file), and request data flows through an untyped channel instead of the framework's typed Request object. Env values should land via the DI container; request data via the framework Request object; tests pull from the container.
4. NoEnvironmentCheckInSrcRector
Forbids runtime environment branching ('prod'/'dev'/'test'/'staging' literal comparisons, $_ENV['APP_ENV'] / getenv('APP_ENV') reads, $kernel->getEnvironment() calls) anywhere under src/**. Skips tests/, migrations/, .rector/.
Why: behaviour gating belongs at the DI / config / route-loader layer, not inside production classes. An inline if ($env === 'test') branch makes the production code do something different under test than under prod — which means the test suite isn't actually testing production behaviour. Env-scoped routes, env-scoped DI bundles, env-scoped services.yaml configurations are the right level to express "this only runs under test".
5. NoAssertCallInSrcRector
Forbids bare assert() / \assert() function calls anywhere under `src/.** Skipstests/,migrations/,.rector/`.
Why: assert() is silent under production zend.assertions=-1 — the line literally compiles to nothing. The narrowing PHPStan gets from \assert($x instanceof Foo) is identical to the narrowing it gets from if (!$x instanceof Foo) throw new \LogicException(...), but the second form actually fires at runtime when the invariant breaks. Explicit throw makes the failure mode visible in stack traces, Sentry alerts, and code review; assert() makes it invisible.
6. NoAssertInsideIfInFunctionalTestsRector
Forbids PHPUnit assertX() calls inside if/elseif/else/match branches in `tests/Functional/.** Skipstests/Functional/Traits/` (shared test infrastructure, not real tests). Detection-only — re-emits the node unchanged so a reviewer refactors the conditional assertion into per-state helpers.
Why: a functional test must drive the production code to ONE deterministic state and assert exactly that state. A conditional assertion (if (X) assertY()) means the test doesn't actually know what state the code reached — it just shrugs and passes either way. That defeats the test's defensive purpose: a real regression where the production code reached the wrong branch wouldn't fire any assertion at all.
7. NoArrayAssertContainsInTestsRector
Forbids assertContains($actual, [A, B, ...]) / self::assertContains(...) calls where the second argument is an inline array literal. Applies under tests/**; .rector/ rules and migrations/ skipped.
Why: the pattern is a smudged "either A or B" assertion that hides non-determinism. If the production code under test is correctly deterministic, the test should assert exactly one expected state via assertSame. If you can't pin one state, the test is racing the production code path or papering over a non-deterministic branch — fix the architecture (mock the right boundary, sequence the drive helpers, install a frozen clock), then assert the one true post-condition.
8. NoTypeOnlyAssertionsInTestsRector
Forbids PHPUnit type-only assertions — every assertIs* and assertIsNot* variant: assertIsArray, assertIsString, assertIsInt, assertIsBool, assertIsFloat, assertIsNumeric, assertIsObject, assertIsCallable, assertIsScalar, assertIsIterable, assertIsResource, assertIsClosedResource. Applies under tests/**.
Why: type-only asserts pass on every wrong-but-typed value. assertIsArray($result) is green for an empty array, for [null, null, null], for ['error' => 'oops'] — none of which is what production should return. A test that "shape is right" is not a test that the production code does the right thing. Pin the actual value via assertSame; the type check is then implicit in the equality comparison.
9. NoExistenceOnlyAssertionsInTestsRector
Forbids PHPUnit existence-only assertions — assertNotEmpty, assertNotNull, assertArrayHasKey, assertObjectHasProperty, assertObjectHasAttribute. Applies under tests/**.
Why: "value is present" without "value is X" passes on every wrong-but-present value. assertNotNull($entity->getCompletedAt()) is green for any DateTime — wrong year, wrong day, wrong timezone — as long as it's not null. Pin the actual value via assertSame; the existence-vs-not is implicit in equality comparison.
10. RequirePsrClockInterfaceRector
Bans time() / microtime() / new DateTime / new DateTimeImmutable in production code. Skips entity classes, migrations, DataFixtures, tests, and .rector/.
Why: non-DI clock access makes the test suite non-deterministic. A unit that internally calls time() can't be tested at a specific moment — the test either tolerates a wall-clock window (flaky) or freezes time globally (race condition between concurrent tests). Inject Psr\Clock\ClockInterface and use $this->clock->now(); in tests, bind a frozen clock VO via the container override. Stored ISO timestamps stay parseable via DateTimeImmutable::createFromFormat(DateTimeInterface::ATOM, $iso).
11. NoDirectDbMutationInFunctionalTestsRector
Forbids direct DB mutations (EntityManagerInterface::{flush,persist,remove,merge,refresh,detach,lock}, Doctrine\DBAL\Connection::{executeStatement,executeUpdate,insert,update,delete,prepare,executeQuery}, Doctrine\DBAL\Statement::{execute,executeStatement}) inside tests/Functional/**. Carve-out: tests/Functional/Repository/** (repository tests inherently exercise persistence).
Why: every functional test should reach state EXCLUSIVELY through the public HTTP API + cron + messenger transports — direct DB writes bypass the controller / event-subscriber / antifraud-guard invariants the test is supposed to defend. A test that mutates rows directly defends a state the real user can never produce in production. Real bugs (controller validation, event-subscriber side effects, repository UPSERT race) slip past such a test and surface only after deploy.
12. NoDirectDispatchInFunctionalTestsRector
Forbids direct EventDispatcher::dispatch / MessageBusInterface::dispatch calls inside `tests/Functional/`.**
Why: direct dispatch bypasses the production trigger (controller / external worker / dispatcher) that fires the event in production. The test ends up asserting the event-handling code in isolation while skipping the controller validation, header parsing, antifraud gates, and side-effects that ALSO fire on the real path. Drive via the API endpoint that wraps the publish, or mock the outbound adapter and let the real dispatcher emit the event end-to-end.
13. NoNullCoalesceNewFallbackRector
Forbids the $dep ?? new ClassName(...) constructor-fallback pattern.
Why: the pattern hides a required dependency behind a default-construction trapdoor — the caller silently runs the wrong wiring instead of failing at the boundary. Tests rely on this fallback to skip wiring; production crashes when the implicit default and the real one diverge. Make the parameter required, let the DI container or the test inject the real collaborator, and crash early if it is missing.
14. NoSilentFallbackRector
Forbids every silent-default shape for a missing value: $x ?? $default, $x ??= $default, isset(...) ? $x : $default, !isset(...) ? $default : $x, array_key_exists(...) ? $x : $default, and the short ternary $x ?: $default. The narrower NoNullCoalesceNewFallbackRector is left to handle ?? new ClassName(...) so the two rules don't double-mark.
Why: every silent fallback is a place where a misconfigured environment, a stale upstream payload, or an AI-generated "safe default" papers over a missing input instead of failing on boot. Validate at the boundary (a requireEnv() / requireConfigInt() helper), narrow the type at the parser/deserializer, or branch explicitly. Use destructuring defaults and function parameter defaults for declared defaults — those are explicit, the inline fallback shapes are not.
Sibling rules in the family carry the same policy across the stack: no-silent-fallback in eslint-plugin-mess-detector (TS/JS — ??, ??=, || with literal RHS); nosilentfallback in go-lint (Go cmp.Or with literal, post-read string/numeric fallback); no_silent_fallback in rust-lint (Rust .unwrap_or / .unwrap_or_else / .unwrap_or_default / .ok_or / .map_or).
Hydration boundary carve-out: Doctrine entities bypass constructors, so ?? new ArrayCollection() in property initialisers is sometimes structurally required. If your src/Entity/ lives in the standard layout, scope the rule out for that directory in your own rector.php:
14. NoTodoCommentRector
Forbids TODO / FIXME / XXX / HACK markers in PHP comments and doc-blocks outright.
Why: a deferred marker is work you decided against but left in the tree — and an owner (@alice) or a ticket (PROJ-123) does not redeem it, it only makes the rot look organized. Implement it now, or track it in an issue and link that from real documentation, but do not leave the stub in the code. Only a marker that opens a comment counts, so prose that merely mentions one (documentation about markers) is left alone.
15. YamlNoCommentsRector (YAML)
Forbids comments — whole-line or inline — in *.yaml / *.yml files at the configured paths. Strips them in place and fails the run.
Why: YAML config files (config/packages/*.yaml, services.yaml, Doctrine *.orm.yml, GitLab/GitHub CI files) collect narrative comments the same way PHP files do — and Rector cannot rewrite them through its PHP AST. This rule walks the configured paths recursively on the first PHP node Rector visits, removes every comment it finds, and throws a RuntimeException listing the original path:line:column findings. The next run is clean.
Configuration is explicit and required. The rule depends on two
interfaces — YamlNoCommentsCheckerInterface and
YamlCommentStripperInterface — so the Rector DI container needs to
know which concrete implementations to instantiate. Bind them with
registerService(concrete, alias):
Without the two registerService calls Rector's container throws
BindingResolutionException because it cannot instantiate an
interface directly.
The scanner is YAML-aware: hashes inside quoted scalars (note: "value with # hash") and inside non-whitespace tokens (docs: https://example.com/page#section) are not flagged.
Requirements
- PHP 8.3+
rector/rector^2.0nikic/php-parser^5.0symplify/rule-doc-generator-contracts^11.2 || ^12.0
Related packages
| Package | Layer |
|---|---|
| amashukov/ton-php | Umbrella TON SDK (Cell/BOC, wallet, toncenter) |
| amashukov/eth-php | Umbrella EVM SDK (Keccak, secp256k1, RLP, EIP-1559, ABI, RPC) |
| amashukov/blockchain-context-bundle | Symfony 7 bundle wiring the TON + EVM stacks |
| amashukov/http-client-php | PSR-18 cURL HTTP client |
Quality
- PHPStan level 9 across
src/. - php-cs-fixer with the
@PER-CSruleset. - GitHub Actions CI on every push.
- Each rule ships with a paired before/after fixture test.
License
MIT — see LICENSE.
All versions of rector-php-rules with dependencies
rector/rector Version ^2.0
nikic/php-parser Version ^5.0
symplify/rule-doc-generator-contracts Version ^11.2 || ^12.0