CodeNewbie Community 🌱

Ulysse Valdenaire
Ulysse Valdenaire

Posted on • Updated on

Let's create a Singleton in PHP !

So I decided to talk about Singleton design pattern in PHP. This is the first tutorial I write, so feel free to comment or suggest any improvements.

Prerequisites

Before going forward, be aware it is better if you know a little of object oriented programming in PHP or in any other langage.

In this tutorial, we will create a Singleton design pattern using PHP.

But first, you need to ask:

What is a design pattern ?

Design patterns are solutions to commonly problems in software design. Developpers before us had problems when they designed their applications, so they found solutions that we can use today.
The singleton design pattern is one of these solutions.

OK, so now that you know what a design pattern is, we can start getting interested with the singleton design pattern.

What is the Singleton design pattern ?

You 'll see, the singleton is one of the simpliest one.
Imagine you are coding an application and you use object oriented programming and you need to create a class which deals with the database. You need to use the connection to your database in your application to do the CRUD basic operations.

To illustrate, here is an example:

<?php

/**
 * Database.php
 */

class Database
{
    private PDO $PDOInstance;

    private string $host = "localhost";
    private string $dbname = "dbtest";
    private string $user = "root";
    private string $password = "";


    // Connection to the database with PDO
    public function __construct()
    {
        $string = "mysql:host=" . $this->host . ";dbname=" . $this->dbname;
        $this->PDOInstance = new PDO($string, $this->user, $this->password);
    }


    // Write method to insert in the DB
    public function write(string $query, array $data): bool
    {
        $statement = $this->PDOInstance->prepare($query);
        return $statement->execute($data);
    }


    public function read(string $query, array $data = array()): array
    {
        $statement = $this->PDOInstance->prepare($query);
        $statement->execute($data);
        return $statement->fetchAll(PDO::FETCH_OBJ);
    }
}
Enter fullscreen mode Exit fullscreen mode

Here you create a simple Database class with a PDOInstance property.
We connect the database using PDO. We use it in the write method to insert data in the DB.

We can imagine that you use this class in a User class :

<?php

/**
 * User.php
 */

require "./Database.php";

class User
{
    private string $name;
    private string $email;
    private string $password;
    private Database $db;

    public function __construct(string $name, string $email, string $password)
    {
        $this->name = $name;
        $this->email = $email;
        $this->password = $password;
        $this->db = new Database();
    }


    public function insert(): bool
    {
        $sql = "INSERT INTO users(name, email, password) VALUES (:name, :email, :password)";
        $data = ['name' => $this->name, "email" => $this->email, "password" => $this->password];
        return $this->db->write($sql, $data);
    }
}
Enter fullscreen mode Exit fullscreen mode

In this class, we simply create a method to insert in the DB the user data. We use the Database class by instantiating the Database class in the user construct method.
Everything works fine.

But now imagine you have an other class which deals with the database. To keep working with user data, let's say it is the UsersManager class :

<?php
/**
 * UsersManager.php
 */

require_once "./Database.php";

class UsersManager
{
    private Database $db;

    public function __construct()
    {
        $this->db = new Database();
    }

    public function getAllUsers(): array
    {
        $sql = "SELECT * FROM users";
        return $this->db->read($sql);
    }
}
Enter fullscreen mode Exit fullscreen mode

Since this class has to have access to the DB, we instantiate the Database class.
But now we have two objects of class Database, so two connections to the database.

What if we want to use the same object in the User and UsersManager classes ?

The singleton design pattern is a solution. We would like to have one connection to the database.

But how do we do that ?

What we want is an instance of the Database class, and we want only one.

We will have a method in the Database class which returns an instance of the Database class. We want to call this method wihout creating an object of class Database, so the method needs to be static. We can call this method getInstance

public static function getInstance()
{

}
Enter fullscreen mode Exit fullscreen mode

This method returns an instance of Database. But if an instance of Database already exists, we don't want to create a new one. So we need to store the instance in a property of Database.

private static $instance = null;
Enter fullscreen mode Exit fullscreen mode

Since we will use it in the static method getInstance, the $instance property is also static.

Now we can simply code in the getInstance method :

public static function getInstance(): self
{
    if (is_null(self::$instance)) {
        self::$instance = new Database();
    }
    return self::$instance;
}
Enter fullscreen mode Exit fullscreen mode

We check if self::instance is null. If it is null, it means it have not been instantiate yet. So we instantiate the Database class in the $instance property.
If it is not null, it means the class has already been instantiate, so we can return self::instance.

And we have our instance.

So the Database class looks like this :

<?php

/**
 * DatabaseSingleton.php
 */

class DatabaseSingleton
{
    public static $instance = null;
    private $PDOInstance;

    private string $host = "localhost";
    private string $dbname = "dbtest";
    private string $user = "root";
    private string $password = "";


    // Connection to the database with PDO
    public function __construct()
    {
        $string = "mysql:host=" . $this->host . ";dbname=" . $this->dbname;
        $this->PDOInstance = new PDO($string, $this->user, $this->password);
    }


    public static function getInstance(): self
    {
        if (is_null(self::$instance)) {
            self::$instance = new DatabaseSingleton();
        }
        return self::$instance;
    }
}
Enter fullscreen mode Exit fullscreen mode

In the User class :


<?php

require "./DatabaseSingleton.php";

class User
{
    private string $name;
    private string $email;
    private string $password;
    private DatabaseSingleton $db;

    public function __construct(string $name, string $email, string $password)
    {
        $this->name = $name;
        $this->email = $email;
        $this->password = $password;
        $this->db = DatabaseSingleton::getInstance();
    }

Enter fullscreen mode Exit fullscreen mode

And we can do CRUD operations like before.

Conclusion

So to conclude, we can use the Singleton design pattern when a class only needs to be instantiated once. However, there are some inconvenients with this design pattern. It might cause issues when you have to test your code.

Thank you for reading this tutorial and see you soon !

Oldest comments (0)