Laravel Manager Pattern

post-thumb

The Builder pattern is a widely recognized pattern, invariably mentioned in any design patterns book or article. It falls under the category of ‘Creational Patterns’, which is one of the varying types of Design Patterns. As mentioned in Refactoring Guru ,

Creational pattern lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.

Further explained in Design Patterns for Humans ,

Allows you to create different flavors of an object while avoiding constructor pollution. Useful when there could be several flavors of an object. Or when there are a lot of steps involved in creation of an object.

Laravel has presented its implementation of the Builder Pattern under the name of the Manager class. Henceforth, we’ll equate the Manager Pattern to the Builder Pattern.

How the Manager Pattern Works

One of Laravel’s built-in packages is Laravel Socialite . This package simplifies the process of constructing social-based authentication. In Socialite, obtaining user details from your chosen provider is made incredibly simple, as demonstrated below:

1Socialite::driver('github')->user();

So, if we examine the driver method, we will locate it within the Laravel\Socialite\SocialiteManager class in the namespace. Socialite supports various platforms such as:

  • Facebook
  • Twitter
  • LinkedIn
  • Google
  • GitHub
  • GitLab
  • Bitbucket
  • Slack

Within the SocialiteManager class, you would find a method that initiates every single one of these platforms. In Laravel, the method that creates these platforms conventionally follows the create[driver name]Driver format. Let’s take a closer look:

 1class SocialiteManager extends Manager implements Contracts\Factory
 2{
 3    protected function createGithubDriver() {}
 4    protected function createFacebookDriver() {}
 5    protected function createGoogleDriver() {}
 6    protected function createLinkedinDriver() {}
 7    protected function createBitbucketDriver() {}
 8    protected function createGitlabDriver() {}
 9    protected function createTwitterDriver() {}
10    protected function createTwitterOAuth2Driver() {}
11    protected function createSlackDriver() {}
12}

You might have noticed that SocialiteManager extends the Manager class.

Laravel houses the Manager class within the Support namespace. Its inherent role is to offer a range of base functionalities that any managing classes necessitate for smooth operation.

Managers List

This list is from Laravel 10.x

Classextends Manager
Illuminate\Database\DatabaseManager.php
Illuminate\Database\Capsule\Manager.php
Illuminate\Database\DatabaseTransactionsManager.php
Illuminate\Broadcasting\BroadcastManager.php
Illuminate\Cache\CacheManager.php
Illuminate\Redis\RedisManager.php
Illuminate\Auth\Passwords\PasswordBrokerManager.php
Illuminate\Auth\AuthManager.php
Illuminate\Mail\MailManager.php
Illuminate\Foundation\MaintenanceModeManager.php✔️
Illuminate\Filesystem\FilesystemManager.php
Illuminate\Hashing\HashManager.php✔️
Illuminate\Support\Traits\CapsuleManagerTrait.php
Illuminate\Support\Manager.php
Illuminate\Support\MultipleInstanceManager.php
Illuminate\Queue\Capsule\Manager.php
Illuminate\Queue\QueueManager.php
Illuminate\Log\LogManager.php
Illuminate\Notifications\ChannelManager.php✔️
Illuminate\Session\SessionManager.php✔️

As you can see, many of the core managers don’t rely on the Support\Manager class. Still they mostly do the same.

Extending Driver Support

The Manager class in Laravel is designed for extensibility. This indicates that you also have the ability to add your unique driver to it. Manager classes in Laravel incorporate an extend method. This method gives you the liberty to increase support for your custom-made drivers.

1public function extend($driver, Closure $callback)
2{
3    $this->customCreators[$driver] = $callback;
4
5    return $this;
6}

So, if you want to include support for Yahoo and implement Login with Yahoo, you can easily register a new driver in the AppServiceProvider as shown below:

1use Laravel\Socialite\Facades\Socialite;
2
3Socialite::extend('yahoo', function(){
4  // Yahoo Oauth implementation here
5});

Then, you can simply use it as follows:

1Socialite::driver('yahoo')->user();

Remember, like other providers, you need to rely on the AbstractProvider in the Socialite package.

