Nika Kurashvili
Nika Kurashvili

Reputation: 6472

why does laravel's facade behave like a singleton?

I know that laravel uses facades. but basically I can't wrap my head around it why they use it. Let's take a look at maybe File facade class. Why did they make the File class as facade when they had already FileSystem class? couldn't we write in our code something like this?

$file = new \Illuminate\FileSystem();
$path = $file->get(public_path("test.txt"));

but laravel decided to use File facade class after which I can write something like this:

$path = File::get(public_path("test.txt")); 

Where does the beauty reside? if you look at facade pattern, it gives you easier interface to do big jobs and not use 10 lines of code when you can do it with facade in 2 lines, but laravel's facade makes it hard to understand.

Could it be because of they made facade class so that it works like a singleton and this is its amazing side? but then why is singleton amazing ?

I don't understand what's the idea for laravel's facade.

Upvotes: 1

Views: 2879

Answers (1)

Hasnat Safder
Hasnat Safder

Reputation: 2485

Laravel has a feature similar to Facade design pattern, also named Facades. This name may confuse you because facades in Laravel don’t fully implement the Facade design pattern. According to the documentation

Facades provide a “static” interface to classes that are available in the application’s service container.

So Facade will allow us to use interfaces without worrying the actual implementation behind those. Lets take an example of Laravel Cache system. When we call $items = Cache::get('items:popular');

Here we retrieve items from cache with the help of Cache facade.

All facade classes are extended from the base Facade class. There is only one method, that must be implemented in every facade class: getFacadeAccessor() which returns the unique service name inside the IoC container. So it must return a string, that will be resolved then out of the IoC container.

Here is the source code of the Illuminate\Support\Facades\Cache facade class:

<?php 
namespace Illuminate\Support\Facade;
class Cache extends Facade 
{
    protected static function getFacadeAccessor()
    {
        return 'cache';
    }
}

It looks like we are calling a static method get() of Cache class, but as we have seen there is no such static method in Cache class. Here method get() actually exists in the service inside the container. All the magic is hidden inside the basic Facade class.

Inside Facade Class we have have __callStatic() method. __callStatic() is fired every time when a static method that does not exist on a facade is called. So, after calling Cache::get('items:popular') we are falling inside this method, we resolve an instance of the service behind a facade out of the IoC container with the help of getFacadeRoot() method. The code for this method is

public static function __callStatic($method, $args)
{
    $instance = static::getFacadeRoot();

    if (! $instance) {
        throw new RuntimeException('A facade root has not been set.');
    }

    return $instance->$method(...$args);
}

Method getFacadeRoot() returns an instance of the service object behind the facade. In this case it eventually points to CacheManager Class. Inside the CacheManager Class we have a getDefaultDriver() method which will get the default cache configuration from .env file.

public function getDefaultDriver()
{
    return $this->app['config']['cache.default'];
}

After getting the default cache configuration, using PHP magic method of __call() it attempts to call the get() method on Concrete Class of default cache (redis, database, memcached etc).

So our original call of $items = Cache::get('items:popular'); will not change if the default cache changes. Most people use database as cache for development and redis or memcached for backend. It will be Laravel Facades job to find out what actions to perform in order to get the value from cache. E.g. the get() implementation of redis is

public function get($key)
{
    $value = $this->connection()->get($this->prefix.$key);

    return ! is_null($value) ? $this->unserialize($value) : null;
}

While get() implementation for memacached is

public function get($key)
{
    $value = $this->memcached->get($this->prefix.$key);

    if ($this->memcached->getResultCode() == 0) {
        return $value;
    }
}

Similarly you can another cache concrete class. Laravel will decide it for you whichever concrete implementation is to be called.

Upvotes: 10

Related Questions