This pattern is almost a follow-up to the previous lesson on Singleton. We just took a look at Singletons in Laravel, but one thing was constantly repeating:
$this->app->singleton('auth', fn ($app) => new AuthManager($app)); $this->app->singleton('mail.manager', function ($app) { return new MailManager($app);}); $this->app->singleton('cache', function ($app) { return new CacheManager($app);});
Why are there "Manager" classes repeating? Well, this is a Manager pattern used in Laravel.
Before we dive into how the Manager pattern is used in Laravel, let's take a look at what the Manager pattern is.
The idea is to create the first class (Manager), responsible for managing the second class (Driver).
For example:
class ExportManager extends Manager{ public function getDefaultDriver() { return 'csv'; } public function createCsvDriver() { return new CsvParser(); } public function createJsonDriver() { return new JsonParser(); }}
While the Manager class comes from `Illuminate\Support\Manager' and handles driver creation, the Driver classes do the actual work.
So let's create a CsvParser
class:
class CsvParser{ public function parse(string $path): array { $file = fopen($path, 'r'); // Do the parsing here fclose($file); return $rows; }}
Note: Our JsonParser class would be similar to this one - just with JSON parsing logic.
Now we can create a singleton for the ExportManager
:
$this->app->singleton('export', function ($app) { return new ExportManager($app);});
And then we can use it like this:
$export = app('export');$export->driver('csv')->parse('file.csv');
While we can still improve this code for better implementation, this is the basic idea behind the Manager pattern.
Create a Manager class to manage the Driver classes. Then, use the Manager instance to consume/call the Driver classes.
In Laravel, the manager pattern appears in a few places. One of them is the AuthManager
class:
Illuminate/Auth/AuthManager.php
class AuthManager implements FactoryContract{ // ... public function __construct($app) { $this->app = $app; $this->userResolver = fn ($guard = null) => $this->guard($guard)->user(); } // ... /** * Attempt to get the guard from the local cache. * * @param string|null $name * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard */ public function guard($name = null) { $name = $name ?: $this->getDefaultDriver(); return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name); } // ...
This essentially means that the AuthManager
class is responsible for managing the Guard
classes.
Each Guard class has its own logic for handling the authentication, but the AuthManager
class is responsible for managing them due to magic method usage:
// ... /** * Dynamically call the default driver instance. * * @param string $method * @param array $parameters * @return mixed */public function __call($method, $parameters){ return $this->guard()->{$method}(...$parameters);} // ...
This allows us to call the AuthManager
instance as if it were the Guard
instance:
$auth = app('auth');$auth->user(); // Or using singleton$auth = Auth::user();
It does not matter if we are using the SessionGuard
or TokenGuard
—the AuthManager
will handle the logic for us.
Another example of the Manager pattern in Laravel is the MailManager
class:
Illuminate/Mail/MailManager.php
class MailManager implements FactoryContract{ // ... public function __construct($app) { $this->app = $app; } // ... public function mailer($name = null) { $name = $name ?: $this->getDefaultDriver(); return $this->mailers[$name] = $this->get($name); } // ...
This class allows us to have multiple mailers and then use them as needed without worrying about the actual implementation:
Notification
// ... public function via(object $notifiable): array{ return ['mail', 'database'];} // ...
While this might seem simple, it takes a lot of work to make different providers work.
This is especially great if your application needs to send emails using different providers, like Mailgun, SendGrid, or SMTP (depending on the application's region).
The last of our examples is the CacheManager
class:
Illuminate/Cache/CacheManager.php
class CacheManager implements FactoryContract{ // ... public function __construct($app) { $this->app = $app; } // ... public function store($name = null) { $name = $name ?: $this->getDefaultDriver(); return $this->stores[$name] ??= $this->resolve($name); } // ...
Once again, this builds support for multiple cache drivers. With this, we can use the following code:
Cache::store('redis')->put('bar', 'baz', 600);Cache::store('file')->put('bar', 'baz', 600);Cache::store('database')->put('bar', 'baz', 600);
But also, with the same concept as before, we can use the default driver:
Cache::put('bar', 'baz', 600);
This is handled by the Manager class, which checks whether the driver was provided and, if not, uses the default one.
The Laravel Manager pattern is used to manage different configurations of the same class type. From Authentication to Logging - there is a massive number of Managers in Laravel core.
They all have the same structure: a Manager class that manages the Driver classes by storing logic inside it.
From there, this is often combined with the Singleton pattern to create a single instance of the Manager but allows real-time driver switching.