1namespace Laravel\Socialite\Two;
2
3class FacebookProvider extends AbstractProvider implements ProviderInterface
4{

The Creation of Drivers

In any Manager class, you would typically find the driver and the createDriver methods.

 1public function driver($driver = null)
 2{
 3    $driver = $driver ?: $this->getDefaultDriver();
 4
 5    if (is_null($driver)) {
 6        throw new InvalidArgumentException(sprintf(
 7            'Unable to resolve NULL driver for [%s].', static::class
 8        ));
 9    }
10    
11    if (! isset($this->drivers[$driver])) {
12        $this->drivers[$driver] = $this->createDriver($driver);
13    }
14
15    return $this->drivers[$driver];
16}

As you can see, Laravel checks if a driver is created beforehand; if it is, the existing driver gets returned. Otherwise, it employs the createDriver method.

 1protected function createDriver($driver)
 2{
 3    if (isset($this->customCreators[$driver])) {
 4        return $this->callCustomCreator($driver);
 5    }
 6
 7    $method = 'create'.Str::studly($driver).'Driver';
 8
 9    if (method_exists($this, $method)) {
10        return $this->$method();
11    }
12
13    throw new InvalidArgumentException("Driver [$driver] not supported.");
14}

In the createDriver process, Laravel checks for custom drivers, such as our Yahoo example. If it doesn’t find any, it will call the create[driver name]Driver method, as we explained earlier.

Let’s Create a Manager

We’re going to implement a basic manager for parsing CSV and JSON files. We’ll organize the entire structure within a single directory, app/Parser.

├── CsvParser.php
├── JsonParser.php
├── Parser.php (Facade)
├── ParserInterface.php
├── ParserManager.php
└── ParserServiceProvider.php

Let’s begin with our ParserManager:

 1namespace App\Parser;
 2
 3use Illuminate\Support\Manager;
 4
 5class ParserManager extends Manager
 6{
 7    public function getDefaultDriver()
 8    {
 9        return 'json';
10    }
11
12    public function createJsonDriver()
13    {
14        return new JsonParser();
15    }
16
17    public function createCsvDriver()
18    {
19        return new CsvParser();
20    }
21}

The getDefaultDriver function could be modified to fetch data from a config file, as shown below. However, for the sake of simplicity, I have hard coded it.

1public function getDefaultDriver()
2{
3    return $this->app['config']['parser.default'];
4}

Now, let’s construct our two drivers. For the purpose of simplicity, their implementation is extremely straightforward.

1namespace App\Parser;
2
3interface ParserInterface
4{
5    public function parse(string $path): array;
6}
 1namespace App\Parser;
 2
 3class JsonParser implements ParserInterface
 4{
 5    public function parse(string $path): array
 6    {
 7        $json = file_get_contents($path);
 8        return json_decode($json, true);
 9    }
10}
 1class CsvParser implements ParserInterface
 2{
 3    public function parse(string $path): array
 4    {
 5        $file = fopen($path, 'r');
 6        $rows = [];
 7
 8        while (($row = fgetcsv($file)) !== false) {
 9            $rows[] = $row;
10        }
11
12        fclose($file);
13
14        return $rows;
15    }
16}

Now, let’s create a Facade class to enable the convenient invocation of the Parser Manager, similar to how we utilize Socialite.

 1namespace App\Parser;
 2
 3use Illuminate\Support\Facades\Facade;
 4
 5class Parser extends Facade
 6{
 7    protected static function getFacadeAccessor()
 8    {
 9        return 'parser';
10    }
11}

In the ParserServiceProvider, we need to bind our Manager to parser.

 1namespace App\Parser;
 2
 3use Illuminate\Support\ServiceProvider;
 4
 5class ParserServiceProvider extends ServiceProvider
 6{
 7    public function register()
 8    {
 9        $this->app->singleton('parser', function ($app) {
10            return new ParserManager($app);
11        });
12    }
13}

That’s all there is to it. Now, we can simply use it as shown below:

1use App\Parser\Parser;
2
3Parser::parse($jsonFile); // because json is our default driver
4
5Parser::driver('csv')->parse($csvPath)

Adding XML Support

Next, let’s attempt to include a custom XML driver.

 1namespace App;
 2
 3use App\Parser\ParserInterface;
 4
 5class XmlDriver implements ParserInterface
 6{
 7    public function parse(string $path): array
 8    {
 9        $xml = simplexml_load_file($path);
10
11        return $this->parseNode($xml);
12    }
13
14    private function parseNode($node) {
15        $arr = [];
16
17        foreach ($node->children() as $child) {
18            $childName = $child->getName();
19
20            $childValue = count($child->children()) > 0 ? $this->parseNode($child) : strval($child);
21
22            if (!isset($arr[$childName])) {
23                $arr[$childName] = $childValue;
24            } else {
25                if (!is_array($arr[$childName]) || !isset($arr[$childName][0])) {
26                    $arr[$childName] = [$arr[$childName]];
27                }
28                $arr[$childName][] = $childValue;
29            }
30        }
31
32        return $arr;
33    }
34}

In the AppServiceProvider, let’s go ahead and embed it. However, remember that we must register it within the boot method, as there’s a possibility that our ParserServiceProvider may not yet be registered when the register method is processed.

 1use App\Parser\Parser;
 2use App\XmlDriver;
 3
 4class AppServiceProvider extends ServiceProvider
 5{
 6    public function boot(): void
 7    {
 8        Parser::extend('xml', function ($app) {
 9            return new XmlDriver();
10        });
11    }
12}

Then, we can utilize it as shown below:

1use App\Parser\Parser;
2
3Parser::driver('xml')->parse($XmlFile);

Conclusion

I hope this article has aided in your understanding of the Manager class and how Laravel employs it. It should also clarify how you can apply it in your own projects. You can now begin to use it to simplify any complex objects within your codebase.

Happy Coding!

comments powered by Disqus

You May Also Like