Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Phpcsutils Laravel Package

phpcsstandards/phpcsutils

PHPCSUtils is a suite of utility functions for PHP_CodeSniffer, giving standard authors reliable, backward-compatible helpers for token handling, sniff testing, and cross-version support. Integrates via Composer or manually; PHP 5.4 to 8.5.

View on GitHub
Deep Wiki
Context7

Getting Started

PHPCSUtils is not an application library — it is a toolkit for people who write PHP_CodeSniffer sniffs. In a Laravel codebase the realistic entry point is: you already run phpcs (or are adding it next to Pint/PHPStan), you have opinions that no off-the-shelf standard encodes (e.g. "no env() outside config/", "no dd()/dump() committed", "queued jobs must declare $tries"), and you are going to write a small in-house standard. PHPCSUtils removes ~80% of the token-walking boilerplate from that job.

Install (dev only):

composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
composer require --dev phpcsstandards/phpcsutils:"^1.0"
composer require --dev squizlabs/php_codesniffer:"^3.13"   # or phpcsstandards/php_codesniffer:^4.0

The package ships the Dealerdirect Composer PHPCS plugin, so once installed it registers itself and your standard with PHPCS automatically. You do not add --config-set installed_paths entries for Composer installs.

Where to look first, in this order:

  1. The docs site (linked from the README) → Utils section. Every utility is a static method with a documented signature; skim the list once so you know what exists (Arrays, FunctionDeclarations, PassedParameters, Variables, Conditions, Context, Lists, Namespaces, ObjectDeclarations, Operators, Scopes, TextStrings, UseStatements, Visibility).
  2. PHPCSUtils\Tokens\Collections — the token groups (Collections::arrayOpenTokensBC(), Collections::functionCallTokens(), …). These replace the long T_* arrays you would otherwise copy-paste.
  3. PHPCSUtils\AbstractSniffs\AbstractArrayDeclarationSniff — if your first rule touches arrays/config files, start here.
  4. Your first real use case: a Laravel.Env.NoEnvOutsideConfig sniff. It is small, obviously valuable, and exercises FunctionDeclarations::getName(), Context::inConfigFile()-style path checks, and File::getFilename().

Skeleton standard layout (PHPCS autoloading is convention-based, so paths and namespaces must match exactly):

tools/phpcs/LaravelStandards/
├── ruleset.xml
└── Laravel/
    └── Sniffs/
        └── Env/
            └── NoEnvOutsideConfigSniff.php
<!-- ruleset.xml -->
<?xml version="1.0"?>
<ruleset name="LaravelInternal">
    <rule ref="PHPCSUtils"/>            <!-- friendly error if PHPCSUtils is missing -->
    <rule ref="Laravel.Env.NoEnvOutsideConfig"/>
</ruleset>
<?php
namespace LaravelStandards\Laravel\Sniffs\Env;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHPCSUtils\Utils\FunctionDeclarations;

final class NoEnvOutsideConfigSniff implements Sniff
{
    public function register(): array
    {
        return [T_STRING];
    }

    public function process(File $phpcsFile, $stackPtr): void
    {
        if (FunctionDeclarations::getName($phpcsFile, $stackPtr) !== 'env') {
            return;
        }
        // ... scope check, then:
        $phpcsFile->addError('Call config() and read from config instead of env().', $stackPtr, 'Found');
    }
}
vendor/bin/phpcs --standard=tools/phpcs/LaravelStandards/ruleset.xml app/ -s

Implementation Patterns

