Package Auto Discovery / Discovering Packages

Before The Dive Discovering Packages

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:

  • \Illuminate\Foundation\Bootstrap\RegisterFacades
  • \Illuminate\Foundation\Bootstrap\RegisterProviders

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 RegisterProviders bootstrapper 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.

I'm Mohamed Said, a web developer from Hurghada-Egypt. I work with the laravel core team trying to deliver the best developer experience.
Find me as @themsaid


Subscribe to mailing list