What It Is
The Singleton pattern makes sure a class has exactly one instance in the whole program, and gives everyone the same access point to it. Typical singletons: a configuration, a connection pool, a logger.
PHP's tool for this is a private constructor plus a static instance() method: nobody outside the class can call new, and the one access point creates the instance on the first call. Since PHP runs each request in its own isolated process, "one for the whole program" means one per request — or one per run of a command-line script.
Example: One Config for Everyone
The config is loaded on the first Config::instance() call and never again. Every caller receives the same object.
Load once behind a private constructor
The "loading config" line prints only once, although instance() is called twice — and $a === $b proves both callers hold the same instance. The ??= operator creates the object only when the property is still null.
Output: PHP script
<?php
class Config {
private static ?self $instance = null;
public string $appName;
// Private: nobody outside the class can call new Config().
private function __construct() {
echo "loading config (happens once)\n";
$this->appName = "PromptCookbook";
}
public static function instance(): self {
return self::$instance ??= new self();
}
}
$a = Config::instance();
$b = Config::instance();
echo "app: ", $a->appName, "\n";
echo "same instance: ", var_export($a === $b, true), "\n";
When to Use It
Use it when there really must be one of something: one loaded configuration, one connection pool, one cache — and creating it is expensive or duplicates would conflict.
Skip it when you can simply create the value at the start of the script and pass it to whoever needs it. Passing dependencies openly is clearer than global access — the singleton is the last resort, not the first.
Pros and Cons
Pros: guaranteed single instance — the private constructor blocks every stray new; lazy creation on first use; one obvious access point.
Cons: it is a global in disguise — hidden dependencies make code harder to follow; tests cannot easily swap it for a fake; overuse leads to tangled programs.