Skip to content

route:cache mutates live route instances, breaking subsequent optimize tasks that analyze routes #60758

Description

@hosni

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.

Image

Steps To Reproduce

Reproduction repository: https://github.com/hosni-labs/laravel-optimize-command-bug

  1. 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
  1. The app registers a custom optimize command via AppServiceProvider:
$this->optimizes(
    optimize: 'test:route-analysis',
    key: 'route-analysis',
);
  1. The test:route-analysis command iterates all routes and analyzes them:
foreach (Route::getRoutes() as $route) {
    dump($route->uri(), $route->getController(), $route->gatherMiddleware());
}
  1. Run the command directly — it succeeds:
php artisan test:route-analysis
  1. Run it as part of optimize — it fails:
php artisan optimize

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions