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 IlluminateContainerContainer resolve method will be called through the overridden proxy method, IlluminateFoundationApplication resolve . So with that covered, let's look at the different approaches.

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 IlluminateContainerContainer make , which is in turn an implementation of 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.

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 IlluminateContainerContainer make . That's why all it does is call that method.

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 IlluminateFoundationApplication make
  • Which calls IlluminateContainerContainer make
  • Which then calls IlluminateFoundationApplication resolve
  • Which proxies the call to IlluminateContainerContainer resolve

This method is actually a marginally less efficient version of IlluminateFoundationApplication make , as the internal call to make 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 IlluminateContractsContainerContainer make
  • Which calls IlluminateFoundationApplication resolve
  • Which proxies the call to IlluminateContainerContainer resolve

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 IlluminateContractsContainerContainer get , which is an override of PsrContainerContainerInterface 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.

This method is arguably the simplest, as calling it does the following.

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.

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.

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.

Calling 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 IlluminateFoundationApplication registerBaseBindings method. This method performs a number of important tasks, but only three are relevant to the current topic.

  1. It calls IlluminateContainerContainer setInstance , passing in this , to set the singleton instance.
  2. It binds this as IlluminateFoundationApplication .
  3. It binds this as IlluminateContainerContainer .

Now, IlluminateContainerContainer setInstance is a public 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.

If you'd like to stay updated with my latest articles, tutorials, projects, and thoughts on everything Laravel and PHP, you can subscribe to my newsletter.

Got thoughts, questions, or just want to chat? I'm always up for a good conversation— get in touch, or find me on BlueSky, X or Discord