Guard the creational pattern Singleton against duplication by clone and unserialize
Implement a Config singleton that can be obtained only through Config::getInstance() and cannot be duplicated by any other route.
Requirements: new Config() must be impossible from outside; the single instance is stored on the class and returned by every getInstance() call; clone $instance must fail; and unserialize(serialize($instance)) must not yield a second object either.
<?php
final class Config
{
private static ?self $instance = null;
private array $values = [];
// your code here — constructor, getInstance(), and the two guards
public function get(string $key): mixed
{
return $this->values[$key] ?? null;
}
}
Write the implementation.
Make __construct private so new is blocked, keep the instance in a private static slot, and hand it out from getInstance(). Then close two back doors: a private __clone() makes clone fatal, and __wakeup() must throw, since unserialize() rebuilds an object without the constructor.
- ✗Blocking
newwith a private constructor but leavingclone $instancewide open - ✗Forgetting that
unserialize()rebuilds an object without ever calling__construct - ✗Believing
finalon the class is what prevents cloning
- →Why does
unserialize()bypass a private constructor, and what does__wakeup()do about it? - →What makes a singleton hard to test, and what would you inject instead?
A singleton is closed by three doors, not one. A private constructor closes new, a private __clone closes clone, and __wakeup() closes unserialize() — which constructs an object while bypassing the constructor entirely.
<?php
final class Config
{
private static ?self $instance = null;
private array $values = [];
private function __construct()
{
$this->values = ['env' => 'prod'];
}
public static function getInstance(): self
{
return self::$instance ??= new self();
}
private function __clone() {}
public function __wakeup(): void
{
throw new \LogicException('Config is a singleton and cannot be unserialized.');
}
public function get(string $key): mixed
{
return $this->values[$key] ?? null;
}
}
__wakeup() is deliberately public: a magic method's signature cannot be hidden, so the guard is that it throws, not its visibility. __clone, by contrast, only has to be private — clone $config from outside is then a fatal error.