Code Quality¶
Accurate reference for Conflux's code quality tooling configuration and enforced architecture rules.
Running Quality Checks¶
All commands run inside the Docker container.
# Run all checks (Pint + PHPStan + Rector + security audit)
docker compose exec php composer quality
# Fix code style
docker compose exec php vendor/bin/pint --dirty
# Static analysis
docker compose exec php composer quality:phpstan
# Rector dry-run (preview changes without applying)
docker compose exec php composer quality:rector
# Apply Rector changes
docker compose exec php vendor/bin/rector process
# CVE audit
docker compose exec php composer quality:security
Tooling Configuration¶
Laravel Pint¶
Style fixer using the Laravel preset with additional rules:
| Rule | Behaviour |
|---|---|
declare_strict_types |
Required on every PHP file |
no_unused_imports |
Unused use statements are removed |
| Import ordering | Alphabetical |
| Class element ordering | Consistent, enforced |
Rector¶
Automated code upgrades and dead-code removal.
| Configuration | Value |
|---|---|
| PHP version target | 8.5 |
| Laravel set | Enabled |
| Type coverage level | 10 |
| Dead code level | 10 |
| Code quality level | 10 |
PHPStan / Larastan¶
Static analysis at level 10 via Larastan.
Architecture Rules¶
Enforced by Pest in tests/Unit/ArchTest.php. Violations fail the test suite.
| Rule | Scope |
|---|---|
Every PHP file must declare strict_types=1 |
App\ |
No debug helpers (dd, dump, var_dump, ray) |
App\ |
All controllers must extend App\Http\Controllers\Controller and be suffixed Controller |
App\ |
All models must extend Illuminate\Database\Eloquent\Model |
App\ |
| Every public method must carry a PHPDoc block with at least a one-line summary | App\Services, App\Jobs, App\Contracts, App\Models, App\Enums, App\Http\Controllers, App\Integrations, App\Auth, App\Console\Commands, App\Policies, App\Exceptions, App\Mcp |
The public-method-documentation rule excludes App\Filament, App\Data, App\Providers, and
App\Logging (declarative or framework-contract methods that don't narrate a design decision),
and excludes any constructor that is only promoted properties with an empty body.
The PHPDoc block needs at least a one-line summary of what the method does (or why, if the what
is obvious from the signature). No @param/@return needed — type hints already cover that
(enforced at PHPStan level 10); add them only for something a type hint can't express (e.g. a
Closure signature, an array shape).
/**
* Resolve the integration registered for the connection's system driver.
*/
public function resolve(SystemDriver $driver): IntegrationContract
Test Structure¶
Tests are split into three tiers, each in its own directory:
tests/
├── Unit/ # No DB, no HTTP. Instantiate classes directly (new Model()).
│ └── Models/ # Cast shapes, relationship types, trait usage.
├── Integration/ # DB required. RefreshDatabase applied automatically via Pest.php.
│ └── Models/ # Scopes, factory round-trips, helper methods that persist.
└── Feature/ # Full HTTP stack — routes, responses, auth flows.
Rules:
- Unit tests must never touch the database. Use new ClassName() directly.
- Integration tests get RefreshDatabase automatically — do not declare it per-file.
- Feature tests cover HTTP behaviour end-to-end.
- Mirror the app/ directory structure inside each tier (e.g. Unit/Models/, Integration/Models/).
- Group related tests with describe() blocks (e.g. casts, relationships, scopes, traits). Test names inside a describe() should omit the group prefix — test('status as enum') not test('casts status as enum').
- For API endpoints that wire several layers together, add end-to-end coverage in addition to the mocked Feature tests — see End-to-End Tests below.
End-to-End Tests¶
Endpoints that span multiple layers (controller → registry/service → integration → outbound HTTP) should have at least one end-to-end Feature test that drives the real chain and fakes only the outermost boundary, rather than mocking internal collaborators:
- Don't mock internal collaborators (e.g. the
IntegrationRegistry). Fake only outbound calls withHttp::fake(). - Register test integrations on the real singleton registry, e.g.
app(IntegrationRegistry::class)->register(SystemDriver::WooCommerce, HttpVerifyingTestIntegration::class). - Reuse the shared doubles in
tests/Support/:FakeIntegration(closure-driven, for unit-level template tests) andHttpVerifyingTestIntegration(makes a realHttpcall, for e2e wiring). - Assert both sides: the persisted state and the outbound request via
Http::assertSent(...), so credential/payload plumbing is covered. - Keep them alongside the mocked Feature tests — the mocked tests pin controller logic; the e2e test pins the wiring the mocks skip (registry resolution,
AbstractIntegration::verify()exception handling, the HTTP boundary).
Reference: tests/Feature/Api/RegisterSystemConnectionE2eTest.php.
PHPStan in Tests¶
PHPStan level 10 runs over the tests/ directory. Key rules that apply specifically to tests:
- Null-narrowing after Pest assertions:
->toBeInstanceOf()in a Pest chain does not narrow nullable types for PHPStan. Useassert($var instanceof SomeClass)immediately after the expect call to narrow the type before accessing sub-properties. - Test integration doubles: Prefer the shared doubles in
tests/Support/(FakeIntegration,HttpVerifyingTestIntegration) over inline anonymousAbstractIntegrationsubclasses. Any subclass you do write must implement all abstract hooks (performVerification(),fetchBulkExport()) and carry a/** @return Collection<int, CustomerData|OrderData|ProductData> */PHPDoc onfetchBulkExport(), otherwise PHPStan infers a narrower generic type and reports a covariance error. - Mocking
IntegrationContract:Mockery::mock()is typed asMockInterfacehere (no Mockery PHPStan extension). Annotate the variable/** @var IntegrationContract $integration */so it satisfies typed parameters, keep the expectation on one line (assign the closure to a variable first), and suppress the resulting analysis noise with// @phpstan-ignore method.notFound, method.nonObject. - Avoid redundant
assert(): For properties typed via#[DataCollectionOf(Foo::class)], PHPStan already knows the element type. A redundantassert($item instanceof Foo)triggersinstanceof.alwaysTrue— skip it and access properties directly.