Laravel Queues in Action (2nd edition) is now available!

Package Auto-discovery in Laravel

Updated: Jan 6, 2019 — 4 min Read

Before getting into how Laravel auto-discovers package providers and facades, let's first have a shallow dive into the concept of packages in PHP:

A package is a piece of code that you can reuse in multiple projects, for example spatie/laravel-analytics is a piece of code that you can use in any of your laravel projects to have an easy way to retrieve data from Google Analytics, such package is hosted on GitHub and is well maintained by the fine folks at Spatie and they constantly release new updates and bug fixes for their packages, if you use this package in your project you'd want to have these updates and fixes once they're released and not have to worry about copying the new code from Github, for that Composer was created.

Composer is a tool for dependency management in PHP. It allows you to declare the libraries your project depends on and it will manage (install/update) them for you. -- getcomposer.org

Laravel is shipped with a composer.json file where you can require more packages to extend the functionality of your application, all you have to do is include the package you want under the require or require-dev section of that file and then run composer update:

{
    "require": {
        "spatie/laravel-analytics": "3.*",
    },
}

You can also use the following command that'll have the same effect:

composer require spatie/laravel-analytics

At this point Composer did its job and pulled the version of that package that you want and downloaded it to your vendor directory, now all the classes and files of this package is loaded into your project and you can use it right away, and every once in a while you can run composer updateagain and Composer will fetch any updates applied to this package and automatically update the files in your projects vendor directory.

Some Laravel packages require a few extra steps for it to be usable in a Laravel project:

If you take a look at the installation instructions of Spatie's package you'll find that you have to register a service provider and a Facade in your project configuration before you're good to go, this step was identified by Taylor Otwell as un-necessary so he teamed up with Dries Vints and came up with a way to auto-register Service Providers and Facades whenever you require a new package and also remove them if you decide to remove the package at any time.

Check out Taylor's announcement of the feature on Medium.

What are service providers and facades?

A service provider is responsible for binding things into Laravel's service container and informing Laravel where to load package resources such as views, configuration, and localization files. -- laravel.com Docs

You can read more about the Service Providers on the official documentation.

Facades provide a "static" interface to classes that are available in the application's service container -- laravel.com Docs

You can read more about Facades on the official documentation.

While finding and installing/updating different packages, Composer fires multiple events that you can subscribe to and run a piece of code of your own or even a command-line executable, one of the interesting events is called post-autoload-dump which is fired directly after composer generates the final list of classes it's going to autoload in your project, at this point Laravel has access to all the classes already and the application is ready to work with the all the package classes loaded into it.

What happens is that Laravel subscribes to this event from within the main composer.json file:

"scripts": {
    "post-autoload-dump": [
        "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
        "@php artisan package:discover"
    ]
}

First it calls a postAutoloadDump() static method, this method handles clearing any cached services or previous discovered packages, the other thing is that it runs the package:discover artisan command, and this is where all the magic happens.

Package Discovery

The Illuminate\Foundation\Console\PackageDiscoverCommand calls the build() method on the Illuminate\Foundation\PackageManifest class, that class is where Laravel discovers installed packages.

The PackageManifest is registered into the container early in the application bootstrap, exactly from within Illuminate\Foundation\Application::registerBaseServiceProviders(), this method runs directly after a new instance of the Laravel Application is created.

Inside the build() method Laravel looks for a vendor/composer/installed.json file, it's generated by composer and holds a complete map of all the composer.json files content of all the libraries installed by composer, Laravel maps over the content of this file and searches for packages that contain an extra.laravel section:

"extra": {
    "laravel": {
        "providers": [
            "Barryvdh\\Debugbar\\ServiceProvider"
        ],
        "aliases": {
            "Debugbar": "Barryvdh\\Debugbar\\Facade"
        }
    }
}

It collects the content of that section first, and then look into the main composer.json file under the extra.laravel.dont-discover to see if you decided not to auto discover certain packages or all packages:

"extra": {
    "laravel": {
        "dont-discover": [
            "barryvdh/laravel-debugbar"
        ]
    }
}

You can add `*` into the array to instruct laravel to stop auto-discovering entirely.

So now Laravel collected info about the packages

Yes, once it gets all the information it needs it'll write a PHP file in bootstrap/cache/packages.php:

<?php return array (
  'barryvdh/laravel-debugbar' =>
  array (
    'providers' =>
    array (
      0 => 'Barryvdh\\Debugbar\\ServiceProvider',
    ),
    'aliases' =>
    array (
      'Debugbar' => 'Barryvdh\\Debugbar\\Facade',
    ),
  ),
);

Packages registration

Laravel has two bootstrappers that are used while the HTTP or the Console kernels are booting up:

The first one uses the Illuminate\Foundation\AliasLoader to load all the facades into the application, what changed now is that Laravel will look into the packages.php generated file and extract all the aliases that packages want Laravel to auto-register and register those ones as well. It uses the PackageManifest::aliases() method to collect this information.

// in RegisterFacades::bootstrap()

AliasLoader::getInstance(array_merge(
    $app->make('config')->get('app.aliases', []),
    $app->make(PackageManifest::class)->aliases()
))->register();

As you can see, the aliases loaded from your config/app.php file is merged with aliases loaded from the PackageManifest class.

Similarly Laravel registers the service providers while booting up, the RegisterProvidersbootstrapper calls the registerConfiguredProviders() method of Foundation\Application, and in there Laravel collects all package providers that should be auto-registered and registers them as well.

$providers = Collection::make($this->config['app.providers'])
                ->partition(function ($provider) {
                    return Str::startsWith($provider, 'Illuminate\\');
                });

$providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);

Here we inject the auto-discovered providers between Illuminate providers and any other providers that might be in your config file, this way we ensure you can override your package providers by re-registering them in your config file and also that Illuminate components will be all loaded before attempting to load any other providers.

Hey! 👋 If you find this content useful, consider sponsoring me on GitHub.

You can also follow me on Twitter, I regularly post about all things Laravel including my latest video tutorials and blog posts.

By Mohamed Said

Hello! I'm a former Laravel core team member & VP of Engineering at Foodics. In this publication, I share everything I know about Laravel's core, packages, and tools.

You can find me on Twitter and Github.

This site was built using Wink. Follow the RSS Feed.