php-collective/code-sniffer
PHP_CodeSniffer ruleset from PhpCollective: PSR-2 compliant with many extra sniffs/fixers (incl. PSR-12). Install via composer, add the provided ruleset to phpcs.xml, and run phpcs/phpcbf (or composer scripts) to check and auto-fix coding style.
Return by reference is covered too (#83). The reference marker added in 0.6.7 required a variable or a variadic ellipsis on the right, so function & getItems() slipped through - the thing on the right is a function name. That form is now matched on the preceding function, closure or fn keyword instead.
With this, PhpCollective.WhiteSpace.ImplicitCastSpacing covers every construct psr2r-sniffer's UnaryOperatorSpacing did, and one it did not (! $b), so the downstream sniff can be retired.
Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.6.7...0.6.8
The reference operator is now covered by PhpCollective.WhiteSpace.ImplicitCastSpacing (#82). That sniff already owned this shape for !, @ and unary minus, so & $list joins it rather than arriving as a separate sniff.
$bad = & $list; // reported
foreach ($items as & $item) {} // reported
foreach ($items as $k => & $v) {} // reported
function f(array & $items) {} // reported
function g(& ...$args) {} // reported
$ok = &$list; // untouched
$okBitwise = $a & $b; // untouched
function h(int $x = self::A & self::B) {} // untouched
Telling a reference from a bitwise and takes more than the preceding token, since a type hint precedes the marker in function f(array & $items) and reads exactly like a left operand. A reference is one that stands in front of a variable, or the ellipsis of a by-reference variadic, and either follows something that cannot end a value or sits in a parameter list.
Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.6.6...0.6.7
if (!$previous) on a findPrevious() result, and index 0 - the open tag - is falsy. So <?php sizeof($x); and <?php is_null($x); were silently ignored while the same call one line lower was caught. Affected RemoveFunctionAlias, NoIsNull, DisallowFunctions (both copies) and ShortCast.Duplicate reporting removed (#80). Three constructs were each flagged by two or three rules at once. In every case the rule with the widest coverage stays and the narrower ones are silenced, so nothing stops being detected - it is reported once instead of two or three times.
| Construct | Kept | Silenced |
|---|---|---|
| long casts | SlevomatCodingStandard.PHP.TypeCast |
PSR12.Keywords.ShortFormTypeKeywords, PhpCollective.PHP.ShortCast.LongInvalid |
| incrementer spacing | Generic.WhiteSpace.IncrementDecrementSpacing |
ImplicitCastSpacing.WhitespaceBeforeVariable / .WhitespaceAfterVariable |
sizeof() |
PhpCollective.PHP.RemoveFunctionAlias |
the sizeof entry on Generic.PHP.ForbiddenFunctions |
Coverage went up rather than down: (double) casts were previously reported by only one of the three cast rules, and that is the one that stayed.
Unary minus spacing is now checked (#80). - $a had no coverage. PhpCollective.WhiteSpace.ImplicitCastSpacing already owned this shape for ! and @, so it gained T_MINUS. Detection is deliberately conservative - a minus counts as unary only when the preceding token cannot end a value, so subtraction such as __LINE__ - 1 is left alone. - -$i keeps its space, since closing it would produce a decrement.
Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.6.5...0.6.6
Security: raise the squizlabs/php_codesniffer floor to ^4.0.2 (#75). CVE-2026-67434, an OS command injection advisory published 2026-08-05, covers >=4.0.0,<4.0.2. The previous ^4.0.1 constraint allowed an affected version.
Generic.PHP.DeprecatedFunctions was effectively disabled (#71). The ruleset set the sniff's forbiddenFunctions property, which replaces the list the sniff builds in its constructor from the Reflection API. The standard reported fewer deprecations than plain Generic did - utf8_encode() among them. Removed functions such as create_function() and each() moved to Generic.PHP.ForbiddenFunctions, where a property override is safe.
VoidCast and PipeOperatorSpacing matched nothing on PHP 8.5 (#71). Both sniffs target PHP 8.5 syntax, and PHP 8.5 collapses each construct into a single token - T_VOID_CAST and T_PIPE. The sniffs registered only the pre-8.5 multi-token shapes, so on the version that introduced the syntax they silently passed everything. PHP 8.5 also joined the CI matrix.
ConsistentIndent mis-indented PHP 8.4 property hooks (#76). Property hook braces are not modeled as scopes by PHP_CodeSniffer, so a hook block read as a single indent level and phpcbf dedented the second hook while leaving its body and braces in place.
Attribute names are no longer rewritten as function calls (#78). An attribute name sits in front of a parenthesis just like a call, so an attribute sharing a name with a function alias was reported and auto-fixed - #[Pos(1)] became #[current(1)], which does not compile.
DocComment emitted tab indentation (#79). Two fixes built indentation as str_repeat("\t", column - 1), so phpcbf wrote tabs that the standard's own Generic.WhiteSpace.DisallowTabIndent then reports, and used a column offset as a repeat count - four spaces of indent produced four tabs.
DocBlockTagGrouping reported a fix it never applied (#74). NoExtraNewlineBeforeTags was listed as fixable, but an inverted guard meant phpcbf skipped the change every time.
RemoveFunctionAlias covers more aliases (#70). Added doubleval to floatval, alongside pos, show_source and user_error. The unreachable die and print entries are gone; die is handled by the Exit sniff.
Fully-qualified global function calls are now detected (#72). Five sniffs - RemoveFunctionAlias, NoIsNull, PreferCastOverFunction, DisallowFunctions and PhpSapiConstant - matched only bare T_STRING names, so a leading-backslash call was invisible to all of them. Namespaced calls such as Foo\pos() remain untouched, and fixers that replace the name preserve the backslash.
Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.6.4...0.6.5
DocBlockVar fixer no longer corrupts callable/Closure property types when appending a missing null. The first-space split used to cut a \Closure(string): string signature in half, producing an unparseable [@var](https://github.com/var) annotation. Types with internal structure (callable/Closure signatures, generics, array shapes) are now left untouched, while simple types with a trailing parenthetical description still get their missing null appended.Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.6.3...0.6.4
Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.6.2...0.6.3
conditions checks + cached arrow-function scopes in ConsistentIndentSniff (#63)UseStatementsTrait::getUseStatements and bound the throw class-name lookup to the current statement (#64)parseUseStatements / getNamespace) and dedupe per-doc-block processing (#65)UseStatementSniff::getUseStatements across phpcbf fix iterations (#66)Combined, these cut composer cs-check wall-clock time roughly in half on large method-heavy codebases. On an 11k-line CakePHP controller the slowest single file went from ~40s to ~6s; on a 1095-file project the whole-codebase scan dropped from ~2m08s to ~30s with parallel=16.
Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.6.1...0.6.2
DocBlockTagOrder to class, interface, and trait docblocks with a new configurable classOrder property (#59)DocBlockTagOrder via a new innerOrder property and separate InnerOrderInvalid error code, so inner ordering can be enabled and scoped independently of bucket ordering (#60)Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.6.0...0.6.1
[@param](https://github.com/param) lists, which could cause an infinite fixer loop with DocBlockParamTypeMismatchSniff (#58)PhpCollective.ControlStructures.DisallowAlternativeControlStructures → Universal.ControlStructures.DisallowAlternativeSyntax, PhpCollective.WhiteSpace.CommaSpacing → Universal.WhiteSpace.CommaSpacing (#55)ReferenceUsedNamesOnly (#57)Partial namespace references (e.g. Mockery\MockInterface when only Mockery is imported) are now flagged and must be imported via a full use statement. To keep the previous behavior, override the property in your project's phpcs.xml:
<rule ref="SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly">
<properties>
<property name="allowPartialUses" value="true"/>
</properties>
</rule>
Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.5.5...0.6.0
mb_strtoupper() (#49)Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.5.4...0.5.5
checkReturnTypeHint property to detect invalid : void return type hints on magic methods (__construct, __destruct, __clone)hasReturnType() to properly detect return type declarations on interface and abstract methods. Previously, methods with return types but no body (e.g., public function foo(): string;) incorrectly triggered ReturnMissingInInterface errors.Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.5.3...0.5.4
PHP 8+ Attribute Support: FQCNs in attributes are now detected and auto-fixed
// Before
#[\Foo\Bar\SomeAttribute]
class MyClass {}
// After
use Foo\Bar\SomeAttribute;
#[SomeAttribute]
class MyClass {}
PHP 8.1+ Enum Support: Enum implements clauses are now handled
// Before
enum Status: string implements \Foo\Bar\SomeInterface {}
// After
use Foo\Bar\SomeInterface;
enum Status: string implements SomeInterface {}
Only fully qualified names (starting with \) are auto-fixed. Partially qualified names (e.g., Foo\Bar without leading \) are intentionally not auto-fixed right now.
Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.5.2...0.5.3
array{...}) - https://github.com/php-collective/code-sniffer/pull/45Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.5.1...0.5.2
[@param](https://github.com/param) annotations - https://github.com/php-collective/code-sniffer/pull/44Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.5.0...0.5.1
[@param](https://github.com/param) annotations by @dereuromark in https://github.com/php-collective/code-sniffer/pull/42The attribute usage is now the same as class usage. This seems to make sense moving forward and to not clash with other sniffer packages - and triggers now the major.
Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.4.6...0.5.0
Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.4.3...0.4.4
Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.4.2...0.4.3
"squizlabs/php_codesniffer": "^4.0.0" now.params better. Only require them once they are needed (missing type on a param or a random param defined already that cannot be removed).none/assoc/all (default assoc).This package now also includes phpcsstandards/phpcsextra dependency for even better sniff experience.
Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.2.21...0.3.0
Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.2.19...0.2.20
Also added
Support for Slevomat V8.16 included.
[@property](https://github.com/property) annotations.PhpCollective.Commenting.DisallowShorthandNullableTypeHint to replace Nullable ? in docblocks with |null verbosely.Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.2.7...0.2.8
Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.2.5...0.2.6
Full Changelog: https://github.com/php-collective/code-sniffer/compare/0.2.4...0.2.5
How can I help you explore Laravel packages today?