dave-liddament/php-language-extensions
PHP Language Extensions adds attributes for immutable classes, pure functions, read-only properties, and named parameters, with PHPStan rules to enforce them. Bring stronger safety and clearer APIs to PHP without waiting for new syntax.
Architecture fit. The package is deliberately minimal: it ships a set of PHP 8 attributes (#[Struct], #[Data], #[Enum]/#[Enum_], #[Singleton], #[Namespace_], and companions) plus the marker classes themselves. It contains no service provider, no runtime hooks, no container bindings, and no Laravel coupling whatsoever. Architecturally that is an excellent fit for a Laravel codebase — it is a pure annotation layer that a static analyser interprets. The critical architectural fact a TPM must internalise is that the attributes do nothing on their own. Enforcement is delivered by the companion rules package (dave-liddament/phpstan-php-language-extensions) loaded into PHPStan. The dependency chain is therefore: attributes (runtime-visible class definitions, require) → analyser rules (require-dev) → PHPStan/Larastan execution in CI → baseline configuration → team discipline. Three of those five links are process, not code.
Integration feasibility. Mechanically trivial. composer require dave-liddament/php-language-extensions plus composer require --dev dave-liddament/phpstan-php-language-extensions, then an includes: entry in phpstan.neon. No migrations, no config publishing, no bootstrapping. The real work is scoping: Laravel projects already carry a large amount of framework-shaped code (Eloquent models, form requests, console commands) that will violate struct/data semantics by design, so paths must be curated to domain and DTO directories rather than the whole app/ tree. Coexistence with Larastan requires care — both register PHPStan extensions and both have opinions about models — but conflicts are configuration-shaped, not fundamental.
Technical risk. Runtime risk is effectively zero: no code executes, no autoload cost beyond a handful of empty classes, no production surface. The risk concentrates in four areas.
unserialize(), Model::fill(), Eloquent hydration, Laravel's toArray()/fromArray() paths, and json_decode all bypass the model entirely. #[Data] does not make a DTO immutable at runtime; it makes statically analysable misuse a build failure. If the team's mental model drifts toward "the tool protects us," you get a class of production bugs the tooling was assumed to prevent.#[Struct] permits," and that definition can change between minor releases of the rules package. A Renovate PR that bumps PHPStan or the rules can turn CI red on completely untouched code. This is the single most likely cause of the tool being disabled six months in.Key questions to resolve before adoption.
readonly properties, 8.2 readonly classes, and native enums already cover most of what this package offered pre-8.2. The defensible remaining value is enforcement of shapes PHP cannot express — "a struct may have no methods," "this class must be constructed only via a named constructor," deep immutability of array properties. If we cannot name those gaps concretely, we should not adopt.spatie/laravel-data (runtime DTOs with validation — a different philosophy solving an overlapping problem) and concluded we want compile-time, not runtime, enforcement?Stack fit. Laravel 10/11/12 on PHP 8.2+ with an existing PHPStan/Larastan pipeline: strong fit, low ceremony. Laravel on PHP 8.0–8.1 without static analysis: poor fit, and the effort would be better spent turning on PHPStan first. Note the split dependency classification — the attributes package belongs in require (the classes must be resolvable for ReflectionClass::getAttributes() to return them without throwing), while the rules package belongs in require-dev.
Migration path. Adopt in five reversible steps, each of which leaves the build green.
continue-on-error: true) scoped to app/Domain, app/Data, app/Support — not app/Models, not app/Http.@phpstan-ignore comments; exclusions are auditable, inline ignores metastasise.Compatibility considerations. Pin the rules package to a version whose PHPStan constraint matches the project's, and verify Larastan's includes ordering — extension registration order affects which rule wins on overlapping node types. Confirm whether Psalm support exists if any service still uses it; mixed-analyser environments double the configuration surface. PHPUnit/Pest, Octane, and the service container are unaffected. Nothing here interacts with runtime performance, so no load-testing or staging validation is required — a genuine advantage over most dependency additions.
Sequencing relative to other work. Do not land this during a framework upgrade or a PHPStan major-version bump; the two change sets will be indistinguishable in CI failures. Land it in a quiet window, own it in a single PR of under 100 lines, and add Renovate/Dependabot coverage for both packages at the same time so the first dependency bump is a controlled experiment rather than a surprise.
Maintenance. Runtime maintenance is negligible — the attributes package may go a year without a release and nothing breaks, because there is no runtime behaviour to break. The genuine maintenance load is the version matrix: PHPStan ↔ rules package ↔ PHP version. Budget roughly one to two engineer-days per PHPStan major bump to re-triage changed rule semantics, and a few hours per minor. Two Composer lines and a short phpstan.neon snippet are the entire footprint; there is no operational runbook, no monitoring, no on-call implication.
Support. MIT, single maintainer, no SLA, no commercial backing. For an enterprise the honest framing is: if it breaks, we own it. The mitigating factor is that both packages are small and the attributes themselves are trivial class declarations — a fork under MIT is a day of work, and a vendored copy would survive indefinite abandonment. The real support burden is internal: whoever owns static analysis owns this, and that knowledge should not sit with one person.
Scaling. Application runtime scaling: unaffected, zero impact. CI scaling: PHPStan already dominates analysis time on a large Laravel monorepo; these rules add marginal cost but the pipeline grows with file count regardless. On very large codebases, diff-aware analysis is not just a migration convenience but a permanent cost control. Team scaling is where the leverage actually is — the attributes encode design intent that would otherwise live in a wiki page nobody reads, which is precisely the thing that decays as headcount grows.
Failure modes, ranked by expected cost.
paths.Ramp-up. Low ceiling, moderate floor. A competent PHP developer absorbs the attribute catalogue in under an hour; the harder and more valuable skill is reading PHPStan output and responding with a scoped exclusion rather than a blanket ignore. Plan a one-hour brown-bag, a one-page ADR naming the specific invariants being enforced and — importantly — what is not enforced, and a link from the CI failure message or CONTRIBUTING to that ADR. Expect the first two weeks of friction to come from developers hitting "why is my DTO rejected" rather than from the tool being wrong.
Verdict. Adopt tactically, not strategically. The score (~3.9/5) and the low opportunity signal (~31) read correctly: this is a sharp, low-risk, narrow tool that pays off only for teams already committed to static analysis as a quality gate. It is not a platform initiative, it does not justify a migration programme, and it should be evaluated against the shrinking set of things PHP still cannot express natively. If the team cannot articulate those specific gaps before installation, defer.
How can I help you explore Laravel packages today?