Laravel has many undocumented features, and unfortunately, unless you're dedicating large chunks of time to digging through the source code and tracking what's going on, these features are likely to pass you by. Fortunately, I have done, and continue to do, exactly that.
Within the service container section of the Laravel documentation, you'll find an entry called "Resolving" , which briefly covers the two options that Laravel provides for resolving dependencies, the dependency injection approach and the service locator approach. For the purpose of this article, we'll be focusing on the latter.
Service Locator = Bad?
The service locator approach is widely considered to be an anti-pattern, and while I personally avoid using it, and recommend that others do the same, everyone is free to do as they please. I care far more about helping people further their understanding of Laravel, than I do about encouraging a particular style of development.
Now, the documentation itself does not refer to this as "service location" or "service locator", most likely because of the stigma surrounding the term, but instead simply calls the entry "The Make Method" , and talks primarily about the helper methods you can use to resolve a particular class or service. Though it doesn't cover them all or their differences.
There are between 4 and 17 different ways of manually resolving a service in Laravel, with most of those being different ways to call a particular method. Ultimately, regardless of how you do it, the same piece of code does the work, sometimes with additional overhead.
Where Services Are Resolved
Before we look at the different approaches, we should first look at where exactly Laravel is resolving services.
The actual heavy lifting is handled by a
protected
method on the
IlluminateContainerContainer
class, called
resolve
. The method itself is quite long, so instead of
showing its code,
here
is a link
.
While this isn't something that will ever come up for most of you, it's important to consider that how this
method is called changes what it does, ever so slightly. Obviously you can't manually call it, as it's
protected
, without subclassing
IlluminateContainerContainer
, but there is already a subclass within
Laravel. The
IlluminateFoundationApplication
class, which not only subclasses the
original container, but also overrides the
resolve
method
, whose source code
I can actually show you.
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
$this->loadDeferredProviderIfNeeded($abstract = $this->getAlias($abstract));
return parent::resolve($abstract, $parameters, $raiseEvents);
}
The underlying container class lacks the concept of service providers, which is something provided by the
application class itself, so deferred providers are logically only loaded when resolving using the
IlluminateFoundationApplication
class. Fortunately, unless you've done
something particularly odd, that'll always be the case. If you're worried that it's not, don't be, that's not
something you could accidentally do.
The biggest takeaway is that no matter how you choose to resolve a service,
the
method will be called through the overridden proxy method,
IlluminateContainerContainer
resolve
.
So with that covered, let's look at the different approaches.
IlluminateFoundationApplication
resolve
The
Application
Class
As I mentioned above, you're almost guaranteed to be working with an instance of
IlluminateFoundationApplication
when resolving services, and this class
itself provides 3 different methods for doing so.
The
make
Method
This is the method mentioned in the documentation, and it's almost definitely the one you'll have
encountered, assuming you've done this before. This
method
actually overrides
,
which is in turn an implementation of
IlluminateContainerContainer
make
.
IlluminateContractsContainerContainer
make
Just like with the
resolve
method, this one also loads
deferred providers, and then proxies a call to the parent method on
IlluminateContainerContainer
.
public function make($abstract, array $parameters = [])
{
$this->loadDeferredProviderIfNeeded($abstract = $this->getAlias($abstract));
return parent::make($abstract, $parameters);
}
This is nothing but a proxy method for
.
IlluminateContainerContainer
resolve
public function make($abstract, array $parameters = [])
{
return $this->resolve($abstract, $parameters);
}
Calling this method will do the following.
- Load Deferred Providers
- Call
IlluminateContainerContainermake - Which calls
IlluminateFoundationApplicationresolve - Which proxies the call to
IlluminateContainerContainerresolve
Since you're almost always going to want deferred classes to also be resolved, this is your best option, though how you retrieve the class instance is where additional complexity comes in. This is also the approach that almost all of Laravel's internal code uses.
The
makeWith
Method
This method is mentioned in the docs, though it mostly still exists to provide backwards compatibility and avoid
breaking changes, because it's a completely pointless method. Firstly, this method doesn't exist in the
IlluminateFoundationApplication
class, as it's inherited directly
from the
IlluminateContainerContainer
class. And secondly, it provides
absolutely nothing that isn't already provided by
.
That's why all it does is call that method.
IlluminateContainerContainer
make
public function makeWith($abstract, array $parameters = [])
{
return $this->make($abstract, $parameters);
}
The documentation would have you believe that this method allows you to provide custom parameters for the
resolution process, which it does, but so does the
make
method. Their signatures are identical.
This means that calling this method does the following.
This content is a correction from a previous version.
- Call
IlluminateFoundationApplicationmake - Which calls
IlluminateContainerContainermake - Which then calls
IlluminateFoundationApplicationresolve - Which proxies the call to
IlluminateContainerContainerresolve
This method is actually a marginally less efficient version of
, as the internal call to
IlluminateFoundationApplication
makemake
is actually calling the version from
IlluminateFoundationApplication
, because of how PHP deals with
overrides and inheritance. This means that it still loads the deferred providers, as it's nothing
but a proxy method.
If I'm being totally honest with you, I think the main reason this method still exists, besides the
backwards compatibility element, is that there are 3 places within the Laravel source where it's used.
All of which exist within the
IlluminateDatabaseConcernsBuildsQueries
trait, and can be found
here
,
here
,
and
here
.
See Original
- Call
IlluminateContractsContainerContainermake - Which calls
IlluminateFoundationApplicationresolve - Which proxies the call to
IlluminateContainerContainerresolve
That does mean that it is technically more efficient, as there's no overhead from loading deferred service providers, but honestly, that's negligible. Deferred providers are only loaded once, and it's unlikely that your attempt at manual resolution is going to be the first call to trigger that process.
While I'm fairly confident
that this method only exists for backwards compatibility, there's a slight chance that it has also
been
kept around to provide a way to manually resolve, without loading deferred
providers. I
say
this, because the only places that I can find, where it's used internally, is on the
IlluminateDatabaseConcernsBuildsQueries
class, and can be found
here
,
here
,
and
here
. All of which are places where it would make sense to avoid loading
deferred providers, but I have no proof either way.
The
get
Method
Much like with
makeWith
, the
get
method doesn't exist on the
IlluminateFoundationApplication
class, but is inherited directly from the
IlluminateContainerContainer
class.
This
method
does a little bit extra on top of wrapping a call to
IlluminateContainerContainer::resolve
.
public function get(string $id)
{
try {
return $this->resolve($id);
} catch (Exception $e) {
if ($this->has($id) || $e instanceof CircularDependencyException) {
throw $e;
}
throw new EntryNotFoundException($id, is_int($e->getCode()) ? $e->getCode() : 0, $e);
}
}
Unlike with the other methods, this one wraps the call to
resolve
in a
try/catch, throwing
an instance of
IlluminateContainerEntryNotFoundException
, which only ever
appears here. By now you're probably wondering why this method even exists? It doesn't allow you to provide
custom parameters for the resolution, and it throws a new kind of exception. Well, it exists because of
something called
PSR-11
, which provides a
common interface for dependency injection containers. The spec itself requires the presence of a method like
this,
with this signature, that throws an instance of
PsrContainerNotFoundExceptionInterface
if the requested service could
not be found.
The method is an implementation of
,
which is an override of
IlluminateContractsContainerContainer
get
,
an interface that comes from the
PSR-11 package
. It's overridden to
provide a custom docblock so that IDEs and static analysis tools have a better understanding of what the
method does.
PsrContainerContainerInterface
get
This method is arguably the simplest, as calling it does the following.
- Calls
IlluminateFoundationApplicationresolve - Which proxies the call to
IlluminateContainerContainerresolve
Getting an Instance of
Application
As we've established above, there's very little difference between calling
make
,
makeWith
, and
get
, at least not in any meaningful way. The biggest
difference is how you go about getting an instance of the class.
There are technically 4 different ways to go about it, though only 2 are viable, with the other 2 being ridiculous. I only include them here for completeness' sake.
Dependency Injection
Let's get this one out of the way. From a purely technical perspective, you could leverage Laravel's dependency
injection to have an instance of the
IlluminateFoundationApplication
class
injected, so you could manually resolve your service(s). Something like this.
private Application $app;
public function __construct(Application $app)
{
$this->app = $app;
}
public function handle()
{
$this->app->make(MyService::class)->doSomething();
}
This works, but it's a ridiculous solution. It's the Laravel equivalent of opening Google Chrome, typing Google into the address bar, hitting enter, then clicking on Google in the search results, all to get to a search input that lets you search Google.
DO NOT DO THIS
Just in case I haven't made it clear enough, this is not a good solution, and should absolutely not be done. If you do this, I will find you.
If you're already leveraging Laravel's dependency injection, just use that instead of resorting to using a service locator.
private MyService $service;
public function __construct(MyService $service)
{
$this->service = $service;
}
public function handle()
{
$this->service->doSomething();
}
It should be noted that a lot of Laravel's internal classes do this very thing, but I don't think either of us has the time for me to get into that!
The
App
Facade
The next way to go about this is to use the
IlluminateSupportFacadesApp
facade, which actually lets you completely circumvent the retrieval of an instance, because it's handled
internally. However, I do suggest not using this approach, and that's got nothing to do with my personal
opinion on facades, and instead, it has everything to do with how facades work.
Let's assume that the following code is executed.
$service = App::make(MyService::class);
Assuming that this is the first time this service is being resolved, and the first time the facade is being used, both of which are highly likely, the following will happen.
-
First, the
makemethod doesn't exist, so instead the magic method__callStaticonIlluminateSupportFacadesFacadeis called. -
This calls the sibling method
getFacadeRoot. -
Which in turn calls
resolveFacadeInstance, passing in the result of callinggetFacadeAccessor, which isapp, the name that the class is bound with in the container. -
This method uses the static property
app, which contains an instance of theIlluminateFoundationApplicationclass, and treats it like an array, accessing the entry keyedappin this case. -
Because the
IlluminateFoundationApplicationclass implementsArrayAccessthroughIlluminateContainerContainer, the methodoffsetGetfromIlluminateContainerContaineris called. -
This method then calls
make, to retrieve an instance of the serviceapp. But, due to how PHP handles subclassing and inheritance, the method that's called is the one onIlluminateFoundationApplication. -
Which is just a proxy for the method
makeonIlluminateContainerContainer. -
This then proxies the call to
resolve, which will return the service bound asapp, which will (should) be an instance ofIlluminateFoundationApplication. (In fact, it's the very instance that these methods are being called on) -
We're now back on
IlluminateSupportFacadesFacade, where the__callStaticmethod continues by callingmakeon the retrieved instance ofIlluminateFoundationApplication. -
As we already know, this then proxies a call to
makeonIlluminateContainerContainer. -
Which proxies the call to the sibling method
resolve, which will hopefully, finally return an instance ofMyService.
As you can see, this whole process is somewhat comical and arguably unnecessary. Again, it's a viable option, and at least this approach has the sense to obscure its issues behind implementation logic. In fact, I'd bet that a lot of you reading this aren't aware of how facades work internally, and there's a very real possibility that I've just ruined them for you.
But the Docs...
I'm aware that this particular approach is mentioned in the docs, but I'm not sure why. It's even mentioned as an alternative to the next approach, and while that's vaguely true, there's a world of difference between the two.
The
app
Helper Function
The
app
helper function is one of Laravel's helpers, and
it lets you either resolve a service from the container, or return the container instance itself. It all
depends on how you call it.
$service = app(MyService::class);
$service = app()->make(MyService::class);
Let's look at the two ways you call this function. First, here's what happens if you call it with no arguments,
or a single
null
value.
-
It calls
, which in almost all cases, would return an instance ofIlluminateContainerContainergetInstanceIlluminateFoundationApplication. It then returns it.
But if you call it with a non-null value, and optionally an array of parameters, just like with the
make
method, the following happens.
-
It calls
, which in almost all cases would return an instance ofIlluminateContainerContainergetInstanceIlluminateFoundationApplication. -
It then calls
makeon the retrieved instance, passing in the value you provided and any parameters you provided. -
This of course proxies the call to
makeonIlluminateContainerContainer. -
Which in turn calls
resolveonIlluminateFoundationApplication. -
Which is again proxied to
resolveonIlluminateContainerContainer, whose return value is returned.
Calling
Container
getInstance
Container
getInstance
This is arguably the simplest and most direct way to get an instance of the
IlluminateFoundationApplication
class. If you can get past the fact that
this makes it abundantly clear that the container is a singleton, you'll be fine.
$service = Container::getInstance()->make(MyService::class);
The only downside to this approach is that if someone has done something a bit odd, this method could return
a different instance than the one you're expecting, which also means the
app
approach above would be broken.
One of the first things
that the
IlluminateFoundationApplication
class does once a new instance
is created, is register some base bindings in the
method. This method
performs a number of important tasks, but only three are relevant to the current topic.
IlluminateFoundationApplication
registerBaseBindings
-
It calls
, passing inIlluminateContainerContainersetInstancethis, to set the singleton instance. -
It binds
thisasIlluminateFoundationApplication. -
It binds
thisasIlluminateContainerContainer.
Now,
is a
IlluminateContainerContainer
setInstancepublic static
method that accepts an instance of
IlluminateContainerContainer
, so it could, in theory, be called
outside of the normal boot phase with any instance. This is extremely unlikely, and if it does
happen, you've got much bigger issues than this. I am only including the warning here because it's something
that you should be aware of.
So What Should I Use?
Honestly, I think you should just use dependency injection and skip this whole service locator business. I do, however, recognise that it's unrealistic to expect everyone to do things the way I think they should be done. I don't know every detail of the situation you're in, and ultimately, you need to make the call to get the experience required for you to make the best call you can in the future.
My aim with this article was to show you that not all options are created equal, and what seems to be a simple alternative way of doing something isn't always what it seems. Hopefully this provides you with enough detail, and enough of an idea of what's going on behind the scenes, that you can answer this question for yourself.
The Hidden Parts of Laravel
This article was slightly different from my previous two, as it focuses a bit more on the hidden underlying aspect than the undocumented. It is also nowhere near the end of possible topics that fall into the category of hidden and/or undocumented. I'll be covering more of those in coming articles, but for now I hope that you find this useful, whether as a solution to a problem you're having, or as a way to learn more about Laravel.