assoconnect/php-quality-config
A dev quality-tooling config for AssoConnect projects, bundling PHPStan rules (HTTPS link enforcement, no DateTime, strict object comparison), a Rector ruleset, and PSR-12 PHPCS standards to keep code consistent and safe.
Installation
composer require --dev assoconnect/php-quality-config:^2.5.0
v2.5.0 requires PHP 8.5+. The 8.4 CI lane was dropped in this release (PR #36), so the shared configs, PHPStan/Psalm baselines and fixer ruleset now assume an 8.5 runtime. Check your matrix before bumping:
php -v # must report 8.5.x
If you cannot move the runtime yet, pin the previous minor instead: composer require --dev assoconnect/php-quality-config:~2.4.0.
First Use Case Point your tooling at the shared configs shipped by the package:
# phpstan.neon
includes:
- vendor/assoconnect/php-quality-config/phpstan.neon
// .php-cs-fixer.dist.php
return (new PhpCsFixer\Config())
->setRules(require __DIR__.'/vendor/assoconnect/php-quality-config/php-cs-fixer.ruleset.php')
->setFinder(
PhpCsFixer\Finder::create()->in(__DIR__.'/app')->in(__DIR__.'/tests')
);
Then run the tools once to confirm baseline parity:
vendor/bin/phpstan analyse
vendor/bin/php-cs-fixer fix --dry-run --diff
Key Files to Explore
phpstan.neon — the canonical baseline/level; read this first when a rule suddenly fires.psalm.xml — Psalm config for teams running Psalm alongside PHPStan.php-cs-fixer.ruleset.php — returns the rules array; diff it against your local override after every bump.Upgrading 2.3/2.4 → 2.5 Treat this as a platform bump, not a lint tweak. Order of operations:
# 1. runtime first
# composer.json -> "php": "^8.5"
# 2. then the package
composer require --dev assoconnect/php-quality-config:^2.5.0
# 3. then re-baseline
vendor/bin/phpstan analyse --generate-baseline
vendor/bin/php-cs-fixer fix
Commit the runtime bump, the package bump and the re-baseline as separate commits — the diff of a re-baseline is noise that hides real regressions.
Laravel-Specific Integrations Run the quality config against app code with a project-level wrapper that keeps Laravel's magic strings out of scope:
# phpstan.neon
includes:
- vendor/assoconnect/php-quality-config/phpstan.neon
parameters:
paths:
- app
- tests
excludePaths:
- app/Console/Kernel.php
- bootstrap/cache/*
ignoreErrors:
- '#Illuminate\\Database\\Eloquent\\Builder#'
Use PHPUnit attributes (PHPUnit 11/12 style) rather than annotations so static analysis can see test intent:
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\DataProvider;
#[Test]
public function it_binds_a_route_model(): void
{
$this->assertIsArray(User::all()->toArray());
}
Team Onboarding Let new contributors adopt stricter settings through a thin override rather than editing the vendored config:
# phpstan.strict.neon
includes:
- vendor/assoconnect/php-quality-config/phpstan.neon
parameters:
level: 9
Land it as a separate, opt-in CI job so the main pipeline stays green while people fix findings incrementally.
CI/CD Pipelines Add an explicit PHP version guard so the 8.4 lane fails loudly instead of silently resolving an old package version:
- uses: shivammathur/setup-php@v2
with:
php-version: '8.5'
coverage: none
- run: composer install --prefer-dist --no-progress
- run: vendor/bin/phpstan analyse --no-progress --memory-limit=2G
- run: vendor/bin/php-cs-fixer fix --dry-run --diff
Cache vendor/ keyed on composer.lock and the PHP version — mixing cache entries between 8.4 and 8.5 will resurface deleted platform packages.
require. It is dev-only tooling; if Composer resolves it in production, your --no-dev install is misconfigured..php-cs-fixer.dist.php, so future package releases still flow through.app/ in the pre-commit hook and the full tree in CI; Laravel's service container defeats level 8+ on some legacy paths and slows hooks noticeably.composer.json alongside the package. The shared configs target specific major versions; an unpinned tool can drift ahead of the config it consumes.PHP 8.5 requirement is a hard platform bump
v2.5.0 drops the 8.4 CI lane entirely, so Composer will refuse to install on 8.4. There is no "works but unsupported" zone — you get a platform conflict. Verify Docker images, CI runners, and any php:8.4-fpm bases before running composer update, or you'll discover it in a deploy.
Transitive dev tooling may lag PHP 8.5
Xdebug, Psalm, and some PHPStan extensions ship 8.5 support later than core PHP. If composer update stalls on phpunit/phpunit or vimeo/psalm, check their 8.5 support explicitly rather than widening constraints blindly.
Silent version downgrade
If a composer.lock on a mixed-version CI matrix resolves the package, older runners will silently get 2.4.x while newer ones get 2.5.x — different rules per job. Pin to a single PHP version in CI and assert it.
Deprecated PHPUnit assertions still lurking
Old tests using assertInternalType() will error on modern PHPUnit:
// before
$this->assertInternalType('array', $result);
// after
$this->assertIsArray($result);
Also check @expectedException annotations — replaced by $this->expectException() / attributes.
--debug and -v to see which config file each rule originates from:
vendor/bin/phpstan analyse -v --debug
Then phpstan analyse --generate-baseline and review the baseline diff — new entries point at genuinely new rules, not code regressions.--dry-run --diff first, and confirm setFinder() is not scanning vendor/ or storage/.composer why-not assoconnect/php-quality-config 2.5.0 shows exactly which constraint (usually php) blocks the bump.--debug traces test/bootstrap execution; pair with --display-deprecations to catch 8.5 deprecations before they become errors.Custom PHPStan rules
Implement the interface (not an abstract base class) in src/PHPStan/Rules and register it:
use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
/** @implements Rule<Node\Expr\New_> */
final class NoDirectModelNewRule implements Rule
{
public function getNodeType(): string
{
return Node\Expr\New_::class;
}
public function processNode(Node $node, Scope $scope): array
{
// ...
}
}
services:
- App\PHPStan\Rules\NoDirectModelNewRule
Layered configs
Compose a base → strict → team override chain with includes: so each level is independently reviewable and revertable.
Quality gates in CI Emit machine-readable output for PR annotations instead of eyeballing logs:
vendor/bin/phpstan analyse --error-format=github
vendor/bin/php-cs-fixer fix --dry-run --format=gitlab > gl-code-quality.json
Composer scripts Wire the whole set behind one entry point so the local and CI invocations can't drift:
"scripts": {
"quality": [
"phpstan analyse --no-progress",
"php-cs-fixer fix --dry-run --diff",
"phpunit"
]
}
How can I help you explore Laravel packages today?