Laravel Version
13.19.0
PHP Version
8.4.23
Database Driver & Version
N/A (not database-related)
Description
RouteCacheCommand calls prepareForSerialization() directly on route instances returned from getFreshApplicationRoutes().
|
$routes = $this->getFreshApplicationRoutes(); |
|
|
|
if (count($routes) === 0) { |
|
return $this->components->error("Your application doesn't have any routes."); |
|
} |
|
|
|
foreach ($routes as $route) { |
|
$route->prepareForSerialization(); |
|
} |
Route::prepareForSerialization() intentionally removes runtime dependencies:
|
unset($this->router, $this->container); |
Those route objects remain referenced by the live router collection. Later in the same PHP process, any code that still needs to analyze routes can fail with:
LogicException: Route is not bound.
This happens because after container is unset.
Access like $route->container goes through __get():
|
public function __get($key) |
|
{ |
|
return $this->parameter($key); |
|
} |
|
} |
which calls
parameters():
|
public function parameter($name, $default = null) |
|
{ |
|
return Arr::get($this->parameters(), $name, $default); |
|
} |
And that throws when the route was never bound to a request:
|
public function parameters() |
|
{ |
|
if (isset($this->parameters)) { |
|
return $this->parameters; |
|
} |
|
|
|
throw new LogicException('Route is not bound.'); |
|
} |
This is especially visible with php artisan optimize, which runs built-in cache commands before ServiceProvider::$optimizeCommands:
[
'config' => 'config:cache',
'events' => 'event:cache',
'routes' => 'route:cache',
'views' => 'view:cache',
...ServiceProvider::$optimizeCommands,
]
If a package registers an optimize task that analyzes routes (for example, to cache generated metadata), that task can fail after route:cache has already run in the same optimize command.
Steps To Reproduce
Reproduction repository: https://github.com/hosni-labs/laravel-optimize-command-bug
- Clone the reproduction repository:
git clone https://github.com/hosni/laravel-optimize-command-bug.git
cd laravel-optimize-command-bug
composer install
cp .env.example .env
php artisan key:generate
- The app registers a custom optimize command via
AppServiceProvider:
$this->optimizes(
optimize: 'test:route-analysis',
key: 'route-analysis',
);
- The
test:route-analysis command iterates all routes and analyzes them:
foreach (Route::getRoutes() as $route) {
dump($route->uri(), $route->getController(), $route->gatherMiddleware());
}
- Run the command directly — it succeeds:
php artisan test:route-analysis
- Run it as part of
optimize — it fails:
Expected: optimize completes successfully, including the route-analysis task.
Actual: The route-analysis task fails with:
LogicException: Route is not bound.
The failure occurs on any route when gatherMiddleware() is called after route:cache has already executed in the same process.
Workaround:
php artisan optimize --except=route-analysis
php artisan test:route-analysis
Proposed fix: Clone each route before calling prepareForSerialization(), and build the route cache file from the cloned/prepared collection so live route instances are not mutated.
Laravel Version
13.19.0
PHP Version
8.4.23
Database Driver & Version
N/A (not database-related)
Description
RouteCacheCommandcallsprepareForSerialization()directly on route instances returned fromgetFreshApplicationRoutes().framework/src/Illuminate/Foundation/Console/RouteCacheCommand.php
Lines 56 to 64 in 3093ff3
Route::prepareForSerialization()intentionally removes runtime dependencies:framework/src/Illuminate/Routing/Route.php
Line 1560 in 3093ff3
Those route objects remain referenced by the live router collection. Later in the same PHP process, any code that still needs to analyze routes can fail with:
This happens because after
containeris unset.Access like
$route->containergoes through__get():framework/src/Illuminate/Routing/Route.php
Lines 1569 to 1573 in 3093ff3
which calls
parameters():framework/src/Illuminate/Routing/Route.php
Lines 432 to 435 in 3093ff3
And that throws when the route was never bound to a request:
framework/src/Illuminate/Routing/Route.php
Lines 483 to 490 in 3093ff3
This is especially visible with
php artisan optimize, which runs built-in cache commands beforeServiceProvider::$optimizeCommands:[ 'config' => 'config:cache', 'events' => 'event:cache', 'routes' => 'route:cache', 'views' => 'view:cache', ...ServiceProvider::$optimizeCommands, ]If a package registers an optimize task that analyzes routes (for example, to cache generated metadata), that task can fail after
route:cachehas already run in the sameoptimizecommand.Steps To Reproduce
Reproduction repository: https://github.com/hosni-labs/laravel-optimize-command-bug
git clone https://github.com/hosni/laravel-optimize-command-bug.git cd laravel-optimize-command-bug composer install cp .env.example .env php artisan key:generateAppServiceProvider:test:route-analysiscommand iterates all routes and analyzes them:optimize— it fails:Expected:
optimizecompletes successfully, including theroute-analysistask.Actual: The
route-analysistask fails with:The failure occurs on any route when
gatherMiddleware()is called afterroute:cachehas already executed in the same process.Workaround:
Proposed fix: Clone each route before calling
prepareForSerialization(), and build the route cache file from the cloned/prepared collection so live route instances are not mutated.