DEV Community

Cover image for Design Patterns in PHP 8: alternative implementations
Max Zhuk
Max Zhuk

Posted on • Edited on

Design Patterns in PHP 8: alternative implementations

Hello, fellow developers!🧑🏼‍💻

In the previous article, I showed examples of implementing the design patterns Singleton and Multiton using inheritance. This is a fairly convenient approach, provided that we have several simple classes in the project that implement just a few methods and require a single instance in all places where these methods are called.

The php language only allows single inheritance, and this may be the reason why the variant from the last article will complicate or even break the architecture of our application. Therefore, I propose to consider other options for implementing the patterns.

Often, the correct solution is to implement the pattern in every class that requires it.

class Database
{
    private static self|null $instance = null;

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

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

        return self::$instance;
    }

    public function connect()
    {
        // ...
    }
}

$db = Database::getInstance();
$db->connect();
Enter fullscreen mode Exit fullscreen mode

An alternative would be to use traits. This option is suitable if the project has several classes and you want to extract part of the repeating code in order to comply with the DRY principle.

trait MultitonTrait
{
    private static array|null $instance = null;

    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 Database
{
    use MultitonTrait;

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

    public function connect()
    {
        // ...
    }
}

$db = Database::getInstance('mysql');
$db->connect();
Enter fullscreen mode Exit fullscreen mode

All options from this article and the previous one have both their pros and cons, so the best solution should be chosen based on the architecture of a particular project.


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!

Top comments (4)

Collapse
 
suckup_de profile image
Lars Moelleken

Traits are maybe helpful to not repeat code parts in a project, but it can easily prevent you to follow other interesting patterns like "Composition Over Inheritance" or "KISS" or "Open/Closed" because a trait in PHP is more or less code that will be copied into the class on runtime. The problem will only come up if you over-use traits or if you extend a class with traits, a quick solution would be to use an interface for every trait: e.g.: 3v4l.org/CfuF7#v8.1.8

Some hints for your code example:

  • do not use mixed types for array-keys
$db1 = Database::getInstance(1);
$db1->connect();

$db2 = Database::getInstance('1');
$db2->connect();

if ($db1 === $db2) {
    echo ":(";
}
Enter fullscreen mode Exit fullscreen mode
  • if you do not need NULL do not use it

e.g. array_key_exists will throw a warning on the first run because NULL is the default value for the static property "instance". You could just use an empty array as default so that you do not need to handle the NULL type.

PS: I already wrote that on my last comment ;) but I think it's important to know if someone is starting using Singleton Patterns -> 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...

Collapse
 
suckup_de profile image
Lars Moelleken

Here is also an interesting article about php traits: matthiasnoback.nl/2022/07/when-to-...

Collapse
 
bdelespierre profile image
Benjamin Delespierre

Or you could let go of the Singleton anti pattern and use a proper registry …

Collapse
 
sergei_shaikin_becf4a1e8c profile image
Sergei Shaikin

Interesting.