Back to Course |
Design Patterns in Laravel 11

Adapter: in Notifications and Filesystem

We don't even have to dig too deep for this next pattern. We have to look at Notifications in Laravel:

Notification

public function toMail($notifiable)
{
return (new MailMessage)
->line('The introduction to the notification.')
->action('Link button', route('link'));
}

In this example, we've transformed our data to be compatible with the MailMessage class, which will be used to send emails. This is the adapter pattern in action! There are two layers to it:

  1. We are transforming our data to be compatible with the MailMessage class
  2. The MailMessage class transforming our data to be compatible with the email API

And all of that is done seamlessly without us even knowing about it! We don't have to worry about the email driver or what data it expects. We have to transform our data to be compatible with the MailMessage class, and that's it!

What if we want more drivers? No problem! We can add more methods to our notification class (e.g., toSlack) and transform our data to be compatible with the SlackMessage class. Let's look at the example:

Notification

public function toMail($notifiable)
{
return (new MailMessage)
->line('The introduction to the notification.')
->action('Link button', route('link'));
}
 
public function toSlack($notifiable)
{
return (new SlackMessage)
->content('The introduction to the notification.');
}

This allows us to use the same notification data but push it to different drivers. What the driver accepts doesn't matter, as our Adapter will transform our data to be compatible with it.


Filesystem - Another Adapter

Another example of the Adapter pattern in Laravel is the Filesystem. Without changing our code, we can use the same code to interact with different filesystems (e.g., local, S3, etc.). Let's look at the example:

Storage::disk('local')->put('file.txt', 'Contents');
Storage::disk('s3')->put('file.txt', 'Contents');

This looks very similar to the Notifications, where we tell the Adapter what driver we want to use, and it will transform our data to be compatible with it.

For example, the local driver will work with local files. But if we switch to the s3 driver, it will use a different (usually remote) filesystem. Let's look at the code:

Illuminate/Filesystem/FilesystemAdapter.php

/**
* Get the URL for the file at the given path.
*
* @param string $path
* @return string
*
* @throws \RuntimeException
*/
public function url($path)
{
if (isset($this->config['prefix'])) {
$path = $this->concatPathToUrl($this->config['prefix'], $path);
}
 
$adapter = $this->adapter;
 
if (method_exists($adapter, 'getUrl')) {
return $adapter->getUrl($path);
} elseif (method_exists($this->driver, 'getUrl')) {
return $this->driver->getUrl($path);
} elseif ($adapter instanceof FtpAdapter || $adapter instanceof SftpAdapter) {
return $this->getFtpUrl($path);
} elseif ($adapter instanceof LocalAdapter) {
return $this->getLocalUrl($path);
} else {
throw new RuntimeException('This driver does not support retrieving URLs.');
}
}

This allows us to use the same code, but the Adapter will transform our data to be compatible with the driver we want to use. This is the Adapter pattern in action!