The Optimize Command and Route Caching

The optimize command has various aspects to improve the performance of the Laravel application. The command provides two flags. Firstly, the –force flag can indicate that the compiled class file should be written (by default the compiled class is not written when the application is in debug mode). On the other hand, the –psr flag can be set to indicate that the composer should not create an optimized class map loader (class maps are generally better for performance reasons).

We can write the compiled files cache to the bootstrap/cache/compiled.php cache file. The compiled and written files are in any cache files that meet the following criteria:

The following examples demonstrate how to use the optimize command:

# Generate a compiled class file with default options.
artisan optimize

# Generate a compiled class file without optimizing the Composer

# autoload file.
artisan optimize –psr

# Generate a compiled class file on a development or debug machine.
artisan optimize –force

Laravel Route Caching

Using the route cache will drastically decrease the amount of time it takes to register all of your application’s routes. In some cases, your route registration may even be up to 100x faster.

public function boot()
{
$this->setRootControllerNamespace();

if ($this->app->routesAreCached()) {
$this->loadCachedRoutes();
} else {
$this->loadRoutes();

$this->app->booted(function () {
$this->app[‘router’]->getRoutes()->refreshNameLookups();
$this->app[‘router’]->getRoutes()->refreshActionLookups();
});
}
}

Once the controller namespace is set, the boot method checks to see if the routes have been cached. So, let’s explore what happens both with and without the route cache.

Without Route Cache
If your routes are not cached, $this->loadRoutes() ends up calling the app/Providers/RouteServiceProvider.php map() function which maps Api routes (mapApiRoutes) and Web routes (mapWebRoutes). For the sake of simplicity, comment out all routes in the routes/api.php file so we will only be dealing with routes in the routes/web.php file. mapWebRoutes pulls in everything from routes/web.php. On the other hand, for each entry in routes/web.php Laravel parses the entry and converts it into an Illuminate/Routing/Route object. Moreover, this conversion actually requires alias resolving, determining middleware and route grouping, resolving the correct controller action and identifying the HTTP action and parameter inputs. Finally, this is done for all routes which are grouped into a final Illuminate/Routing/RouteCollection.

Lastly, $this->app[‘router’]->getRoutes()->refreshNameLookups() and $this->app[‘router’]->getRoutes()->refreshActionLookups() run once the app finishes booting. These are called on the Illuminate/Routing/RouteCollection in case any new route names were generated or if any actions were overwritten by new controllers. This could be caused by other service providers later in the boot cycle.

With Route Cache
When you run PHP artisan routes: cache, an instance of Illuminate/Routing/RouteCollection is built. After being encoded, the serialized output is written to bootstrap/cache/routes.php.

Here we will decode and unserialize the RouteCollection instance that holds all of the route information for an application. This allows us to instantaneously load the entire route map into the router.

In other words, our application no longer has to parse and convert entries from the routes files into Illuminate/Routing/Route objects in an Illuminate/Routing/RouteCollection. Likewise, the application also does not call refreshNameLookups or refreshActionLookups. Thus, be sure to always regenerate your route cache if you add/modify routes or add service providers that will add/modify your routes.

In Illuminate\Foundation\Support\Providers\RouteServiceProvider.php, let’s modify the boot() function to capture time like this:

public function boot()
{
$this->setRootControllerNamespace();

// Initialize a global route load time variable
$this->app->routeLoadTime = 0;

if ($this->app->routesAreCached()) {
$time_start = microtime(true);
$this->loadCachedRoutes();
$this->app->routeLoadTime += microtime(true) – $time_start;
} else {
$time_start = microtime(true);
$this->loadRoutes();
$this->app->routeLoadTime += microtime(true) – $time_start;

$this->app->booted(function () {
$time_start = microtime(true);
$this->app[‘router’]->getRoutes()->refreshNameLookups();
$this->app[‘router’]->getRoutes()->refreshActionLookups();
$this->app->routeLoadTime += microtime(true) – $time_start;
});
}

$this->app->booted(function() {
dd($this->app->routeLoadTime);
});
}

If routes are not cached, we immediately measure the time of loadRoutes(). When the app is booted, we add the time it takes to run refreshNameLookups and refreshActionLookups. Lastly, we dump out the total captured time and kill the request.
If routes are cached, we measure the time it takes to call loadCachedRoutes(). However, notice that loadCachedRoutes() defers its work until the app is booted.
protected function loadCachedRoutes()
{
$this->app->booted(function () {
require $this->app->getCachedRoutesPath();
});
}

Let’s modify loadCachedRoutes to capture that time:
protected function loadCachedRoutes()
{
$this->app->booted(function () {
$time_start = microtime(true);
require $this->app->getCachedRoutesPath();
$this->app->routeLoadTime += microtime(true) – $time_start;
});
}

Now when we use the cache we are measuring both the time to register the booted callback and the time it takes to run that callback (require $this->app->getCachedRoutesPath();), which turns the cached routes back into a Illuminate/Routing/RouteCollection. At last, we dd() the output.

Hung Nguyen

Share with us your business goals and product ideas, we will support you to develop, scale and succeed together.