DEV Community

vimuth
vimuth

Posted on • Edited on

Singleton pattern in PHP and IOC (Inversion Of Control) container in laravel

Singleton pattern

The Singleton pattern is a simple and effective way to ensure a class has only one instance and provides a global point of access to that instance. This is particularly useful for shared resources like database connections or configuration managers.

Regular Object Creation
Think about regular class and object.

class Config {
    private $name;

    public function setName($value) {
        $this->name = $value;
    }

    public function getName() {
        return $this->name;
    }
}

$config1 = new Config();
$config2 = new Config();

$config1->setName('value');
echo $config1->getName(); // Outputs: value
echo $config2->getName(); // Outputs: (empty string), because $config2 has a different instance
Enter fullscreen mode Exit fullscreen mode

Here for the first object we are calling set method adding the name. And after that for both objects we are trying to get the name. But we are getting the name only inside $config1 object because it is set only inside that object instance

Singleton Config Class
Now let's go to the singleton.

<?php
// Config.php

class Config {
    private static $instance = null;
    private $name;

    // The static method to get the instance of the class.
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new Config();
        }

        return self::$instance;
    }

    public function setName($value) {
        $this->name = $value;
    }

    public function getName() {
        return $this->name;
    }
}
?>
Enter fullscreen mode Exit fullscreen mode

This is how it works,

<?php
// useConfig.php

// Include the Config class file
require_once 'Config.php';

// Get the instance of the Config class
$config1 = Config::getInstance();
$config2 = Config::getInstance();

// Set and get the name using the singleton instance
$config1->setName('value');
echo $config1->getName(); // Outputs: value
echo $config2->getName(); // Outputs: value, because $config2 is the same instance as $config1

// Verify both variables hold the same instance
var_dump($config1 === $config2); // Outputs: bool(true)
?>
Enter fullscreen mode Exit fullscreen mode

If how many instances we created like $config1 = Config::getInstance(); values are same. And also this part make sure it only initialize once

public static function getInstance() {
    if (self::$instance === null) {
        self::$instance = new Config();
    }

    return self::$instance;
}
Enter fullscreen mode Exit fullscreen mode

Use it with dependency injection
Now let us see how to use this with dependency injection.

<?php 

// Config.php

class Config {
    private static $instance = null;
    private $name;

    // The static method to get the instance of the class.
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new Config();
        }

        return self::$instance;
    }

    public function setName($value) {
        $this->name = $value;
    }

    public function getName() {
        return $this->name;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now let us call it inside a service.

<?php 

// Service.php

require_once 'Config.php';

class Service {
    private $config;

    // Constructor with dependency injection
    public function __construct(Config $config) {
        $this->config = $config;
    }

    public function processName() {
        $this->config->setName('injected value');
        echo $this->config->getName(); // Outputs: injected value
    }
}
Enter fullscreen mode Exit fullscreen mode

In above code we have injected Config class to another class. And we can use it like this

<?php
// useConfig.php

// Include the Config and Service class files
require_once 'Config.php';
require_once 'Service.php';

// Get the singleton instance of the Config class
$config = Config::getInstance();

// Inject the Config instance into MyService
$service = new Service($config);

// Use MyService
$service->processName(); // Outputs: injected value

// Verify the Config instance retains the state
echo $config->getName(); // Outputs: injected value
Enter fullscreen mode Exit fullscreen mode

Here we create a singleton instance and inject the object to second class constructor. And we can inject multiple classes inside constructor and do something combined inside processName() function. Imagine we call two API's with API keys and getting results here.

Important note - Why have we use dependency injection here.

Dependency injection is more flexible, easier to test, promotes decoupling, adheres to single responsibility principle. Let me show you. Imagine we are going to use two classes. One for testing and another one for real app. Real app has one config set and testing we use different set of configs. We can inject abstract class or interface instead of adding the same class here.

Laravel IOC container.
This is how we use laravel ioc (Inversion of Control) container with the provided code. First we create a service class.

Create the ConfigService class in app/Services/ConfigService.php.

<?php

namespace App\Services;

class ConfigService {
    private static $instance = null;
    private $name;

    public function __construct() {
        // Private constructor to prevent direct instantiation
    }

    // Static method to get the singleton instance
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new ConfigService();
        }

        return self::$instance;
    }

    public function setName($value) {
        $this->name = $value;
    }

    public function getName() {
        return $this->name;
    }
}

Enter fullscreen mode Exit fullscreen mode

Open AppServiceProvider located at app/Providers/AppServiceProvider.php and register the ConfigService as a singleton.

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\ConfigService;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        // Register ConfigService as a singleton
        $this->app->singleton(ConfigService::class, function ($app) {
            return ConfigService::getInstance();
        });
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}

Enter fullscreen mode Exit fullscreen mode

Create a controller and inject the ConfigService directly into its constructor. You can create a controller using the Artisan command:

<?php

namespace App\Http\Controllers;

use App\Services\ConfigService;

class ConfigController extends Controller
{
    private $configService;

    // Constructor with dependency injection
    public function __construct(ConfigService $configService) {
        $this->configService = $configService;
    }

    public function index() {
        $this->configService->setName('value');
        echo $this->configService->getName(); // Outputs: value
    }
}

Enter fullscreen mode Exit fullscreen mode

Now we don't need to do this.

$configService = ConfigService::getInstance();
Enter fullscreen mode Exit fullscreen mode

When controller class gets initialized laravel does it automatically for you.

And we can call this inside routes.

Route::get('/config', [ConfigController::class, 'index']);
Enter fullscreen mode Exit fullscreen mode

Top comments (0)