1. Prefer the abstract sniffs over hand-rolled token walking. AbstractArrayDeclarationSniff gives you processArray(File $phpcsFile, int $stackPtr) plus helpers like getArrayItems(), getActualArrayKey(), getDoubleArrowPtr(), isShortArrayOrList(). Sniffs over config/*.php become a dozen lines. Similarly, extend PHPCSUtils\AbstractSniffs\AbstractFunctionCallParameterSniff when the rule is about a call's arguments — you get processParameters()/processParameter() callbacks and the open/close parenthesis bookkeeping for free.

2. Reach for PassedParameters and FunctionDeclarations before raw tokens. Almost every Laravel-flavoured rule looks like "find call X, inspect argument N". That is:

use PHPCSUtils\Utils\PassedParameters;
use PHPCSUtils\Utils\FunctionDeclarations;

$name = FunctionDeclarations::getName($phpcsFile, $ptr);
$params = PassedParameters::getParameters($phpcsFile, $ptr);
$arg2 = PassedParameters::getParameterFromStack($params, 2, 'options');

These correctly handle nested parentheses, arrays, closures, and named arguments — the things that make naive findNext(T_COMMA) implementations wrong.

3. Use Collections for token sets instead of your own arrays. Collections::arrayOpenTokensBC(), Collections::functionCallTokens(), Collections::parameterTypeTokens() etc. are maintained across PHPCS 3.x/4.x and PHP version differences, so you do not have to re-audit your sniff when PHP 8.5 adds tokens.

4. Centralise "which file am I in" logic. Laravel rules are usually path-sensitive (app/ yes, config/ no, tests/ sometimes). Wrap File::getFilename(), File::getFilenameWithoutExtension() and Common::getDeclareScope() checks in a tiny LaravelStandards\Util\Paths helper so every sniff applies the same policy. Do not scatter strpos($file->getFilename(), 'config/') everywhere.

5. Pair with a fixer where it is safe, and only there. PHPCSUtils has no fixer helpers — you still write $phpcsFile->fixer->beginChangeset() / replaceToken() / endChangeset() and return the fix count from process(). Auto-fix imports ordering and whitespace; report-only for env() and dd() (a wrong autofix in a Laravel app is worse than a warning).

6. Unit-test the sniffs. Two mechanisms, do not mix them up:

  • Sniff tests → PHPCS native AbstractSniffUnitTest, with *.inc and *.fixed fixture files. This is what actually validates your rule.
  • Custom utility method tests → PHPCSUtils\TestUtils\UtilityMethodTestCase. This requires the extra autoloader:
<!-- phpunit.xml -->
<php>
    <env name="PHPCSUtils_DIR" value="/path/to/PHPCSUtils"/>
</php>
// tests/bootstrap.php
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../vendor/phpcsstandards/phpcsutils/phpcsutils-autoload.php';

7. Wire it into the Laravel dev loop, not just CI.

"scripts": {
    "lint:sniffs": "phpcs --standard=tools/phpcs/LaravelStandards/ruleset.xml app config routes database",
    "pint": "pint",
    "check": ["@pint", "@lint:sniffs", "phpstan", "test"]
}

Pint handles formatting (PHP-CS-Fixer under the hood); PHPCS handles your semantic rules. They do not conflict because they cover different concerns — but keep Pint's preset from rewriting files in the same CI step in a way that masks PHPCS failures.

8. Escape hatches in application code. Every sniff you add should have a documented ignore comment form, and you should use -s locally so developers can see the sniff code to write:

// phpcs:ignore Laravel.Env.NoEnvOutsideConfig.Found -- required for container bootstrapping
$key = env('APP_KEY');

9. Debugging a sniff. phpcs -vvv shows tokenising/proces decisions; phpcs --report=source shows which sniffs fire most; phpcs --sniffs=Vendor.Standard.Cat.Name isolates your rule. If a sniff fires zero times, first check the class/file naming (...Sniffs\Category\NameSniff ↔ Vendor.Standard.Category.Name) before suspecting PHPCSUtils.

Gotchas and Tips

  • Minimum PHPCS 3.13.5 / 4.0.1, PHP 5.4+. Most Laravel projects are fine, but a pinned squizlabs/php_codesniffer in an older monorepo will silently fail to load PHPCSUtils classes. Bump PHPCS first.
  • The "class not found" fatal is almost always registration, not code. Under Composer the plugin must be allowed: composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true. Without it, the standard file loads but PHPCSUtils\... classes never resolve. Adding <rule ref="PHPCSUtils"/> to your ruleset converts the fatal into ERROR: the "PHPCSUtils" coding standard is not installed.
  • Non-Composer / Docker-without-plugin setups must register both paths. phpcs --config-set installed_paths overwrites, so always read phpcs --config-show first and pass a comma-separated list. Prefer --runtime-set installed_paths or a mounted CodeSniffer.conf in CI images to avoid mutating a shared config.
  • PHPCS's autoloader is namespace-convention based. Your sniff must live at <Standard>/<Category>/Sniffs/<Name>Sniff.php with namespace <Standard>\<Category>\Sniffs. A typo here produces "referenced sniff does not exist", which looks like a PHPCSUtils problem but isn't.
  • PHPCSUtils does not upgrade PHPCS native sniffs. If your ruleset also includes PSR12/Squiz.*, those keep behaving exactly as the installed PHPCS behaves. Only your own sniffs get the back-compat layer via PHPCSUtils\BackCompat\Helper::getVersion() and the BC utility wrappers.
  • UTF-8 only. Non-UTF-8 files may tokenise oddly and give you phantom results; the fix is instructing the team/editor, not patching the sniff.
  • No JS/CSS support. Removed with PHPCS 4.x; don't plan rules around Blade/JS assets. Restrict PHPCS to PHP: --extensions=php and exclude resources/views/**/*.blade.php, public/, storage/.
  • Always exclude vendor/, storage/, bootstrap/cache/. PHPCS scanning vendor is the single biggest cause of "PHPCS is too slow" complaints; it also produces false positives from third-party code.
  • --cache masks sniff edits. While iterating on a sniff, run with --no-cache (or delete the cache file); otherwise PHPCS skips files that hash-match and you'll conclude your new rule is broken. In CI, use a cache key that includes the sniff source hash.
  • Fixing invalidates pointers. Inside a fix loop, $stackPtr positions shift. Re-fetch with $phpcsFile->findNext() after replaceToken(), or collect all targets first and let a single changeset handle them. Also return the affected token count from process() so PHPCS's loop accounting stays correct.
  • Performance: PassedParameters::getParameters() and scope utilities are cheap per-call but not free. Register the narrowest token set you can (T_STRING + a name check beats registering 40 token types), and memoise per-file results in a private array keyed by file + token when you analyse the same construct repeatedly.
  • Extension points, in order of preference: (1) compose existing utilities into your sniff; (2) extend the abstract sniffs; (3) extend PHPCSUtils\Tokens\Collections-style constants in your own standard's Tokens class; (4) only fork PHPCSUtils if you are maintaining a public standard and need to support a PHPCS version the package hasn't caught up with yet.
  • Keep it a dev dependency and pin it. "require-dev": { "phpcsstandards/phpcsutils": "^1.0" } — never let it land in production autoload. If you publish your internal standard as a package, ^1.0 is the correct constraint for consumers of your standard too, since the release cadence is active and 1.x is documented as the supported line.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log