mll-lab/laravel-strict-stubs
Laravel Strict Stubs replaces Laravel’s default scaffolding stubs with stricter versions, so generated models, controllers, and other classes include declare(strict_types=1) and opinionated defaults. A small package for teams that want consistent, stricter code from the first Artisan make command.
Scope correction first. The prior TPM assessment in the evidence pack frames this as a migration-stub formatter and evaluates only the v2.0.1 changelog line. That understates and mischaracterizes the package. mll-lab/laravel-strict-stubs is a curated set of strictly typed generator stubs covering models, controllers, migrations and more — it injects declare(strict_types=1), final classes, and explicit return/param types into everything php artisan make:* produces. The v2.0.1 brace alignment is genuinely cosmetic, but the package's value proposition is a house code-style policy encoded in scaffolding, not formatting parity for one stub. Any decision framed around "cosmetic patch" is evaluating the wrong thing.
Architecture fit. Excellent, and deliberately so. The package has essentially no runtime surface: it ships .stub files and relies on Laravel's native stub:publish mechanism, where Laravel's generators check a project-root stubs/ directory before falling back to framework defaults. There is no service provider meddling, no middleware, no config merge, nothing in the request path. From an architecture standpoint this is about as inert as a dependency can be — the entire blast radius is "what a developer sees immediately after running a generator."
Integration feasibility. High in the mechanical sense, low in the decision sense. Mechanically it is composer require --dev plus php artisan stub:publish; a developer can have it working in ten minutes. The decision-level difficulty is that the package is archived with 2 stars, a composite score of 0.155, and no release since February 2023. That combination means we are not adopting a maintained baseline — we are adopting a snapshot that nobody is going to update. Our real choice is not buy-vs-build, it is harvest-and-own vs write-from-scratch, and harvesting wins on effort by a wide margin.
Technical risk. Concentrated almost entirely in staleness, not in runtime behavior:
protected function casts(): array replacing the $casts property on models. An archived stub set will not track these. The failure is quiet: make:model succeeds, produces a file that compiles, but omits conventions the rest of the codebase now assumes. That is worse than a loud break.{{ namespace }}, {{ class }}, {{ model }}. If framework token contracts change, a stale stub either throws or emits malformed code. This must be diffed, not assumed.final by default is a policy decision disguised as a tooling default. final on Eloquent models breaks Mockery-based test doubles and any legacy inheritance pattern. Teams that mock models in tests — common in older suites — will hit this on day one. This is the single largest source of "why is this package annoying" tickets.declare(strict_types=1) on generated controllers can surface TypeErrors where request input coercion was previously implicit. Correct behavior, but it converts latent bugs into runtime errors at the boundary, and that should be a deliberate choice rather than a side effect of scaffolding.Key questions to answer before adoption:
final needs an explicit exception policy before rollout.stubs/ (override semantics) and does anything else in our stack publish to that path?stub:publish overwrite hand-edited stubs on re-run, and what is our recovery story if it does?Stack fit. Strong for Laravel 8–10 era applications on PHP 8.1+, which is almost certainly the package's support window given the 2023 release date. Fit degrades for Laravel 11/12, where the framework's own stubs have moved and this package has not. Verify by direct diff against vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/ (and the per-generator stub locations) at our pinned version — that diff is the compatibility assessment, and it takes under an hour.
Recommended migration path — vendor, don't depend. Given the archived status, I would not add a long-lived Composer dependency on this package. Instead:
composer require --dev mll-lab/laravel-strict-stubs.php artisan stub:publish to materialize the stub set.strict_types, final, explicit return types — into framework-accurate stubs.composer.json.This converts an unmaintained supply-chain item into roughly a dozen files of code we own and can edit freely. It also eliminates the "we can't customize without forking" problem the prior assessment correctly flagged.
Compatibility and sequencing.
final/strict-types policy with engineering → vendor adapted stubs into the template repo → wire a CI gate that runs PHPStan/Larastan and Pint against freshly generated code (a smoke test that scaffolds into a temp dir and lints) → roll out to new projects only.composer.json files. One maintained stub set, inherited by every new service, no per-repo dependency to audit.If we insist on the dependency route (defensible for a single small team wanting zero setup): pin the exact version, accept that we own any Laravel-major breakage, and add a CI job that scaffolds a model/controller/migration and lints the output so drift surfaces as a red build rather than as bad code in a PR.
Maintenance. The package itself requires none — because nobody is maintaining it. The maintenance burden transfers entirely to us at each Laravel major upgrade: re-diff our vendored stubs against the new framework stubs and port any new placeholders, methods, or generator types. Realistically a few hours per major, done by whoever owns developer tooling. That is a small, predictable, schedulable cost — and it is the same cost we would pay to write the stubs from scratch, just paid later and with a better starting point.
Support. None, in the formal sense. Archived repository, two stars, no vendor. Community issue tracker is effectively dead. We should not plan around upstream fixes; the vendoring approach is precisely what makes this acceptable, because after step 6 above there is no upstream to depend on.
Scaling. Runtime scaling impact is zero — nothing ships to production. Organizational scaling is the real axis: N repositories each carrying the package or a copy of the stubs is N maintenance points and N drift surfaces. Centralizing in the project skeleton collapses that to one. Worth doing even for a small fleet, because the cost of centralization is a single PR and the cost of drift is inconsistent code style across services discovered months later.
Failure modes, ranked by likelihood × impact:
final classes breaking test doubles — Mockery cannot mock final classes; legacy suites that mock models will fail. High likelihood on adoption day, mitigated by explicit policy and an allowlist of exceptions.TypeErrors at controller boundaries — surfaces pre-existing coercion bugs as runtime failures. Correct but disruptive; mitigate by introducing on new projects first.stubs/ directory publishing to the same paths. Detected immediately in local testing; low impact.stub:publish. Low likelihood if we vendor and drop the publish step, which is another argument for vendoring.Ramp-up. Effectively nil. A single README paragraph — "generated code is strict-typed and final by default; stubs live in stubs/; edit them there" — covers it. The genuine ramp-up cost is not learning the package, it is the policy conversation: convincing engineers that final and strict_types are house standard, and agreeing on where exceptions are legitimate. That conversation is worth having regardless of whether we adopt this package, which is arguably its strongest argument — it gives us a concrete, already-written artifact to react to instead of an abstract style debate.
Recommendation. Harvest the stubs, vendor them into the internal Laravel template, drop the dependency. Do not standardize on the package as a live dependency; the archived status makes that a liability with no compensating upstream value. Adopt the conventions enthusiastically, and the artifact as a one-time starting point.
How can I help you explore Laravel packages today?