Facades are singletons 🤯

If you know a little about the service container, you also may know that you can explicitly bind things in the container as a singleton. What you might not know, is that they all actually are singletons.

Before we begin, let's quickly go through how the service container works.

NB: All code examples uses PHP 8 syntax

How the service container works

In laravel you often inject classes in controllers like this

1public function show(MyAwesomeClass $awesome): View
2{
3}

For this to work laravel needs to know how to resolve the class MyAwesomeClass. If the MyAwesomeClass constructor does not take any parameters or they are all resolvable using the container then laravel will just magically new up a class instance. For the sake of argument let's say that our class constructor requires a parameter to create a new instance of the class.

1<?php
2namespace App\Foobar;
3 
4class MyAwesomeClass
5{
6 public $name;
7 
8 public function __construct(string $name): void
9 {
10 $this->name = $name;
11 }
12 
13 public function setName(string $name): self
14 {
15 $this->name = $name;
16 return $this;
17 }
18}

We would then need to set this up in the container. This can be done as follows in a service provider.

1$this->app->bind(MyAwesomeClass::class, function ($app) {
2 return new MyAwesomeClass('foo');
3});

Here we tell the service container that it should new up an instance of the class with the parameter $name = 'foo' if it is requested from the container.

The service container will always make a new instance of the class each time we call it. This might not always be what we want. In some cases it might make more sense to always get the same exact instance. Examples of this can be the Request or Session class, as we want to be able to change these in one part of our app, and have the change reflected elsewhere (like changing the request in a middleware). We can easily change our binding from before so that it always returns the same MyAwesomeClass instance

1$this->app->singleton(MyAwesomeClass::class, function ($app) {
2 return new MyAwesomeClass(['foo' => 'bar']);
3});

A quick recap. With the first example we will get a new class instance of the MyAwesomeClass each time.

1$class1 = app(MyAwesomeClass::class);
2$class1->setName('Foobar');
3$class2 = app(MyAwesomeClass::class);
4 
5echo $class1->name; //shows Foobar
6echo $class2->name; //shows foo

And with the singleton we get the same class instance each time

1$class1 = app(MyAwesomeClass::class);
2$class1->setName('Foobar')
3$class2 = app(MyAwesomeClass::class);
4 
5echo $class1->name; //shows Foobar
6echo $class2->name; //shows Foobar

How facades work

So now that you understand the basics of how the service container works, let's see how a Facade works. A facade has just one method: getFacadeAccessor()

1<?php
2namespace App\Facades;
3 
4use Illuminate\Support\Facades\Facade;
5 
6class MyAwesomeClassFacade extends Facade
7{
8 /**
9 * Get the registered name of the component.
10 *
11 * @return string
12 */
13 protected static function getFacadeAccessor() {
14 return \App\Foobar\MyAwesomeClass::class;
15 }
16}

Here we tell the facade that it is uses the MyAwesomeClass from before. That means that when we use the facade, it knows that it should return a MyAwesomeClass instance.

1$myclass = MyAwesomeClassFacade::setName('foobar');

This will make a new instance of MyAwesomeClass and then automatically run the setName()method. This is done using the __callStatic()method in the base Facade class.

1public static function __callStatic($method, $args)
2{
3 $instance = static::getFacadeRoot();
4 
5 if (! $instance) {
6 throw new RuntimeException('A facade root has not been set.');
7 }
8 
9 return $instance->$method(...$args);
10}

The getFacadeRoot() method calls the resolveFacadeInstance() method that is in charge of getting the correct class instance from the container.

Why facades return the same class instance again and again (singleton)

I hope you now have a basic understanding of how classes are resolved in laravel. No let's see why facades are indeed singletons. As I said earlier, the resolveFacadeInstance() method is responsible for getting the correct class from the service container. Let's now have a look at what it does.

1protected static function resolveFacadeInstance($name)
2{
3 if (is_object($name)) {
4 return $name;
5 }
6 
7 if (isset(static::$resolvedInstance[$name])) {
8 return static::$resolvedInstance[$name];
9 }
10 
11 if (static::$app) {
12 return static::$resolvedInstance[$name] = static::$app[$name];
13 }
14}

Let's break it down further, and we will start at the end.

1protected static function resolveFacadeInstance($name)
2{
3 if (is_object($name)) {
4 return $name;
5 }
6 
7 if (isset(static::$resolvedInstance[$name])) {
8 return static::$resolvedInstance[$name];
9 }
10 
11 if (static::$app) {
12 return static::$resolvedInstance[$name] = static::$app[$name];
13 }
14}

The very last part of this code is what actually gets the instance back from the container. static::$app[$name].

$app is the container and the $name is the facade accessor from earlier. Now comes the singleton part when Laravel adds the returned value to a static array called $resolvedInstance, and if we look at the code before this, we will see that Laravel checks if the instance is already present in that array.

1protected static function resolveFacadeInstance($name)
2{
3 if (is_object($name)) {
4 return $name;
5 }
6 
7 if (isset(static::$resolvedInstance[$name])) {
8 return static::$resolvedInstance[$name];
9 }
10 
11 if (static::$app) {
12 return static::$resolvedInstance[$name] = static::$app[$name];
13 }
14}

Since this array is static, it will remember it's content throughout the entire request. That means that if we have resolved a facade once, then the next time we resolve it we get the same instance! 🤯

How to work around this!

Laravel itself is actually quite good at working around this. Let's take the example of the Http client. If we did this...

1$response1 = Http::withHeaders([
2 'X-First' => 'foo',
3 'X-Second' => 'bar'
4])->post('http://example.com/users', [
5 'name' => 'Taylor',
6]);
7 
8$response2 = Http::get('http://example2.com/users');

...it would be easy to assume that we would be using the same Http client instance, and yes, in fact we are! However laravel does a clever trick here with the methods. These withheaders(), post(), get(), etc. don't actually exist on the underlying class. The class that the Http facade refers to is a Factory class which has the following method instead.

1public function __call($method, $parameters)
2{
3 if (static::hasMacro($method)) {
4 return $this->macroCall($method, $parameters);
5 }
6 
7 return tap($this->newPendingRequest(), function ($request) {
8 $request->stub($this->stubCallbacks);
9 })->{$method}(...$parameters);
10}

So when you call a method that does not exist like get() it will first check if there is a macro with that name. If not it will create a new PendingRequest class instance, and return that.

1protected function newPendingRequest()
2{
3 return new PendingRequest($this);
4}

An easier fix

If you need to ensure that you always get a new instance when using the facade, you can do this little trick (inspired by Spatie) on the class you are resolving by adding this.

1public function new()
2{
3 return new static();
4}

You can then use the facade like this, to always get a new instance

1$myclass = MyAwesomeClassFacade::new();
2$myclass->setName('foobar');

This will call the new() method which will ensure that you get a new instance.

Note: Spatie has taken the facade completely out of the equation, and simply allow using the class itself as if it was a facade.

1public static function new()
2{
3 return new static();
4}

Notice the static keyword. That means we can run it one the class directly.

1$myclass = MyAwesomeClass::new();
2$myclass->setName('foobar');

Wrapping up

I hope this was helpful and that you learned something new. This post was inspired by: Laravel GitHub issue #1088