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

Laravel Strict Stubs Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Know what you're adopting before you install: this package is archived (last release 2023-02-14) and has 2 stars. Treat it as a one-time source of strict stub files, not as a long-lived dependency you keep in composer.json forever. The install-and-publish step is the whole product; after publishing, the stub files live in your repo and the package is no longer load-bearing.

Minimal steps:

# 1. Install as a dev dependency (it has no runtime role)
composer require --dev mll-lab/laravel-strict-stubs

# 2. Publish the stubs into the project root
php artisan vendor:publish --tag=stubs
# if the tag doesn't match, inspect the provider's publishes() call:
php artisan vendor:publish --provider="MLL\LaravelStrictStubs\LaravelStrictStubsServiceProvider"

This drops stub files into stubs/ at the project root. Laravel's generators resolve base_path('stubs/<name>.stub') before the framework's built-in stubs, so anything you publish there immediately takes effect for make:* commands. Commit the stubs/ directory.

Where to look first: the service provider and the stubs/ list in the repo. You need to know exactly which stub names are shipped (model.stub, model.pivot.stub, controller.stub, controller.plain.stub, migration.stub, migration.create.stub, migration.update.stub, factory.stub, seeder.stub, policy.stub, request.stub, resource.stub, test.stub, console.stub, …). Anything not shipped silently falls back to Laravel's default, which is the single biggest source of inconsistent output (see Gotchas).

First use case — validate on one generator, on a throwaway branch:

php artisan make:model Invoice -mf
git diff

Check four things on the generated model: declare(strict_types=1); is the first statement, the class is final, the casts() / $fillable shape matches your Laravel version, and the file passes Pint/php-cs-fixer with no diff. Then delete the artifacts. If the generated skeleton looks like an older Laravel, stop — the stub is stale for your framework version and you should copy-edit it before adopting.

Implementation Patterns

Publish once, then drop the dependency. The cleanest day-to-day posture for an archived stub package: publish the stubs, copy them into your project, composer remove --dev mll-lab/laravel-strict-stubs, and commit. You keep 100% of the value (the files), you stop tracking an abandoned release cadence, and there is no supply-chain surface left. The only thing you lose is the ability to vendor:publish --force your way back to an upstream baseline — which you weren't going to get anyway, since the package is archived.

For multi-service shops, put the stubs in a starter kit, not in every repo. Because the stub files are just files, the right home is your internal skeleton / template repo (or a small private Composer package with a stubs/ directory and a publish tag). Then make:model produces consistent output across services without each service independently depending on a 2-star archived package.

Keep them in sync with the framework, deliberately. Stubs are version-coupled to Laravel's generators. A reasonable workflow:

# When upgrading Laravel major:
php artisan stub:publish            # framework's own stubs (Laravel 8+)
diff -ru stubs/ vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/

Use the framework diff as the changelog for what your strict stubs are now missing (new casts() method style, HasFactory, anonymous-class migrations, protected function casts(): array, etc.), then hand-edit your stubs. This is the recurring maintenance task the package was supposed to absorb for you — since it's archived, it's now yours.

Publish all stub variants, never a subset. make:migration picks between migration.create.stub, migration.update.stub, and the base migration.stub depending on flags and whether the table exists. make:model picks model.pivot.stub for --pivot. make:controller picks controller.plain.stub for --plain. Partial publishing produces a codebase where half the generated files are strict and half aren't, which is worse than either extreme.

Add a CI guard so drift is caught. A cheap check that generated code is lint-clean and strict:

php artisan make:model CiProbe -m && vendor/bin/pint --test app/Models/CiProbe.php database/migrations/*ci_probe* && rm app/Models/CiProbe.php .../*ci_probe*

Optionally assert strictness with a tiny script (head -3 contains declare(strict_types=1);, grep -q 'final class'). This catches both a stale stub and an accidental revert to framework defaults.

Pair with static analysis. The real payoff of declare(strict_types=1) plus typed signatures on generated classes is that PHPStan/Larastan has less to complain about on day one — newly scaffolded models/controllers don't need a baseline entry. If you're not running static analysis, the value of this package drops to formatting only.

Gotchas and Tips

  • Published stubs shadow the package's stubs — forever. After vendor:publish, upgrading (or, hypothetically, republishing) the Composer package does nothing to your generated output. vendor:publish --force will overwrite your local edits, so if you've customized, back up before re-publishing. This is the #1 confusion with stub packages.
  • declare(strict_types=1) must be the literal first statement in the file. No BOM, no blank line after <?php, no license header, no <?php // comment. A byte-order mark or a leading newline produces a fatal parse error, and it will look like it came from the generator, not the stub. If your tooling prepends headers (some IDE templates, some CI formatters), verify the first bytes of the output with xxd.
  • final on generated classes will break mocking in tests. Mockery and PHPUnit cannot double final classes, and createPartialMock will fail. If your test suite mocks Eloquent models, controllers, or policies, either drop final from those specific stubs or convert those tests to use real instances / fakes. Do this before rolling out — it's a delayed failure that surfaces in CI on a test unrelated to the scaffolding change.
  • final also blocks legitimate extension. Packages and app code that subclass your models or controllers (custom base models, admin panels that extend your controllers, trait-based augmentation) will fatal. Eloquent models are usually safe to mark final in application code, controllers less obviously so.
  • final on migrations conflicts with old-style named migrations. Laravel 8+ generates anonymous classes (return new class extends Migration); older stubs generate a named class. If you copy a stub across Laravel versions you can end up with a class-name collision or a final migration that a package tries to extend. Check that the migration stub form matches your framework version.
  • strict_types changes runtime behavior of generated code, not just formatting. Methods with int/string signatures generated by your stubs will now throw TypeError on coercion where they used to silently cast (e.g. passing '5' where int is expected). Typically fine, occasionally surprising in tests and in strict_types-sensitive route model binding. If you have a boundary that relies on coercion, the stub's type hints are the bug, not the setting.
  • The package is archived — assume the stub set is frozen at Laravel ~9/10 era. Concretely: it will not know about newer generator changes to casts(), factory definition() shapes, or resource/request stubs. Don't install it alongside a Laravel major upgrade; bring the stubs in first, diff them against the framework's current stubs, and reconcile by hand.
  • Brace style matters for your formatter. v2.0.1 exists solely to align stub braces with php-cs-fixer. If your project uses Pint with a preset that differs from that stub style, the very first make:migration will produce a file your CI rejects — the exact problem the package was bought to solve. Run --test mode on generated files as part of adoption, not after.
  • Stubs are plain text with no validation. A typo in a stub (missing use Illuminate\Database\Eloquent\Model;, wrong namespace token) isn't caught until you run the generator and get a broken file. Keep one "golden" generated artifact per generator type in a scratch branch and re-generate it after any stub edit.
  • Extension point: nothing beyond the files. There's no config file, no stub-resolution hook, no ability to conditionally apply strictness per generator. Customization = editing stubs/*.stub. This is a feature (zero runtime overhead, trivially reviewable) and a limit (no "strict for models, default for tests" granularity).
  • Removal is trivial and non-breaking. Deleting stubs/ restores framework defaults immediately; removing the package changes nothing at runtime. There is no migration path, no cached state, no published config to clean up. Factor that into your risk calculus — the cost of trying this and reverting is measured in minutes.
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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource