mike-bronner / clean-code
PHPCS ruleset and custom sniffs enforcing the clean-code standards defined at mikebronner.dev/clean-code.
Package info
github.com/mike-bronner/clean-code
Type:phpcodesniffer-standard
pkg:composer/mike-bronner/clean-code
Requires
- php: ^8.3
- dealerdirect/phpcodesniffer-composer-installer: ^1.0
- sirbrillig/phpcs-variable-analysis: ^3.0
- slevomat/coding-standard: ^8.15
- squizlabs/php_codesniffer: ^3.13
Requires (Dev)
- pestphp/pest: ^4.0 || ^5.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- 0.1.1
- 0.1.0
- dev-chore/229-dogfood-ruleset-against-sniff-sources
- dev-fix/376-check-every-preg-return-value
- dev-fix/380-guard-removestageddirectory-against-symlinks
- dev-fix/378-give-the-ordinalindex-diagnostic-a-ruleset-level-b
- dev-chore/375-retire-wall-clock-assertions-and-timing-helper
- dev-chore/354-replace-wall-clock-assertions-9-files
- dev-fix/366-componentmarkup-template-wrapper-preg-replace
- dev-chore/373-wire-duplicatedarraykey-against-test-source
- dev-chore/319-ternarysides-clamp-closer
- dev-chore/369-main-is-red-converttocollection-reviewed-site-cens
- dev-chore/320-migrate-global-function-call-detectors
- dev-feature/193-primary-constructor-no-mode-branching
- dev-feature/110-phpmddesign-numberofchildren
- dev-feature/28-collections-only-use-collection-methods
- dev-fix/334-componentmarkupsniff-componenttags-templatetags-preg-check
- dev-fix/362-php-8-5-tokenizer-compat-gap
- dev-feature/157-flag-chained-assignments-and-assignment-in-cond
- dev-chore/363-wire-remaining-tests-through-violationtuples
- dev-fix/359-typediscriminatordispatchsniff-switchbranches-walk-do
- dev-feature/73-classes-introspection-type-casting
- dev-feature/249-sniff-non-invokable-special-action-route-controllers
- dev-feature/149-sniff-flag-internet-traversing-primitives
- dev-feature/40-constructors-no-logic-in-constructors
- dev-feature/324-sniff-flag-type-discriminator-dispatch-chai
- dev-feature/27-classes-class-naming-suffix-by-folder
- dev-feature/59-operators-manipulative
- dev-fix/356-main-is-red-convert-to-collection-census
- dev-feature/159-sniff-flag-section-labelling-comments
- dev-fix/343-per-stream-cache-key-collision
- dev-feature/148-sniff-flag-external-concerns-in-tests-unit-fil
- dev-feature/132-sniff-flag-interfaces-declaring-too-many-methods-p
- dev-feature/131-sniff-flag-throw-only-method-overrides
- dev-feature/150-sniff-flag-http-fakes-in-integration-tests
- dev-feature/126-sniff-flag-dedicated-repository-classes-by-name-pa
- dev-chore/321-replace-wall-clock-ratio-assertions
- dev-feature/248-sniff-flag-non-resource-route-verb-calls
- dev-feature/192-sniff-flag-accessor-mutator-query-scope-methods
- dev-feature/190-junk-drawer-namespace-sniff
- dev-feature/174-sniff-flag-closure-route-actions
- dev-feature/161-sniff-action-class-shape-single-public-entry
- dev-feature/143-sniff-codestyle-noformatterdirectives
- dev-feature/138-sniff-flag-todo-fixme-hack-debt-marker-comments
- dev-feature/146-sniff-flag-mocking-of-first-party-classes
- dev-feature/89-phpmd-codesize-npathcomplexity
- dev-feature/75-models-organization-member-ordering
- dev-feature/112-phpmd-design-depthofinheritance
- dev-chore/233-extract-a-shared-global-function-call-detector
- dev-chore/32-pattern-model-view-controller-mvc
- dev-chore/286-review-convert-to-collection-warnings
- dev-feature/206-unusedprivateelements-construct-coverage
- dev-fix/317-logicalgroupingssniff-indentofline-reindent-line-cache
- dev-feature/172-sniff-flag-verb-prefixed-action-methods-that-de
- dev-chore/60-testing-test-suites
- dev-fix/318-componentmarkupsniff-iscomponentview-preg-check
- dev-chore/316-token-kind-classification-completeness-guard
- dev-fix/314-foreachclauseas-matches-header-own-as
- dev-chore/308-verify-the-ci-workflow_dispatch-trigger
- dev-chore/213-strengthen-operator-spacing-fixtures
- dev-chore/199-add-inheritdoc-fixture-case-to-method-type-hints
- dev-fix/120-unusedformalparameter-inheritance-and-callable-gaps
- dev-chore/6-pattern-repository
- dev-chore/5-pattern-solid
- dev-feature/44-models-naming-conventions
- dev-chore/299-extract-shared-declaration-detector
- dev-feature/41-indentation-logical-groupings
- dev-feature/25-strings-interpolation-quoting-heredocs
- dev-feature/38-indentation-multi-line-statements
- dev-feature/120-phpmd-unusedcode-unusedformalparameter
- dev-feature/71-methods-no-null-arguments
- dev-feature/64-operators-passive
- dev-feature/69-methods-declared-parameters
- dev-feature/55-properties-are-required
- dev-chore/46-livewire-components
- dev-chore/42-models-relationship-properties
- dev-feature/36-indentation-methods-max-2-nesting-levels
- dev-chore/201-document-the-existing-sniff-standard-test-c
- dev-feature/62-operators-active
- dev-feature/14-conditionals-no-else-or-elseif
- dev-feature/20-conditionals-ternary-conditionals
- dev-chore/79-phpmd-cleancode-ifstatementassignment
- dev-chore/65-routes-conventions-do--do-not
- dev-chore/51-code-style-linters-config--no-auto-formatter
- dev-chore/11-clear-code-group-code-by-concepts
- dev-chore/26-debt-mental-debt
- dev-chore/24-debt-technical-debt
- dev-chore/1-boy-scout-rule
- dev-chore/8-clear-code-one-idea-per-statement
- dev-chore/13-clear-code-encapsulate-each-concept-in-a-method
- dev-chore/15-clear-code-encapsulate-related-methods-in-a-class
- dev-chore/16-clear-code-encapsulate-related-classes-in-a-domain
- dev-chore/31-dont-optimize-early
- dev-feature/29-no-dead-code
- dev-chore/39-models-structure-attributesqueries-traits
- dev-feature/56-operators-evaluative
- dev-feature/70-methods-type-hints
- dev-chore/61-methods-naming
- dev-fix/292-arrayaccessorssniff-staggered-staircase-nested-read-quadratic
- dev-chore/282-review-duplicate-block-warnings
- dev-chore/295-shipped-installed-package-smoke-test
- dev-chore/293-ci-checks-silently-fail-to-trigger
- dev-feature/169-sniff-manual-model-resolution
- dev-chore/50-controllers-route-model-binding
- dev-feature/108-phpmd-naming-longvariable
- dev-feature/184-sniff-named-constructors-must-delegate-to-th
- dev-feature/128-sniff-flag-source-class-files-with-no-correspondi
- dev-feature/114-phpmd-design-couplingbetweenobjects
- dev-feature/106-phpmd-naming-shortvariable
- dev-feature/90-phpmd-controversial-superglobals
- dev-feature/181-sniff-flag-adjacent-if-statements-with-identical
- dev-feature/88-phpmd-codesize-cyclomaticcomplexity
- dev-feature/134-duplicate-method-bodies
- dev-feature/141-sniff-controllers-nocustomactions
- dev-feature/163-sniff-mapping-array-candidate
- dev-feature/165-sniff-flag-native-array-manipulation-functi
- dev-chore/239-replace-per-token-enclosing-bracket-rescan
- dev-chore/66-routes-types-api-view
- dev-feature/77-phpmdcleancode-elseexpression
- dev-feature/84-phpmd-cleancode-missingimport
- dev-feature/93-phpmd-codesize-excessiveclasslength
- dev-feature/98-phpmd-codesize-toomanyfields
- dev-feature/99-phpmd-design-countinloopexpression
- dev-feature/101-phpmd-naming-longclassname
- dev-feature/105-phpmd-design-exitexpression
- dev-feature/109-phpmd-design-gotostatement
- dev-feature/113-phpmd-naming-constructorwithnameasenclosingclass
- dev-feature/81-phpmd-cleancode-duplicatedarraykey
- dev-feature/82-phpmd-cleancode-errorcontroloperator
- dev-feature/83-phpmd-codesize-toomanypublicmethods
- dev-feature/87-phpmd-codesize-excessiveclasscomplexity
- dev-feature/91-phpmd-codesize-excessivemethodlength
- dev-feature/95-phpmd-codesize-excessiveparameterlist
- dev-feature/96-excessive-public-count
- dev-feature/103-phpmdnaming-shortclassname
- dev-feature/116-phpmd-naming-booleangetmethodname
- dev-feature/129-sniff-flag-procedural-code-in-source-files
- dev-feature/136-sniff-flag-magic-numbers-named-constants
- dev-feature/145-sniff-flag-reflection-based-access-in-tests
- dev-feature/111-phpmd-naming-shortmethodname
- dev-chore/263-byte-identical-docblock-overclaim
- dev-feature/80-phpmd-codesize-toomanymethods
- dev-feature/153-sniff-flag-non-empty-protected-with-in-models
- dev-chore/72-dependency-injection
- dev-chore/58-testing-databases-sqlite-caveats
- dev-chore/54-testing-guidelines
- dev-feature/76-phpmd-cleancode-booleanargumentflag
- dev-chore/12-conditionals-avoid-conditionals
- dev-feature/118-phpmd-unusedcode-unusedlocalvariable
- dev-chore/10-classes-contracts-interfaces
- dev-feature/67-use-statements-sort-alphabetically
- dev-chore/195-harden-psr-opening-tag-regression-coverage
- dev-chore/57-testing-development-process-tdd
- dev-chore/18-naming-semantic-naming-principles
- dev-feature/86-phpmd-design-developmentcodefragment
- dev-chore/48-controllers-no-business-logic
- dev-chore/52-policies-secure-front--and-back-ends
- dev-feature/85-phpmd-cleancode-undefinedvariable
- dev-chore/74-models-eager-loading
- dev-feature/107-phpmd-design-evalexpression
- dev-test/pest-migration
- dev-chore/217-remove-redundant-psr1-sniff-refs
- dev-chore/155-readme-standards-preamble-tier-taxonomy
- dev-chore/37-models-persistence-methods-repository-pattern
- dev-feature/33-arrays-array-accessors-data-get
- dev-feature/53-code-style-multiline-strings-heredoc
- dev-feature/35-arrays-operator-spacing-line-break-rules
- dev-feature/19-classes-no-statics
- dev-feature/68-use-statements-no-unused-entries
- dev-feature/47-constructors-property-promotion
- dev-feature/43-blank-lines
- dev-feature/9-conditionals-no-inline-if-statements
- dev-feature/3-line-length
- dev-feature/45-type-hints-and-return-types
- dev-feature/49-code-style-industry-standards-psr1212
- dev-feature/63-exceptions
- dev-feature/17-conditionals-one-condition-per-line
- dev-feature/7-clear-code-one-thought-per-line
- dev-feature/22-naming-casing-conventions
- dev-chore/21-conditionals-combine-where-possible
- dev-chore/34-constructors-primary--named-constructors
- dev-chore/30-arrays-convert-to-collection
- dev-feature/2-foundation-composer-package-scaffold-phpcs-slevoma
This package is auto-updated.
Last update: 2026-09-24 17:28:16 UTC
README
PHPCS linter rules for all coding standards defined in https://mikebronner.dev/clean-code.
Installation
Composer 2.2 and later will not run a plugin the consuming project has not allowed, and the codesniffer installer is a plugin. Allow it first:
composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
composer require --dev mike-bronner/clean-code
Or add it to composer.json by hand:
{
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}
Without that opt-in the installer never runs, PHP_CodeSniffer's
installed_paths is never written, and the standard does not register —
phpcs then reports the standard as not installed, with nothing pointing at the
plugin as the cause. It is the most common reason an install appears to do
nothing.
With the plugin allowed, vendor/bin/phpcs -i lists one standard from this
package, CleanCode. It is the whole rule set: the custom sniffs, the PSR
and Slevomat wiring, the PHP version pin and Blade support.
Usage
Reference the standard by name from your project's own phpcs.xml (or
phpcs.xml.dist):
<rule ref="CleanCode"/>
Or run it directly:
vendor/bin/phpcs --standard=CleanCode src/
The name works from anywhere in your project and survives a custom Composer
vendor-dir, which a path reference does not. It is also the prefix on every
code the custom sniffs report, so CleanCode.Naming.ShortVariable in a report
and CleanCode in your config are the same word.
Contributing
See CONTRIBUTING.md for the package layout, how a new
sniff or standard plugs into CleanCode/ruleset.xml, the test-fixture
contract, and how to add its Pest test.
Standards
Each standard is documented under docs/standards/. Every
entry below carries the tier assigned in that document's Enforceability
section, and summarizes how the standard is enforced.
- Tier 1 and Tier 2 standards are enforced automatically, by rules wired
into the
CleanCodestandard: a bundled standard, an existing PHPCS or Slevomat rule (configured where needed), a custom sniff, or a combination of these. - Tier 3 standards are the ones a token-based sniff cannot verify. They are enforced by code review and developer discipline rather than a PHPCS rule. A few carry a rule over one narrow slice that is token-visible — the tier still describes the standard's core, not that slice.
Where a rule covers only part of a standard, that standard's document records what stays with code review.
- Code Style: Linters (config & no auto-formatter) — Tier 3, custom sniff
CleanCode.CodeStyle.NoFormatterDirectivesover one narrow slice: flags every comment carrying an auto-formatter directive (@formatter:off,@formatter:on,prettier-ignoreby default, replaceable through adirectivesproperty), detection-only; editor configuration, formatter config files, and a formatter run that left no directive behind stay with code review (#51 + #143) - Debt: Technical Debt — Tier 3, not statically enforceable
- Code Style: Linters (config & no auto-formatter) — Tier 3, not statically enforceable
- Debt: Technical Debt — Tier 3, core not statically enforceable, with one narrow slice enforced: self-declared debt markers warn, via
Generic.Commenting.TodoandGeneric.Commenting.Fixme(the latter lowered to a warning) plus custom sniffCleanCode.Commenting.DebtMarkersfor theHACKandXXXkeywords core ships nothing for, detection-only; recognizing undeclared debt, judging whether a construct makes future change costlier, and paying debt down promptly all stay with code review (#24 + #138) - Boy Scout Rule — Tier 3, not statically enforceable
- Don't Optimize Early — Tier 3, not statically enforceable
- Arrays: Array Accessors (
data_get) — Tier 2, custom sniffCleanCode.Arrays.ArrayAccessors: flags direct element ($array['key']) and property ($object->property) reads once per accessor chain, leaving write-side access, existence checks, array literals,$this-rooted access, and method calls alone, detection-only (#33) - Arrays: Convert To Collection — Tier 2, custom sniff
CleanCode.Arrays.ConvertToCollection: warns on calls to a configurable list of native array functions (array_map,array_filter,array_reduceby default), naming the Collection equivalent, and leaving method calls, static calls, declarations, and namespaced same-named functions alone, detection-only; whether a given manipulation reads better as a pipeline, andforeachaccumulation, stay with code review (#30 + #165) - Arrays: Operator Spacing & Line Breaks — Tier 1, configured
Squiz.WhiteSpace.OperatorSpacing+Squiz.Strings.ConcatenationSpacing(exactly one space each side, auto-fixable) plus custom sniffsCleanCode.Operators.NotOperatorSpacing(auto-fixable) andCleanCode.Operators.OperatorLineBreak(reporting only) (#35) - Blank Lines — Tier 1, custom sniff:
CleanCode.WhiteSpace.BlankLines, auto-fixable (#43) - Classes: Class Naming — Tier 2, custom sniff
CleanCode.Naming.RedundantNamespaceSuffix, error severity, detection-only: flags a class, interface, trait, or enum under anApp\namespace whose name ends in a suffix one of its own namespace segments already says (App\Services\BillingService,App\Http\Controllers\UserController), matching every ancestor segment and its singular forms at a PascalCase word boundary, case-insensitively; no per-folder exemption, so Laravel's generatedController/Request/Formnames are flagged too and the fuller name belongs in ause … asalias at the call site. Renaming a type rewrites every reference to it, so there is no fixer (#27) - Classes: Contracts (Interfaces) — Tier 3, no rule: whether a class needs a contract depends on how it is instantiated and consumed across the codebase, which a single-file token stream cannot see, so the whole standard stays with code review (#10)
- Classes: Introspection / Type Casting — Tier 2, custom sniff
CleanCode.Classes.DisallowTypeIntrospection: flagsinstanceofandget_class()/get_debug_type()/gettype()/is_a()/is_subclass_of()where they decide a branch (if/elseif/while/switch/match/ternary), detection-only (#73) - Classes: No Statics — Tier 2, custom sniff
CleanCode.Classes.DisallowStaticMembers: flags static method/property declarations in any class/interface/trait/enum, detection-only. A member a resolvable ancestor declares static and non-private is skipped, because PHP refuses to load a class that drops the keyword — a Laravel facade'sgetFacadeAccessor()is the common case (#19) - Clear Code: One Thought Per Line — Tier 2, custom sniff
CleanCode.ClearCode.OneThoughtPerLine: one access operator per chain per line, auto-fixable. An operator still held by an unclosed(,[, or short-array opener is exempt, so argument lists, array literals and statement conditions stay on one line; the exemption stops at a brace, so a closure body's chains are still counted (#7) - Code Style: Industry Standards (PSR1/2/12) — Tier 1, bundled standard: PHPCS
PSR12(includes PSR1; supersedes PSR2) wired into the CleanCode standard (#49) - Code Style: Multiline Strings (HEREDOC) — Tier 2, custom sniff
CleanCode.Strings.MultilineStrings, which owns length only: flags multi-line quoted strings (auto-fixed to a HEREDOC, whatever the source was quoted with) and quoted-string concatenation whose value carries more thanmaximumLineslines (default 3, detection-only). Lines of text, never lines of source — a sentence wrapped across four source lines is one line of text, and a HEREDOC cannot express it without either breaking the 120-character limit or putting real newlines into the value, so counting the source made the rule report what no fix could satisfy. Source layout is already governed by the 100-character limit and the leading-operator rule. Embedded languages belong toCleanCode.Strings.RequireHeredocForStructuredTextat any length, so the two hold disjoint slices (#53) - Collections: Only Use Collection Methods — Tier 2, custom sniff
CleanCode.Collections.OnlyUseCollectionMethods: flags generic PHP array/string functions applied to a Collection, auto-fixable for the unambiguous 1:1 swaps (count(),array_sum()) on a provably-typed receiver (#28) - Conditionals: Avoid Conditionals — Tier 2, custom sniff
CleanCode.Conditionals.AvoidConditionals: one warning per if/elseif/ternary/switch, detection only, plusSlevomatCodingStandard.ControlStructures.UselessIfConditionWithReturnlowered to warning for the auto-fixable boolean-returnif; whether a branch was avoidable stays with code review (#12) - Conditionals: Combine Where Possible — Tier 2, custom sniff
CleanCode.Conditionals.CombinableConditions: one warning per participating branch, for adjacent branches of one if/elseif chain with identical bodies and for adjacent separate plainifstatements whose identical bodies unconditionally exit; detection only, since whether the combined condition reads better stays with code review (#181) - Conditionals: Mapping Arrays — Tier 2, custom sniff
CleanCode.Conditionals.MappingArrayCandidate: one warning per if/elseif chain whose conditions all compare the same variable against scalar literals and whose branches only produce a value, detection only, with a configurableminimumBranchesthreshold; whether a mapping array reads better stays with code review (#163) - Conditionals: No else or elseif — Tier 1, custom sniff
CleanCode.Conditionals.DisallowElse: reports everyelse(.Found) and everyelseif, including the two-wordelse if(.ElseIfFound), one violation per keyword; auto-fixable where every preceding branch already ends in a jump statement and the layout is canonical, reported and left alone everywhere else (#14) - Conditionals: No Inline If-Statements — Tier 1, existing sniff: Generic.ControlStructures.InlineControlStructure, auto-fixable (#9)
- Conditionals: One Condition Per Line — Tier 2, custom sniff: auto-fixable condition-layout enforcement (#17)
- Conditionals: Ternary Conditionals — Tier 1, Slevomat
ControlStructures.RequireTernaryOperatorfor the if/else that only assigns or returns (auto-fixable) plus custom sniffCleanCode.Conditionals.DisallowNestedTernary, which reports every nested ternary at the inner operator, detection only — unfolding one needs a variable or method name, which no fixer can invent (#20) - Constructors: No Logic in Constructors — Tier 2, custom sniff
CleanCode.Constructors.NoLogic: flags every non-assignment statement in a__construct()body, detection-only (#40) - Constructors: Primary + Named Constructors — Tier 2, custom sniffs:
CleanCode.Constructors.PrimaryConstructorDelegationwarns on a named constructor — astaticmethod returningself,static, or the declaring class — whose body neither instantiates that class nor hands off to another of its static methods, detection-only (#184);CleanCode.Constructors.DisallowCombinedConstructorwarns when a__constructbody branches on a construction-mode signal — a boolean mode flag (ModeFlag), a parameter's runtime type (TypeSwitch), or the argument list the caller supplied (ArgumentCount) — skipping guard clauses and coalesce defaults, detection-only (#193) - Constructors: Property Promotion — Tier 1, Slevomat
Classes.RequireConstructorPropertyPromotion, auto-fixable (#47) - Controllers: No Business Logic — Tier 3, code review only: "business logic" is a semantic judgement and the prescribed extraction to Form Request / Response classes is cross-file, so no single-file sniff can decide it; the one token-visible slice (non-RESTful public method names) is tracked as follow-up sniff issue #141 (#48)
- Controllers: Route Model Binding — Tier 2, custom sniff
CleanCode.Controllers.ManualModelResolution: warns when a public*Controllermethod resolves a model by hand (Model::find($id)/findOrFail($id)) from one of its own parameters instead of type-hinting the model and letting route-model binding resolve it, detection-only (#50, superseding #169) - Controllers: No Business Logic — Tier 2, custom sniff
CleanCode.Controllers.NoCustomActions: warns on a public method declared in a*Controllerclass that is not one of the seven Laravel resource actions,__construct,__invoke, or a name in the configurableallowedMethodsallowlist, detection-only (#141); the rest of the standard stays with code review — "business logic" is a semantic judgement and the prescribed extraction to Form Request / Response classes is cross-file, so no single-file sniff can decide it (#48) - Dependency Injection — Tier 2, custom sniff
CleanCode.Classes.DisallowConstructorInstantiation: warns once pernewinside the body of a__constructa class-like scope holds, skipping whatever athrowraises and any nested declaration, detection-only; whether a collaborator should be injected is an architectural judgement no single-file token scan can make, so that half stays with code review (#72 + #176) - Exceptions — Tier 2, enforced via Slevomat rules:
\Throwable-only catches + non-capturing catch, both auto-fixable (#63) - Indentation: Logical Groupings — Tier 2, custom sniff
CleanCode.Indentation.LogicalGroupings: indents parenthesized condition groups one level deeper per nesting level, auto-fixable (#41) - Indentation: Methods (max 2 nesting levels) — Tier 2, custom sniff
CleanCode.Metrics.MethodNestingLevel: flags control structures nested more than 2 levels deep, not auto-fixable (#36) - Indentation: Multi-Line Statements — Tier 1, custom sniff
CleanCode.WhiteSpace.MultiLineStatementIndent: every line after the first of a multi-line statement is indented exactly one level in from the line its construct opens on, auto-fixable (#38) - Line Length — Tier 1, configured rule:
Generic.Files.LineLengthwarns above 100 characters, errors above 120 (#3) - Methods: Declared Parameters — Tier 2, custom sniff
CleanCode.Methods.DeclaredParameters: flagsfunc_get_args()/func_get_arg()/func_num_args()calls, exempting magic methods, detection-only (#69) - Livewire: Components — Tier 2, custom sniff
CleanCode.Livewire.ComponentMarkup: reads Blade views (registered via theblade.phpextension) and reports framework attributes on a component's root element, a<livewire:…>tag in a loop withoutwire:key, and adjacent components missing a key-matched<template>wrapper; heuristic and detection-only, so root-element count andwire:keyuniqueness stay with code review (#46) - Methods: No Null Arguments — Tier 2, custom sniff
CleanCode.Methods.NoNullArguments: flags a literalnullpassed positionally into an optional parameter, auto-fixed to a named argument wherever the file proves which declaration the call reaches (#71) - Methods: Type Hints — Tier 1, Slevomat
TypeHints.ParameterTypeHint+TypeHints.ReturnTypeHint, configured in the CleanCode standard, plus the customCleanCode.TypeHints.InferredReturnType, which carries the fixer Slevomat cannot: Slevomat writes a return type only from a@returnannotation, while this one writes it wherever it is provable from the source — a type a resolvable ancestor already declares (read by reflection), a body whose returns are all literals, one returning$this, or one returning a typed parameter. It stays silent on everything else, because a guessed return type is a runtimeTypeErrorrather than a lint finding, and cedes the magic-method,@return-annotated and returns-nothing cases to Slevomat outright (#70) - Models: Eager Loading — Tier 2, two custom sniffs, both detection-only:
CleanCode.Models.RequireLazyLoadingPreventionwarns when the application service provider never callsModel::preventLazyLoading()(orModel::shouldBeStrict()), leaving Laravel's runtime lazy-loading safety check switched off (#154);CleanCode.Models.DisallowAlwaysOnEagerLoadingwarns on a populated$withproperty in a class extending a model-shaped parent, the always-on eager loading the standard's first rule prohibits (#153) - Models: Organization (member ordering) — Tier 2, custom sniff
CleanCode.Models.MemberOrdering: in a class extending a model-shaped parent — named or anonymous, each ordered against itself — errors on a trait use, property, relationship method, getter/setter, or other method that is out of alphabetical order, on a property whose visibility group precedes the previous property's, and on ausestatement declaring several traits; detection-only, because reordering members would move doc blocks and comments bound to a declaration by nothing but adjacency (#75) - Models: Naming Conventions — Tier 2, custom sniff
CleanCode.Naming.ModelNamingConventions: yes/no prefixes on boolean model properties/methods,find/getprefixes keyed off the return type, and legacygetXAttribute()accessors, detection-only (#44) - Models: Persistence Methods (Repository Pattern) — Tier 2, custom sniff
CleanCode.Models.DisallowExternalPersistenceCalls: warns on generic CRUD calls (create/delete/save/update) on any receiver other than$this, detection-only (#37) - Models: Relationship Properties — Tier 2, custom sniff
CleanCode.Models.DisallowChainedPropertyFetch: errors on chained property-fetch access, not auto-fixable (#42) - Naming: Casing Conventions — Tier 1, existing sniffs: camelCase variables/properties/methods, PascalCase classes (#22)
- No Dead Code — Tier 2, Squiz + Slevomat rules for commented-out code, unused parameters, and unused imports, plus a custom unused-private-elements sniff (#29)
- Operators: Active — Tier 1, four cooperating sniffs, each owning a disjoint slice of the operator list so no violation is reported twice:
Squiz.WhiteSpace.OperatorSpacingfor the assignment operators,Squiz.Strings.ConcatenationSpacingfor., andCleanCode.Operators.NotOperatorSpacingfor!(all three wired in for #35), plus the customCleanCode.Operators.BooleanOperatorSpacingfor the logical connectives&&,||,and,or,xor— the only operators in the list nothing else already covered; all auto-fixable (#62) - Operators: Evaluative — Tier 1, custom sniff: newline-around-evaluative-operator detection, auto-fixable (#56)
- Operators: Manipulative — Tier 2, three cooperating sniffs, each owning a disjoint slice of the operator list so no wrap is reported twice:
CleanCode.Operators.OperatorLineBreakfor.,&&and||andCleanCode.Conditionals.OneConditionPerLineinside a control-structure condition (both already wired in, for #35 and #17), plus the customCleanCode.Operators.ManipulationOperatorPlacementfor the math (+ - * / % **) and bitwise (& | ^ << >>) groups — the only operators in the list nothing else already covered. A manipulation operator must lead a wrapped line, not trail it; inline usage, unary/reference forms and catch-clause type unions are untouched, and the new sniff auto-fixes its own half (#59) - Operators: Passive — Tier 1, custom sniff
CleanCode.WhiteSpace.PassiveOperatorSpacing(identity, negation, error control, execution) plus existing sniffs for increment/decrement,->, and[], all auto-fixable (#64) - Pattern: Don't Repeat Yourself (DRY) — Tier 2, custom sniff: repeated-block detection (#134)
- Naming: Semantic naming principles — Tier 3, one custom sniff over a mostly review-owned standard: whether a name reveals its intent, misleads, or keeps one word per concept is a judgement about meaning that a single file's token stream cannot make, so the semantic core stays with code review (#18); the one token-catchable slice is carried by
CleanCode.Naming.DisallowMagicNumbers, which warns on a bare numeric literal used outside a declaration site, where a named constant would make the concept searchable — detection-only, with a configurableignoredNumberslist defaulting to0, 1, -1(#136) - Pattern: Don't Repeat Yourself (DRY) — Tier 2, custom sniff
CleanCode.Pattern.AvoidDuplicateCodeBlocks: warns on every run of code lines in a file that repeats another run — each block reported at its own first line, naming every other block of that shape — comparing token types rather than token content so renamed variables and changed literals still match, above a configurable line threshold (default 5), detection-only; whether a flagged duplication has earned an abstraction, and duplicated knowledge that is not duplicated text, stay with code review (#134) - Pattern: Model-View-Controller (MVC) — Tier 3, code review: MVC is a recommended default, not a mandatory architecture, so no rule requires a feature to have a Model, View, or Controller class, and accepted alternatives — Livewire full-page and single-file components — are not violations; the one token-visible slice, RESTful public-method naming, applies only to a class already suffixed
Controllerand ships asCleanCode.Controllers.NoCustomActionsunder Controllers: No Business Logic (#167, closed as a duplicate of #141) - Pattern: Repository — Tier 3 for the semantic half only: whether persistence logic actually lives in the model or its attribute/query traits is a judgement about intent that no token-based sniff can verify, so that half stays with code review (#6); the complementary "no dedicated class outside the model implements the repository pattern" half is token-visible and is Tier 2, carried by the custom sniff
CleanCode.Pattern.DisallowRepositoryClasses: it warns on a class, interface, trait, or enum whose declared name ends inRepository/RepositoryInterfaceor whose namespace carries aRepositoriessegment (both case-insensitive), reading declarations only —extends,implements, a traituse, an import andneware consumption sites and are never flagged — detection-only, a naming/namespace heuristic rather than a verification of the semantic standard (#126) - Policies: Secure Front- and Back-Ends — Tier 3, no rule: front-end restrictions live outside the linted token stream, and a missing back-end check is an absence a single-file sniff cannot distinguish from a guard placed in another file, so the whole standard stays with code review (#52)
- Strings: Interpolation, quoting, HereDocs — Tier 2, custom sniffs
CleanCode.Strings.*: concat→interpolation (auto-fixable for any chain whose operands all have an interpolated form, including member, index and call expressions; a grouping parenthesis or a binary-string prefix stays detection-only), HTML attribute double-quoting (auto-fixable), embedded-language-should-be-HereDoc at any length, covering HTML, XML, SQL, JSON, YAML, INI/config and markdown, each behind an anchored signal (detection-only), nested-quote escaping (auto-fixable), and NOWDOC→HEREDOC (auto-fixable — the two are the same construct and differ only in the quotes around the opening identifier, so the package keeps one; a backslash doubles and a$is escaped, which covers{$and${alike, while a"needs nothing) (#25) - Routes: Conventions (Do / Do Not) — Tier 2, one enforceable bullet at a time. Shipped: custom sniff
CleanCode.Routes.DisallowNonResourceRoutes, which warns on a route registered with an HTTP-verb call (Route::get,post,put,patch,delete,options,any,match) instead ofRoute::resource()/Route::apiResource(), anchored at the verb token, confined to configurable route-path globs (routeFilePatterns), detection-only and warning-severity because the standard permits a rare special-action exception a sniff cannot recognise (#248). Still queued as focused follow-ups: closure route actions at error severity (#174) and non-invokable special-action controllers (#249). Whether the target controller is really RESTful, one-model-per-route, model-based naming, and model-unrelated routes need project-wide symbol resolution and stay with code review (#65) - Routes: Conventions (Do / Do Not) — Tier 2, three focused sniffs, one per enforceable bullet: closure route actions at error severity, custom sniff
CleanCode.Routes.DisallowClosureRoutes(#174), plus heuristic warnings for non-resource verb routes (#248) and non-invokable special-action controllers (#249); one-model-per-route, model-based naming, and model-unrelated routes need project-wide symbol resolution and stay with code review (#65) - Testing: Development Process (TDD) — Tier 3, not statically enforceable: test-first order and the Red/Green/Refactor cycle are facts about the process, not the tokens, so the standard stays with code review (#57); two token-visible slices are queued as focused follow-ups (#128, #129)
- Routes: Types (API / View) — Tier 2, custom sniff
CleanCode.Routes.ApiControllerNamespace: requires a controller'sAPInamespace segment and itsAPIpath segment to agree, in both directions, detection-only. A proxy for the written standard — the route-registration wording it stands in for, and the one-controller-per-model clause, stay with code review (#66) - Testing: Databases (SQLite caveats) — Tier 3, no rule: the standard forbids a pairing — SQLite as the test driver together with JSON columns, exact decimal maths, table-altering migrations, or raw date queries — and the driver half is declared in
phpunit.xml/.env.testing/CI config that PHPCS never tokenizes, so no single-file sniff can join the two halves and no partial-enforcement slice survives (#58) - Testing: Development Process (TDD) — Tier 3 for the process: test-first order and the Red/Green/Refactor cycle are facts about the process, not the tokens, so that half stays with code review (#57); the classes-only slice is Tier 2, custom sniff
CleanCode.Files.NoProceduralCode, scoped tosrc/+app/: any top-level statement outside a single class/interface/trait/enum declaration is an error, detection-only, and stricter thanPSR1.Files.SideEffects(#129); the test-existence slice is Tier 2 as well, custom sniffCleanCode.Testing.RequireTestFile, which warns when a concrete class under a configured source directory has no companion test file — oneglob()call per class against a configurable source-to-test mapping (sourceDirectories,testDirectory,testPathTemplate,excludePatterns), detection-only, exempting interfaces, traits, enums, anonymous and abstract classes, and enforcing test existence only, never test-first order or test quality (#128) - Testing: Guidelines — Tier 3 core, two partial rules: the standard turns on process and cross-file judgements (where testing started, whether every scenario has success and failure coverage, whether a mocked interface is one the team controls) that a single file's token stream does not carry, so the semantic core stays with code review (#54); the "only test public methods" slice is enforced by the custom sniff
CleanCode.Testing.NoReflectionAccess, which warns on Reflection reaching a named non-public member inside a test file (new ReflectionMethod/ReflectionProperty,setAccessible,getMethod,getProperty,invoke,invokeArgs), detection-only and confined to configurable test-path globs (#145); the "do not mock classes you control" slice is enforced by the custom sniffCleanCode.Testing.NoFirstPartyMocks, which warns on a mock-creation call (createMock,createPartialMock,getMockBuilder,Mockery::mock/spy, Laravel'smock/partialMock/spy) whose class argument resolves into a configured first-party namespace root, also detection-only and test-path confined (#146) - Testing: Guidelines — Tier 3 core, one partial rule: the standard turns on process and cross-file judgements (where testing started, whether every scenario has success and failure coverage, whether a mocked interface is one the team controls) that a single file's token stream does not carry, so the semantic core stays with code review (#54); the "only test public methods" slice is enforced by the custom sniff
CleanCode.Testing.NoReflectionAccess, which warns on Reflection reaching a named non-public member inside a test file (new ReflectionMethod/ReflectionProperty,setAccessible,getMethod,getProperty,invoke,invokeArgs), detection-only and confined to configurable test-path globs (#145); the mocking slice remains a focused sniff issue (#146) - Testing: Test Suites — Tier 3 core, four enforced slices: what each suite is for — a unit test's actual scope, whether a feature test stays off the internet at runtime, and the faked-feature ↔ unfaked-integration twin pairing — turns on runtime behaviour and cross-file judgement, so the core stays with code review (#60); the layout slice is carried by the custom sniff
CleanCode.Testing.TestSuiteNamespace, which warns when a test class's declared suite namespace and its suite directory contradict each other, in both directions (NamespaceMismatch,DirectoryMismatch), reading both sides from the segment directly below a configurable test root and leaving a no-namespace class,STDIN, and non-test declarations alone — detection-only, since reconciling a mismatch means moving the file or renaming its namespace and only the project's layout says which; the content slice for the unit suite is carried by the custom sniffCleanCode.Testing.UnitTestExternalConcerns, which warns when a file under a configurableunitTestPath(defaulttests/Unit/) carries a database trait (DatabaseTrait), a facadefake()(FacadeFake), or an HTTP-kernel request call on the test case (HttpRequest) — token-visible external concerns that belong intests/Feature/, matched on whole path segments anchored at the test root closest to the file, warning-severity, detection-only and suppressible per line (#148); the feature-suite content slice is carried by the custom sniffCleanCode.Testing.NoInternetTraversal, which warns on a raw internet-traversing primitive written in a feature test —curl_init,curl_exec,fsockopen,stream_socket_client, afile_get_contentswhose filename argument — positional or namedfilename:— is a whole string literal (quoted, heredoc or nowdoc) naminghttp://orhttps://, and a directnew GuzzleHttp\Clientresolved through the file's own namespace and imports — leaving the sanctionedHttp::fake()route alone and confined to configurable feature-path globs (featureTestPatterns), detection-only and warning-severity because a primitive is a strong signal rather than proof a request left the machine (#149); the integration-suite content slice is carried by the custom sniffCleanCode.Testing.NoHttpFakesInIntegrationTests(#150) - Type Hints and Return Types — Tier 1, Slevomat
TypeHints.PropertyTypeHint(parameter/return hints for all callables are owned by Methods: Type Hints, #70), partly auto-fixable (#45) - Use Statements: No Unused Entries — Tier 1, Slevomat
Namespaces.UnusedUses, auto-fixable (#68) - Use Statements: Sort Alphabetically — Tier 1, Slevomat
Namespaces.AlphabeticallySortedUsesplusDisallowGroupUse+MultipleUsesPerLineto close the group-use and comma-separated bypasses, auto-fixable for flat blocks (#67) - Pattern: SOLID — Tier 3 core; all five principles yielded a token-visible slice, and all five are enforced today: Single Responsibility by the seven shipped size/coupling sniffs (#80, #83, #87, #93, #96, #98, #114); Dependency Inversion by
CleanCode.Classes.DisallowConstructorInstantiation(#72); Liskov Substitution byCleanCode.Pattern.ThrowOnlyMethodOverride, which warns on a method whose entire body is a singlethrowin a type that extends or implements — refused bequest — detection-only (#131); Interface Segregation byCleanCode.Pattern.TooManyInterfaceMethods, which warns on aninterfacedeclaring more thanmaxMethods(default 5) method signatures — counting only what the body declares, not what anextendslist inherits — detection-only (#132); and Open-Closed byCleanCode.Conditionals.TypeDiscriminatorDispatch, which warns once perswitchorif/elseifchain that dispatches on one type-discriminator read ($shape->type,$row['type']) across enough literal branches, at theswitchkeyword or the leadingif, detection only, with a configurableminimumBranchesthreshold (default 3) (#324); whether a flagged dispatch should have been polymorphism, and the semantic core of all five, stay with code review (#5)
PHPMD rule coverage
The CleanCode standard also replicates PHPMD rules, so a project running this ruleset does
not need to run phpmd separately for them. Each mapping is documented under
docs/phpmd/.
- Clear Code: Group Code By Concepts — Tier 3, not lintable; enforced via code review (partial blank-line heuristic noted, follow-up sniff issue left to a human)
- Debt: Mental Debt — Tier 3, code review only; measurable sub-rules tracked separately
- Clear Code: One Idea Per Statement — Tier 3, code review; partial enforcement via chained-assignment / assignment-in-condition sniffs (#157)
- Clear Code: Encapsulate Each Concept in a Method — Tier 3 core, code review; the section-labelling-comment slice is enforced by the custom sniff
CleanCode.ClearCode.SectionComment, which warns on a standalone single-line comment inside a function body that stands between two statements and introduces a further statement in the same scope, with debt markers and auto-formatter directives excluded to their own standards, detection-only (#159) - Clear Code: Encapsulate Related Methods in a Class — Tier 3, code review; partial slice: Action class shape sniff (#161)
- Clear Code: Encapsulate Related Classes in a Domain — Tier 3 core, code review (#16); one Tier 2 slice, the custom sniff
CleanCode.ClearCode.JunkDrawerNamespace, which warns on a namespace segment naming a generic technical bucket (Helpers,Utils,Utilities,Misc,Common,Generalby default, configurable), detection-only and case-insensitive, with framework layers such asControllersandModelsdeliberately absent (#190) - Clear Code: Encapsulate Related Methods in a Class — Tier 3 core, code review: deciding that scattered logic belongs in a class of its own is a semantic judgement no single file's token stream can make (#15); the Action-class-shape slice is Tier 2, custom sniff
CleanCode.ClearCode.ActionSingleEntryPoint, which warns on every public method past the first in a class whose name ends inActionor whose namespace carries anActionssegment —__constructexcluded, static and implicitly-public methods included, public accessors reported like any other extra entry point, detection-only (#161) - Clear Code: Encapsulate Related Classes in a Domain — Tier 3, code review; partial slice: junk-drawer namespace sniff (#190)
- Models: Structure (Attributes/Queries traits) — Tier 3, code review; partial slice carried by the custom sniff
CleanCode.Models.ModelMagicMethodLocation: warns on an accessor, mutator, modernAttributemethod, orscope*/#[Scope]query scope declared in a class body rather than in the model'sAttributesorQueriestrait, detection-only (#192) - Methods: Naming — Tier 3, code review; narrow command–query slice tracked in #172
- Models: Structure (Attributes/Queries traits) — Tier 3, code review; partial slice: accessor/mutator/scope methods declared in a class body (#192)
- Methods: Naming — Tier 3, code review; narrow command–query slice enforced by custom sniff
CleanCode.Naming.ActionMethodReturn, warning severity, report-only: a method named for an action verb that declares a non-void/neverreturn type, or returns an expression with no declared type; configurable verb list and fluent-interface exemption (#172) - Pattern: Don't Repeat Yourself (DRY) — Tier 2, custom sniff: repeated-block detection (#134)
- CleanCode: BooleanArgumentFlag — Tier 2, custom sniff
CleanCode.Functions.DisallowBooleanArgumentFlag, error severity, report-only; stricter than PHPMD on three shapes (#76) - CleanCode: DuplicatedArrayKey — Tier 2, custom sniff
CleanCode.Arrays.DuplicatedArrayKey: flags an array literal that declares the same key twice, comparing the key PHP stores rather than the literal's spelling, report-only; stricter than PHPMD on four shapes, drops one PHPMD false positive, and one shape neither tool catches (#81) - CleanCode: ElseExpression — Tier 1, custom sniff
CleanCode.Conditionals.DisallowElse: reports everyelsebranch; SlevomatControlStructures.EarlyExitcovers only the mechanically fixable subset, so it is not used (#77). The same sniff also carries the stricter clean-code standard #14, so it reportselseiftoo and auto-fixes a narrow subset — both beyond the PHPMD rule - CleanCode: ErrorControlOperator — Tier 1, existing sniff
Generic.PHP.NoSilencedErrorsraised to error severity, report-only; stricter than PHPMD on one shape (#82) - CleanCode: IfStatementAssignment — Tier 1, existing sniff
Generic.CodeAnalysis.AssignmentInConditionraised to error severity, report-only; stricter than PHPMD on the compound assignment operators, thefor/while/switch/case/matchconditions, and file-scope code, plus custom sniffCleanCode.Conditionals.DisallowListAssignmentInConditionfor thelist()destructuring target the Generic sniff misses (#79) - CleanCode: MissingImport — Tier 1, Slevomat
Namespaces.ReferenceUsedNamesOnly, already error severity, auto-fixable; stricter than PHPMD on every reference that is not anew, and one shape neither tool catches (#84) - CleanCode: UndefinedVariable — Tier 1, external sniff
VariableAnalysis.CodeAnalysis.VariableAnalysisraised to error severity, report-only; stricter than PHPMD on two shapes, and one shape neither tool catches (#85) - Controversial: Superglobals — Tier 2, custom sniff
CleanCode.Controversial.Superglobals, error severity, report-only; all sixteen names PHPMD carries, including the seven PHP 4 long-form aliases, bare or interpolated into a double-quoted string or heredoc; stricter than PHPMD on file-scope access and the${name}form, narrower on the static property access PHPMD falsely flags (#90) - CodeSize: CyclomaticComplexity — Tier 2, custom sniff
CleanCode.Metrics.CyclomaticComplexity, error severity, report-only; replicates PDepend'sccn2metric, whichGeneric.Metrics.CyclomaticComplexitycannot express at any threshold, and PHPMD's inclusivereportLeveldefault of 10 (#88) - CodeSize: ExcessiveClassComplexity — Tier 2, custom sniff
CleanCode.Metrics.ExcessiveClassComplexity, error severity, report-only; themaximumproperty defaults to PHPMD's 50, and a class measuring exactly the maximum is reported, as PHPMD reports it (#87) - CodeSize: ExcessiveClassLength — Tier 2, custom sniff
CleanCode.Classes.ExcessiveClassLengthat PHPMD's own thresholds, report-only; one disclosed undercount where PHP_CodeSniffer mis-pairs class braces (#93) - CodeSize: ExcessiveMethodLength — Tier 2, custom sniff
CleanCode.Functions.ExcessiveMethodLength, error severity, report-only; replicates PDepend'slocandelocmetrics and PHPMD's inclusive threshold, and covers plain functions as PHPMD does (#91) - CodeSize: ExcessiveParameterList — Tier 2, custom sniff
CleanCode.Functions.ExcessiveParameterList, error severity, report-only; thresholdminimum(default 10) is inclusive, so ten parameters violate; stricter than PHPMD on one shape (#95) - CodeSize: ExcessivePublicCount — Tier 2, custom sniff
CleanCode.Metrics.ExcessivePublicCount, error severity, report-only; PHPMD'sminimumproperty and its default of 45, applied inclusively (exactly 45 public members is already a violation, as in PHPMD); stricter than PHPMD on the three shapes PDepend cannot model — a trait's public properties, a constructor's promoted public properties, and an anonymous class's own members (#96) - CodeSize: NPathComplexity — Tier 2, custom sniff
CleanCode.Metrics.NPathComplexity, error severity, report-only; theminimumproperty defaults to PHPMD's 200, and a callable measuring exactly the minimum is reported, as PHPMD reports it (#89) - CodeSize: TooManyFields — Tier 2, custom sniff
CleanCode.Metrics.TooManyFields: reports a class declaring more thanmaxFields(default 15) fields, report-only; stricter than PHPMD on promoted properties, PHP 8.4 property modifiers, and anonymous classes (#98) - CodeSize: TooManyMethods — Tier 2, custom sniff
CleanCode.CodeSize.TooManyMethods, report-only; PHPMD's shipped defaults (maxmethods25,ignorepattern(^(set|get|is|has|with))i) (#80) - CodeSize: TooManyPublicMethods — Tier 2, custom sniff
CleanCode.Classes.TooManyPublicMethods, error severity, report-only; exact parity with PHPMD on its defaults, which are taken from the shippedcodesize.xmlrather than phpmd.org's staleignorepattern(#83) - Design: CouplingBetweenObjects — Tier 2, custom sniff
CleanCode.Metrics.CouplingBetweenObjects, report-only; inclusive threshold as PHPMD implements it, stricter on the unused import #114 asks for and on scoring a nested anonymous class as its own scope, looser on themixed/object/docblock types PDepend reads and the TypeHints rules already require (#114) - Design: CountInLoopExpression — Tier 2, custom sniff
CleanCode.ControlStructures.DisallowCountInLoopExpression, report-only; stricter than PHPMD on two spellings it misses, looser on the first-class callablecount(...)it falsely flags (#99) - Design: DepthOfInheritance — Tier 2, custom sniff
CleanCode.Metrics.DepthOfInheritance, error severity, report-only; PHPMD'sminimumproperty and its default of 6, applied inclusively (exactly 6 parents is already a violation, as in PHPMD); the one rule here that resolves across the whole analysed fileset, via PHP_CodeSniffer's ownFileList, sophpcs src/sees the parentsphpmd src/sees and an ancestor outside the run weighs 2 as PDepend weighs it (#112) - Design: DevelopmentCodeFragment — Tier 1, existing sniff
CleanCode.Debug.DisallowDebugFunctionsextended to PHPMD'sunwanted-functionsdefaults, report-only (#86) - Design: EvalExpression — Tier 1, existing sniff
Squiz.PHP.Evalraised to error severity, report-only (#107) - Design: ExitExpression — Tier 2, custom sniff
CleanCode.ControlStructures.DisallowExitExpression, report-only; scoped to function and method bodies as PHPMD scopes it, and stricter than PHPMD on two shapes PDepend does not model (#105) - Design: GotoStatement — Tier 1, existing sniff
Generic.PHP.DiscourageGotoraised to error severity with a replacement message, report-only; stricter than PHPMD on target labels and ongotoat file scope (#109) - Design: NumberOfChildren — Tier 2, custom sniff
CleanCode.Metrics.NumberOfChildren, report-only; the ruleset's first cross-file rule, counting direct subclasses across PHPCS's own file list, so a single-file run sees only that file's children exactly as PHPMD does; inclusive threshold as PHPMD implements it, against #110's acceptance criteria asking for the boundary to be silent (#110) - Naming: BooleanGetMethodName — Tier 2, custom sniff
CleanCode.Naming.BooleanGetMethodName, error severity, report-only;checkParameterizedMethodsmatches PHPMD, and stricter than PHPMD on six shapes (#116) - Naming: ConstructorWithNameAsEnclosingClass — Tier 1, existing sniff
Generic.NamingConventions.ConstructorName(already error severity, call-site code excluded), report-only; stricter than PHPMD on namespaced classes, looser on two shapes PHPMD misreads (#113) - Naming: LongClassName — Tier 2, custom sniff
CleanCode.Naming.LongClassName: flags a class, interface, trait, or enum whose declared name exceeds a configurable maximum (default 40, with optional prefix/suffix subtraction), report-only (#101) - Naming: LongVariable — Tier 2, custom sniff
CleanCode.Naming.LongVariable: flags a field, formal parameter, or local variable whose name exceeds a configurable maximum (default 20,$not counted, with optional prefix/suffix subtraction), report-only; diverges from PHPMD on trait duplicates and string interpolation (#108) - Naming: ShortClassName — Tier 2, custom sniff
CleanCode.Naming.ShortClassName, error severity, report-only; a class, interface, trait, or enum name shorter thanminimum(default 3), with no divergence from PHPMD (#103) - Naming: ShortMethodName — Tier 2, custom sniff
CleanCode.Naming.ShortMethodName, report-only; PHPMD'sminimumandexceptionsproperties carried over unchanged, and stricter than PHPMD on anonymous-class methods (#111) - Naming: ShortVariable — Tier 2, custom sniff
CleanCode.Naming.ShortVariable, report-only; PHPMD'sminimumandexceptionsproperties carried over unchanged, stricter than PHPMD on member-access chains, catch blocks, procedural code and anonymous classes, and quieter on$this, which PHPMD reports onceminimumis raised above four but no rename can fix (#106) - UnusedCode: UnusedFormalParameter — Tier 2, custom sniff
CleanCode.DeadCode.UnusedFormalParameter, error severity, report-only; a declared parameter never read in the body, exempting an@inheritdocor#[\Override]annotation, a same-file resolvable override, and a body-less declaration; stricter than PHPMD on an override of a parent it cannot see in the same file (#120) - UnusedCode: UnusedLocalVariable — Tier 1, the
UnusedVariablecode on the same external sniffVariableAnalysis.CodeAnalysis.VariableAnalysis, raised to error severity, report-only; three properties configured to match PHPMD, and one report per assignment where PHPMD reports one per name (#118)