Design Patterns in PHP 8: Singleton & Multiton
Max Zhuk

Max Zhuk @zhukmax

About: Software Engineer (Php, Mysql, React), Open Source committer, Tech writer, Judge of Hackathons

Location:
Novi Sad, Serbia
Joined:
Apr 4, 2021

Design Patterns in PHP 8: Singleton & Multiton

Publish Date: Jul 20 '22
76 10

Hello, fellow developers!🧑🏼‍💻

I want to talk about design patterns in php in the few next articles. And I really like how the language is progressing that's why I'll make examples with the last innovations of php 8.

Singleton

Sometimes we need only one instance of some a class in different code places. For example, when we make a web application, usually it needs to connect to the database and it will be a really good idea to create just one copy of the connection and use it in every file, class, function. The pattern that will help us with this - Singleton, is one of the most popular and easiest design patterns.

Let's see how we can implement classes a database connection and logging with one instance in all objects:

class Singleton
{
    protected static self|null $instance = null;

    final private function __construct(){}
    final protected function __clone(){}
    final protected function __wakeup(){}

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

        return static::$instance;
    }
}

class Database extends Singleton
{
    public function connect()
    {
        // ...
    }
}

class Logger extends Singleton
{
    private $connection;

    public function settings($connection = null)
    {
        $this->connection = $connection ?? '/var/logs/filename.log';
    }

    public function error(string $message)
    {
        // ...
    }

    public function warn(string $message)
    {
        // ...
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we'll use a logger with writing logs in a database table. For that, we need a connection to the db and set it by the settings method:

$db = Database::getInstance();
$db->connect();

$logger = Logger::getInstance();
$logger->settings($db);
$logger->error('Something wrong');
Enter fullscreen mode Exit fullscreen mode

Multiton

But what if we need more than just one instance of a logger because some messages must be written in a file, some need to be send by email? Or we can have other reasons. For that, we'll use the Multiton pattern. Multiton looks really resembling the previous pattern but with an array of instances of a class.

class Multiton
{
    protected static array|null $instance = null;

    final private function __construct(){}
    final protected function __clone(){}
    final protected function __wakeup(){}

    public static function getInstance(int|string $key): self
    {
        if (!array_key_exists($key, self::$instance)) {
            self::$instance[$key] = new self;
        }

        return self::$instance[$key];
    }
}

class Logger extends Multiton
{
    private array $settings;

    public function setSettings(array $settings)
    {
        // ...
    }

    public function error(string $message)
    {
        // ...
    }

    public function warn(string $message)
    {
        // ...
    }
}
Enter fullscreen mode Exit fullscreen mode

Let's make two loggers with different settings for saving logs into files and database. I won't describe the setter of settings and writer to file/database in details cause it isn't important for the pattern.

$fileLogger = Logger::getInstance('file');
$fileLogger->setSettings([
    //...
]);
$fileLogger->error('Error text');

$dbLogger = Logger::getInstance('database');
$dbLogger->setSettings([
    //...
]);
$dbLogger->error('Error will write in Database');
Enter fullscreen mode Exit fullscreen mode

P.S. Fellow developers, if you've found value in this article and are eager to deepen your understanding of design patterns in PHP and TypeScript, I have thrilling news for you! I am in the midst of crafting a comprehensive book that delves extensively into these topics, filled with practical examples, lucid explanations, and real-world applications of these patterns.

This book is being designed to cater to both novices and seasoned developers, aiming to bolster your understanding and implementation of design patterns in PHP and TypeScript. Whether you are aiming to refresh your existing knowledge or venture into new learning territories, this book is your perfect companion.

Moreover, your subscription will play a pivotal role in supporting the completion of this book, enabling me to continue providing you with quality content that can elevate your coding prowess to unprecedented heights.

I invite you to subscribe to my blog on dev.to for regular updates. I am eager to embark on this journey with you, helping you to escalate your coding skills to the next level!

Comments 10 total

  • Lars Moelleken
    Lars MoellekenJul 21, 2022

    I found recommend not to use "extend" for such patterns.

    <?php
    
    final class SingletonLib {
    
        /**
         * @var array<class-string, object>
         */
        private static $singleton_array = [];
    
        /**
         * @param string $classname
         *
         * @return object
         *
         * @template       TInit as object
         * @phpstan-param  class-string<TInit> $classname
         * @phpstan-return TInit
         */
        public static function init($classname) {
            if (!isset(self::$singleton_array[$classname])) {
                self::$singleton_array[$classname] = new $classname();
            }
    
            /* @phpstan-ignore-next-line | static magic */
            return self::$singleton_array[$classname];
        }
    
        /**
         * @param string $classname
         *
         * @return object
         *
         * @template       TInit as object
         * @phpstan-param  class-string<TInit> $classname
         * @phpstan-return TInit|null
         */
        public static function get($classname) {
            if (!isset(self::$singleton_array[$classname])) {
                return null;
            }
    
            /* @phpstan-ignore-next-line | static magic */
            return self::$singleton_array[$classname];
        }
    
        /**
         * @return array<class-string, object>
         */
        public static function getAll() {
            return self::$singleton_array;
        }
    }
    
    Enter fullscreen mode Exit fullscreen mode
    class Database
    {
        public function connect()
        {
            // ...
        }
    
        /**
         * @return static
         */
        public static function singleton() {
            return SingletonLib::init(static::class);
        }
    }
    
    Enter fullscreen mode Exit fullscreen mode

    PS: you maybe do not need this at all because you can move this decision into your container e.g.: container.thephpleague.com/4.x/def...

    • Max Zhuk
      Max ZhukJul 22, 2022

      Thanx for detailed comment

  • Alexander Dyriavin
    Alexander DyriavinJul 22, 2022

    Nice article!

  • Sergei Shaikin
    Sergei ShaikinJul 25, 2022

    Good.

  • Iuri Jacob
    Iuri JacobJul 25, 2022

    In the Singleton example you are getting an instance of Logger, but you´re calling a static method to log. In this case, singleton is not needed.

    • Max Zhuk
      Max ZhukJul 27, 2022

      Thanks for your comment, you're right, I fixed this in article.

  • Dan Jones
    Dan JonesAug 5, 2022

    You need to replace all those selfs with static, or your other classes will only be Singleton, and not Database or Logger.

  • Anneta Wamono
    Anneta WamonoSep 4, 2023

    Thank you for the breakdown!

  • websilvercraft
    websilvercraftMay 2, 2024

    I'm trying to avoid using singletons, because they tend to make the code mot tightly coupled. I'm using an alternative implementation of the singleton pattern in php using functions.

  • Hòa Nguyễn Coder
    Hòa Nguyễn CoderNov 4, 2024

    Thanks you very much

Add comment