Julian Julian - 2 months ago 23
PHP Question

Laravel deferred service provider `provides` not being called

I have the following definition:

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\SomeClass;

class SomeProvider extends ServiceProvider
{
protected $defer = true;

public function register()
{
$this->app->bind(SomeClass::class, function ($app)
{
return new SomeClass();
});
}

public function provides()
{
die("This never gets called");
return [SomeClass::class];
}
}


And it returns an instance of
SomeClass
as expected, except that according to the documentation, if
$defer
is true then the
provides()
method should be called. No matter what I set
$defer
to, and no matter if I actually ask for an instance of
SomeClass
or not,
provides()
is never called.

The way I'm asking for an instance of the class is as follows:

App::make('SomeClass');

Answer

Short answer:
Your compiled manifest file is already compiled by framework.

On the first time when Laravel build the application (and resolves all ofservices providers in IoC) it writes a cached file called services.php (that is, the manifest file, placed in: bootstrap/cache/services.php). So, if you clear the compiled via php artisan clear-compiled command it should force framework to rebuild the manifest file and you could to note that provides method is called. On the next calls/requests provides method is not called anymore.

The sequence of framework boot is nearly like this:

//public/index.php
$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
    \Illuminate\Foundation\Http\Kernel::__construct();
    \Illuminate\Foundation\Http\Kernel::handle();
        \Illuminate\Foundation\Http\Kernel::sendRequestThroughRouter();
            \Illuminate\Foundation\Http\Kernel::bootstrap();
                \Illuminate\Foundation\Application::bootstrapWith();
                    # where $bootstrapper is a item from \Illuminate\Foundation\Http\Kernel::$bootstrappers 
                    # and $this is instance of \Illuminate\Foundation\Application
                    \Illuminate\Foundation\Application::make($bootstrapper)->bootstrap($this);

One of bootstrappers is Illuminate\Foundation\Bootstrap\RegisterProviders which invokes \Illuminate\Foundation\Application::registerConfiguredProviders() and then invokes \Illuminate\Foundation\ProviderRepository::__construct() and finally:

\Illuminate\Foundation\ProviderRepository::load()

When \Illuminate\Foundation\ProviderRepository::load() is called all services providers is registered and \Illuminate\Support\ServiceProvider::provides() are called also well.

And here is the snippet you should know (from \Illuminate\Foundation\ProviderRepository::load):

    /**
     * Register the application service providers.
     *
     * @param  array  $providers
     * @return void
     */
    public function load(array $providers)
    {
        $manifest = $this->loadManifest();

        // First we will load the service manifest, which contains information on all
        // service providers registered with the application and which services it
        // provides. This is used to know which services are "deferred" loaders.
        if ($this->shouldRecompile($manifest, $providers)) {
            $manifest = $this->compileManifest($providers);
        }

        // Next, we will register events to load the providers for each of the events
        // that it has requested. This allows the service provider to defer itself
        // while still getting automatically loaded when a certain event occurs.
        foreach ($manifest['when'] as $provider => $events) {
            $this->registerLoadEvents($provider, $events);
        }

        // We will go ahead and register all of the eagerly loaded providers with the
        // application so their services can be registered with the application as
        // a provided service. Then we will set the deferred service list on it.
        foreach ($manifest['eager'] as $provider) {
            $this->app->register($this->createProvider($provider));
        }

        $this->app->addDeferredServices($manifest['deferred']);
    }

\Illuminate\Foundation\ProviderRepository::compileManifest() is the place where your provides() method is performed.

Comments