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');
Short answer:
Your compiled manifest file is already compiled by framework.
On the first time when Laravel build the application (and resolves all of services providers in IoC container)
it writes to cached file named 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.
After doing my own testing, it would seem this is an issue (maybe "issue" is a strong word in this case).
If you register something in a service provider which has the name of a class, Laravel is just going to return that class and disregard whatever is in the service provider. I started doing the same thing as you did....
protected $defer = true;
public function register()
{
$this->app->bind(SomeClass::class, function ($app)
{
return new SomeClass();
});
}
public function provides()
{
dd('testerino');
}
$test = \App::make('App\SomeClass');
And $test
is an instance of SomeClass
. However if I make the following change...
$this->app->bind('test', function ($app) { ... }
And use
$test = \App::make('test');
Then it hits the deffered
function and outputs the text testerino
.
I think the issue here is that Laravel knows you are just trying to grab a class. In this instance, there is no reason to register what you are trying to register with the container, you aren't doing anything except telling Laravel to make an instance of App\SomeClass
when it should make an instance of App\SomeClass
.
However, if you tell Laravel you want an instance of App\SomeClass
when you call App::make('test')
, then it actually needs to bind that class to test
so then I think it starts to pay attention to the service provider.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With