Laravel 12: starter-kit choices and optional dependency changes

Keep starter-kit adoption separate from an upgrade. Reproduce when Laravel 12 keeps a null constructor default, when it still resolves a class, and which bindings take precedence.
Separate the kit decision from the framework upgrade
Laravel 12 was released on 24 February 2025. For an existing application, the practical questions are which dependency changes affect its behavior and whether a new frontend or authentication stack is needed at all. This article works through one container change. It does not certify an application upgrade.
Laravel’s March 2025 starter-kit introduction covers React and Vue with Inertia, and Livewire with PHP and Blade. WorkOS is an optional authentication variant. The default kits use Laravel’s built-in authentication. The generated application owns its kit code, so adopting a kit also means maintaining that code.
The later Svelte kit appears in Laravel’s February product roundup, published on 2 March 2026. Current 12.x documentation includes it. That makes the current kit list useful for choosing a stack, but unsuitable as an unchanged record of what launched with Laravel 12.
For an existing application, I recommend separating the framework upgrade from a kit adoption or identity-provider migration. For example, a team keeping its Blade screens and current login flow can investigate the container change below without first moving those screens into an Inertia application.
A nullable type and an optional argument are different
Laravel’s 12.x upgrade guide describes a changed default: an unbound class dependency with a constructor default of null now keeps that default instead of being automatically constructed. A service that immediately calls a method on that property may therefore encounter null after the upgrade.
The comparison fixture resolves three constructors: CountingClock $clock, ?CountingClock $clock, and ?CountingClock $clock = null. The first two receive a CountingClock in both tested containers. The nullable type permits null as a value; without a default, it does not make the argument optional.
Only the third form changes in this comparison. Standalone Illuminate Container 11.0.0 constructs a CountingClock, while the container from Laravel 12.69.3 returns null. A constructor counter records one construction versus zero. This matters if the old constructor did work merely because the container happened to resolve it.
Run the default-value regression test
The test below uses the real Laravel container through Orchestra Testbench. Its ExportClock has no dependencies or side effects, so a failure points to resolution behavior rather than a database, route or external service.
Extract the example bundle, enter its frontend directory and run python3 scripts/verify-laravel-12.py. Docker and Python 3 are required. The runner retrieves locked dependencies during image build, then executes the test with networking disabled. It verifies the published file’s exact bytes before running it.
<?php
namespace Tests\Feature;
use Tests\TestCase;
final class ExportClock
{
}
final class ExportPreview
{
public function __construct(public ?ExportClock $clock = null)
{
}
}
final class OptionalDependencyTest extends TestCase
{
public function test_optional_dependency_keeps_its_default(): void
{
$preview = $this->app->make(ExportPreview::class);
$this->assertNull($preview->clock);
}
}
Check what the test does and does not detect
The published test passes once in Laravel 12.69.3. Removing = null from the constructor makes that same assertion fail because the container supplies an ExportClock. Changing assertNull to assertInstanceOf also fails: the unchanged optional dependency is null. The runner requires one intended assertion failure from each altered file, rather than accepting any nonzero process exit.
Explicit wiring remains significant. The comparison binds a known CountingClock with instance(), then repeats the experiment with when()->needs()->give(). Both containers return that exact object despite the constructor default. An unbound optional interface with a null default already returns null in both. The change shown here is not a claim that all nullable dependencies or all defaults behave differently.
If a service requires its clock, I recommend expressing that requirement in its constructor and testing the configured instance. If null means a preview should omit timestamps, keep the default and test the resulting output. A binding is appropriate when the application chooses a particular clock; adding one solely to hide an accidental assumption leaves the service’s contract unclear.
Use the result within its tested boundary
The captured run uses PHP 8.5.6 and PHPUnit 12.5.33. The older side tests only Illuminate Container 11.0.0, not a complete Laravel 11 application. It emits four implicit-nullability deprecations on this PHP runtime. The evidence retains them. This historical comparison is not a recommendation to deploy Laravel 11 on PHP 8.5.
As checked on 13 October 2026, Laravel’s support table lists PHP 8.2–8.5 for Laravel 12, regular bug fixes ending on 13 August 2026, and security fixes ending on 24 February 2027. The executed example covers one pinned PHP/framework combination, not that whole supported range.
The upgrade guide separately documents the removal of Carbon 2 support, the HasUuids change to UUIDv7, and SVG exclusion from the image validation rule unless explicitly allowed. None is exercised by this container fixture. For an application using those features, my recommendation is to capture its date behavior, ID assumptions and accepted upload formats in separate tests. Do not interpret a passing container assertion as evidence about those boundaries.
No starter-kit login flow, queue worker, production package graph or deployment is tested here. Use the example to recognize one dependency-resolution assumption, then write the application test that makes the affected behavior visible.
Reproduce the examples
Locked Laravel test environment, historical container comparison and two deliberate failures.
Download the source and tests (.zip)
