Implementing Singletons in PHP using classes or functions
websilvercraft
Posted on May 1, 2024
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());
}
}
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();
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;
}
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;
}
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! š¬
ā ā ā ā ā ā ā ā ā ā šššš
ā ā ā ā ā ā ā ā ššššššššššš
ā ā šššššššššššššššššššš
šššššššššššššššššššššššššššššššš
Posted on May 1, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.