Singleton Design Pattern in Laravel

Dhemy

April 15, 2024 - 5 min read

Singleton Design Pattern in Laravel
Image source: https://unsplash.com/photos/white-and-blue-labeled-carton-jmRikC7H2FA

Google search console told me that I got some visits from people searching for “Singleton Design Pattern in Laravel”. But they landed on my PHP Real-world use cases of singleton design pattern post. So I decided to make them happy with a dedicated post about Singleton Design Pattern in Laravel.

In the mentioned post, I explained the Singleton Design Pattern in PHP, how to implement it, how to test it, and some real-world use cases. If you are not familiar with this pattern, you can check it out.

Why would you use Singleton Design Pattern?

A perfect use case for the Singleton is a FileSystem class. You want to perform file operations in your application, and you want to use the same instance of the FileSystem class everywhere in your application. It’s impossible to depend on the constructors to get the same instance of the FileSystem, that’s where the Singleton Design Pattern comes in.

It may look unnecessary to use the Singleton Design pattern in PHP because each request is a new instance of the application and all instances are destroyed after the request is done. But you get more benefits from it using it in something like a Queue worker or a Laravel Octane server.

Some Laravel Binding basics

Before we create our Laravel Singleton, let’s understand some Laravel binding basics. The Laravel service container is a powerful tool for managing class dependencies and performing dependency injection. Binding is the process of defining how the container should resolve a class or an interface. Most of the time you don’t need to instruct the container how to resolve your dependencies, but sometimes you do.

To bind a class into the container, you can use the bind() method inside one of the service providers. The following example binds a Foo class. We can use the \App\Providers\AppServiceProvider for this.

Let’s assume we have a Foo class that can tell us when it was created.

<?php

declare(strict_types=1);

namespace App;

final readonly class Foo
{
    public function __construct(public int $createdAt)
    {
    }
}

Now we can bind the Foo class to the container.

<?php

namespace App\Providers;

use App\Foo;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        $this->app->bind(Foo::class, function () {
            return new Foo(time());
        });
    }
    
    // .. other methods
}

Now we can resolve the Foo class from the container.

<?php

use App\Foo;
use Illuminate\Support\Facades\Route;

Route::get('/', static function (Foo $foo) {
   return $foo->createdAt;
});

Now if you visited your home page, you can see the timestamp when the Foo class was created, Something like this

1713182568

Binding a Singleton

The singleton method binds a class or interface into the container that should only be resolved one time. Once a singleton binding is resolved, the same object instance will be returned on subsequent calls into the container. To see it in action, let’s change our Foo example to a Singleton.

// app/Providers/AppServiceProvider.php

    public function register(): void
    {   
-       $this->app->bind(Foo::class, function () {
+       $this->app->singleton(Foo::class, function () {
            return new Foo(time());
        });
    }

Now, let’s create two instances of the Foo class in our route.

<?php
// routes/web.php

use App\Foo;
use Illuminate\Support\Facades\Route;

Route::get('/', static function () {
  $firstCall = app(Foo::class);

  // Simulate a delay
  sleep(1);

  $secondCall = app(Foo::class);

  return response()->json([
        'first_call' => $firstCall->createdAt,
        'second_call' => $secondCall->createdAt,
        'is_same_instance' => $firstCall === $secondCall,
    ]);
});

The response should be something like this

{
  "first_call": 1713183052,
  "second_call": 1713183052,
  "is_same_instance": true
}

As you can see, the first_call and second_call are the same, and the is_same_instance is true. Try to replace the singleton with bind and see the difference.

Two types of Singletons binding in Laravel

We have already tried the singleton() method, but there is another way to bind a Singleton in Laravel. The Scoped Bindings. The scoped() method binds a class or interface into the container that should only be resolved one time within a given Laravel request / job lifecycle. While this method is similar to the singleton method, instances registered using the scoped method will be flushed whenever the Laravel application starts a new “lifecycle”, such as when a Laravel Octane worker processes a new request or when a Laravel queue worker processes a new job:

// app/Providers/AppServiceProvider.php

    public function register(): void
    {   
-       $this->app->singleton(Foo::class, function () {
+       $this->app->scoped(Foo::class, function () {
            return new Foo(time());
        });
    }

Conclusion

Laravel offers two types of Singleton bindings, the singleton() and the scoped(), each with its own use cases and lifecycle.

© 2021 - 2024 . imdhemy.com Published by Jekyll, hosted on GitHub Pages.