Wanna learn design patterns? Here's a "secret": you've all actually USED them already while working with Laravel itself. Let's see the examples of patterns like Facade, Builder, and Adapter in the framework code.
The most common and familiar Builder pattern is the Query Builder in Laravel. Of course, it is extended by Eloquent Models but the core example is still the Query Builder. Let's look at the example:
Let's get a basic example:
Query Builder
DB::table('users') ->where('name', 'John') ->where('age', '>', 18) ->orderBy('age') ->get();
So what did we do, and why is it a Builder? We've built a query by chaining methods on top of a base. So what does it do under the hood? Let's look at the example:
DB::table('users')
- We indicate that we will work with the users
table->where('name', 'John')
- Added a condition that only John
users should be selected->where('age', '>', 18)
- Added a condition that only users with an age greater than 18 should be selected->orderBy('age')
- Order the users by age->get()
- Executed the query and retrieved the results from the databaseEach of these individual blocks will append to a raw SQL query as we are building it in parts. Think about it like this:
DB::table('users')
- SELECT * FROM users...
->where('name', 'John')
- ...WHERE name = 'John'...
->where('age', '>', 18)
- ...WHERE name = 'John' AND age > 18...
->orderBy('age')
- ...ORDER BY age...
->get()
- SELECT * FROM users WHERE name = 'John' AND age > 18 ORDER BY age
If we want to look at how we are building an update query we can look at this example:
Query Builder
DB::table('users') ->where('name', 'John') ->update(['age' => 18]);
This will do the following:
DB::table('users')
- We indicate that we will work with the users
table->where('name', 'John')
- Added a condition that only John
users should be updated->update(['age' => 18])
- Updated the age
column to be 18
for all users that match the conditionEach of those is individually added to the query. Let's look under the hood:
DB::table('users')
- UPDATE users...
->where('name', 'John')
- ...WHERE name = 'John'...
->update(['age' => 18])
- ...SET age = 18...
->get()
- UPDATE users SET age = 18 WHERE name = 'John'
As you can see, we are building the query in parts and then executing it. This is the Builder pattern in action!
Another commonly used pattern within your code will be Facade Pattern. It's a really simple pattern that allows you to hide the complexity of a system behind a simple interface. Let's look at the example:
Let's look at the example of the Auth Facade:
Auth Facade
Auth::login($user);Auth::check();Auth::user();Auth::id();Auth::logout();Auth::attempt($credentials);
I think that a lot of you are familiar with this Facade and used it multiple times in your application. If you think that you have never used it, there's another way that Laravel implements this - via helper:
Auth helper
auth()->login($user);auth()->check();auth()->user();auth()->id();auth()->logout();auth()->attempt($credentials);
Both of these examples are using the same Facade but different syntax to help you out. You have the full Facade class Auth
but also a helper auth()
that will return the same instance of the Facade. So what's under it?
Under the Auth
, we have a full class: \Illuminate\Auth\AuthManager
that is responsible for managing the authentication. There are a lot of methods there, but you don't have to worry about the majority of them for your application so for your convenience the Facade provides the most commonly used methods. This is the Facade pattern in action!
Bonus - it also provides one custom method:
Illuminate/Auth/AuthManager
/** * Register the typical authentication routes for an application. * * @param array $options * @return void * * @throws \RuntimeException */public static function routes(array $options = []){ if (! static::$app->providerIsLoaded(UiServiceProvider::class)) { throw new RuntimeException('In order to use the Auth::routes() method, please install the laravel/ui package.'); } static::$app->make('router')->auth($options);}
Which will register the authentication routes for you.
Working with different systems can be hard as every system might have a different standard. It doesn't matter if it's a 3rd party API or email service - they all have different standards and expect different data. This is where Adapter pattern comes in handy! Its main goal is to take whichever format you have and adapt it to the required format to become compatible. Think about it like using a power adapter for your phone to convert the wall socket into a USB cable. Did you know that you've been using it without fully understanding what it does? They are commonly used as Notifications in Laravel. Let's look at them:
Notifications in Laravel adapt your data to interact with different drivers such as email, broadcasting channels, Slack, and many more with custom drivers. So how does it work? Let's take a look at the examples:
Let's look at the toMail
method that we usually use:
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 an email. This is the adapter pattern in action! There are two layers to it:
MailMessage
classMailMessage
class transforming our data to be compatible with the email APIAnd 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 just 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 just 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.');}
And once again, there are going to be a few layers of the adapter pattern:
MailMessage
classMailMessage
class transforming our data to be compatible with the email APISlackMessage
classSlackMessage
class transforming our data to be compatible with the Slack APIWhile we still have a nice developer experience!
Another pattern that we use quite extensively under the hood is the Singleton pattern. Its main goal is to have a single instance of a specific class that's always accessed no matter where you are in your application or how many times you call it. It solves the problem of re-creating the same instance over and over again. Let's look at the example:
In Laravel, we have quite a few singletons that we use but one stands out as a really common - Request
and request()
usage. Let's look at the example:
In this case, to demonstrate that it is still the same instance being accessed we'll use both the Request
class and request()
helper. We'll set fake data on one of them and then dump the other one to see if it's the same data. Let's look at the example:
Controller
public function index(Request $request){ dump(request()->all()); request()->merge(['test' => '123']); dump(request()->all()); dd($request->all());}
Running this in the browser will show us the following:
So what happened here?
request()
helperrequest()
helper has the new keyRequest
class has the new keyAll the data was in place for both the request()
and Request
classes. It is because they are using the same instance of the Request
class. This is the Singleton pattern in action!
Another common pattern in Laravel is the Observer pattern. Its main goal is to track events that are happening and notify other classes about them. This is done with the help of the Observers
that are listening for specific events on the Models
. Let's look at the example:
app/Observers/TransactionObserver.php
// ...class TransactionObserver{ public function created(Transaction $transaction): void { $transaction->client->notify(new \App\Notifications\TransactionCreated($transaction)); } // ...}
app/Models/Transaction.php
use App\Observers\TransactionObserver; // ... public function boot(): void{ self::observe(TransactionObserver::class);}
We've just created an Observer that will track the created
event on the Transaction
model and notify the client about it with a custom notification. Do you need to also generate a PDF invoice? No problem! Just add another method to the observer, and you're good to go!
app/Observers/TransactionObserver.php
use App\Services\TransactionService; // ... public function created(Transaction $transaction): void{ $transactionService = new TransactionService(); $transactionService->generateInvoice($transaction); $transaction->client->notify(new \App\Notifications\TransactionCreated($transaction));}
That's it, we've added more functionality to our Observer. Once an event is triggered it will now generate a PDF and notify the client. This is the Observer pattern in action!
Patterns are common within all the tools that we have available to us. We've just taken a glance at what you might find commonly used but there are many more patterns applied in Laravel. I hope that this article will help you understand them better and maybe even use them in your projects!