DEV Community

websilvercraft
websilvercraft

Posted on • Edited on

Implementing Singletons in PHP using classes or functions

Singleton pattern is probably the most used design patterns. It's a useful way to ensure that a class has only one instance while providing a global access point to this instance. It is helpful in coordinating actions across a system but also makes the code more tightly coupled and harder to extend and test. This is why I prefer to use functions as singletons because IMO it offers more flexibility.

Let's start with the following code:

function getDatabaseConnection() {
    $host = 'localhost'; // or your host
    $db = 'your_database_name';
    $user = 'your_username';
    $pass = 'your_password';
    $charset = 'utf8mb4';

    $dsn = "mysql:host=$host;dbname=$db;charset=$charset";
    $options = [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES => false,
    ];
    try {
        return new PDO($dsn, $user, $pass, $options);
    } catch (\PDOException $e) {
        throw new Exception($e->getMessage());
    }
}
Enter fullscreen mode Exit fullscreen mode

The problem with the code above is the fact that each time we request a connection a new connection will be open. To ensure that database connections are reused efficiently we can consider implementing a connection pooling strategy or a singleton pattern.

Since PHP's standard PDO does not support connection pooling natively (connections are closed at the end of the script) and we each script is run in a single thread, we don't really need a pool of multiple connections, the singleton pattern is a common approach to manage and reuse a database connection within the lifecycle of a single PHP request.

Singleton pattern

Let's adapt the getDatabaseConnection function to use the singleton pattern:

class Database {
    private static $instance = null;
    private $pdo;

    private function __construct() {
        $host = 'localhost'; // or your host
        $db = 'your_database_name';
        $user = 'your_username';
        $pass = 'your_password';
        $charset = 'utf8mb4';

        $dsn = "mysql:host=$host;dbname=$db;charset=$charset";
        $options = [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false,
        ];
        try {
            $this->pdo = new PDO($dsn, $user, $pass, $options);
        } catch (\PDOException $e) {
            throw new Exception($e->getMessage());
        }
    }

    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new Database();
        }
        return self::$instance->pdo;
    }
}

// Usage:
$db = Database::getInstance();
Enter fullscreen mode Exit fullscreen mode

The Singleton Pattern ensures that only one instance of a particular class (in this case, your database connection) is created and provides a global point of access to that instance. By using this pattern, we can reuse the same connection across different parts of your application during a single request.

When using this method, we need to take care at the name clashes, and also to check how we include classes.

Singleton Function using a Global Variable

If we want to avoid using classes, we can simply use functions along with global variables:

$globalPDOConnection = null;

function getDatabaseConnection() {
    global $globalPDOConnection;
    if ($globalPDOConnection === null) {
        $host = 'localhost'; // or your host
        $db = 'your_database_name';
        $user = 'your_username';
        $pass = 'your_password';
        $charset = 'utf8mb4';

        $dsn = "mysql:host=$host;dbname=$db;charset=$charset";
        $options = [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false,
        ];
        try {
            $globalPDOConnection = new PDO($dsn, $user, $pass, $options);
        } catch (\PDOException $e) {
            throw new Exception($e->getMessage());
        }
    }
    return $globalPDOConnection;
}
Enter fullscreen mode Exit fullscreen mode

We define the PDO connection as a global variable and check if it's already set before creating a new connection. This way, the connection is reused for all subsequent calls during the request lifecycle.

Singleton Function using a Static Variable Inside Function

Another alternative is to encapsulate everything within the function using a static variable. This keeps the connection handling within the function scope, avoiding the global scope:

function getDatabaseConnection() {
    static $pdoConnection = null;
    if ($pdoConnection === null) {
        $host = 'localhost'; // or your host
        $db = 'your_database_name';
        $user = 'your_username';
        $pass = 'your_password';
        $charset = 'utf8mb4';

        $dsn = "mysql:host=$host;dbname=$db;charset=$charset";
        $options = [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false,
        ];
        try {
            $pdoConnection = new PDO($dsn, $user, $pass, $options);
        } catch (\PDOException $e) {
            throw new Exception($e->getMessage());
        }
    }
    return $pdoConnection;
}
Enter fullscreen mode Exit fullscreen mode

Singleton pattern is useful in certain scenarios, but we should avoid overusing it. For good reasons is also considered sometimes an antipattern, because it makes out code tightly coupled and harder to extend without modifying the code(see open close principle).

A better approach would be to use the factory pattern to provide the necessary objects, this way would be easier to customize the code. For database connections, i prefer to use a simple function with static blocks, as it gives me enough flexibility and i can always include a different php file with another version of the function.

πŸš€ Interested to learn more πŸ› οΈπŸ€–πŸ’»? Then don't forget to πŸ“¬βœ‰οΈ, πŸ‘‡

πŸš€ If you are interested in learning more about programming, πŸ› οΈ building applications, or in general about AI πŸ€– and tech πŸ’», you can subscribe to my newsletter at websilvercraft.substack.com βœ‰οΈ to get the posts delivered directly to you as soon as I publish them! πŸ“¬
β €β €β € β €β €β €β € β €β € β €πŸ‘†πŸ‘†πŸ‘†πŸ‘†
β €β €β €β €β €β €β €β € πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†
β € β € πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†
πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†πŸ‘†

Top comments (1)

Collapse
 
skipperhoa profile image
HΓ²a Nguyα»…n Coder

Thanks you