2

How can I make a singleton of the PDO extention? Extending doesn't work, because I get a fatal error when I try it ...

1 Answers1

12

You don't need a Singleton.

But to answer this nevertheless:

You cannot turn a public visibility to a stricter visibility. So PDO cannot have the constructor's visibility changed to anything but public. So you need to wrap the PDO instance into a Singleton:

class MyPdo
{
    /**
     * @var MyPdo
     */
    protected static $_instance;

    /**
     * @var Pdo
     */
    protected $_pdo;

    /**
     * Creates instance and returns it on subsequent calls
     * 
     * @throws  InvalidArgumentException
     * @param   array $options PDO connection data
     * @returns MyPdo
     */
    public static function getInstance(array $options = NULL)
    {
        if(self::$_instance === NULL) {

            if($options === NULL) {
                throw new InvalidArgumentException(
                    'You must supply connection options on first run');
            }

            // call constructor with given options and assign instance
            self::$_instance = new self(
                $options['dsn'], 
                $options['user'], 
                $options['password'],
                $options['driver_options']
            );
        }

        return self::$_instance;
    }

    /**
     * Creates new MyPdo wrapping a PDO instance
     * 
     * @throws PDOException
     * @param  String $dsn
     * @param  String $user
     * @param  String $password
     * @param  Array  $driver_options
     * @return void
     */
    private function __construct($dsn, $user, $password, $driver_options)
    {
        try {
            $this->_pdo = new PDO($dsn, $user, $password, $driver_options);
        } catch (PDOException $e) {
            echo 'Connection failed: ' . $e->getMessage();
        }
    }

    /**
     * Singletons may not be cloned
     */
    private function __clone() {}

    /**
     * Delegate every get to PDO instance
     *  
     * @param  String $name
     * @return Mixed
     */
    public function __get($name)
    { 
        return $this->_pdo->$name;
    }

    /**
     * Delegate every set to PDO instance
     *  
     * @param  String $name
     * @param  Mixed  $val
     * @return Mixed
     */    
    public function __set($name, $val)
    { 
        return $this->_pdo->$name = $val; 
    }

    /**
     * Delegate every method call to PDO instance
     *  
     * @param  String $method
     * @param  Array  $args
     * @return Mixed
     */    
    public function __call($method, $args) {
        return call_user_func_array(array($this->_pdo, $method), $args);
    }
}

You'd use it like this:

$db = MyPdo::getInstance(array(
    'dsn'=>'mysql:dbname=mysql;host=127.0.0.1',
    'user' => 'root',
    'password' => 'minnymickydaisydonaldplutogoofysanfrancisco',
    'driver_options' => array(
        PDO::MYSQL_ATTR_INIT_COMMAND =>  "SET NAMES utf8"
    )));

$version = $db->query( 'SELECT version();' );
echo $version->fetchColumn();

// remove reference to instance
unset($db);

// doesn't require connection data now as it returns same instance again
$db = MyPdo::getInstance();
$version = $db->query( 'SELECT version();' );
echo $version->fetch();
Community
  • 1
  • 1
Gordon
  • 312,688
  • 75
  • 539
  • 559
  • But using depency injection means I have to add pdo as a parameter to all of my domain objects on instantiation, which is messy and smells bad. A singleton will allow me to globally access my database connection from within my classes without having to constantly pass pdo to the `__construct` method. If I need to change the database settings I simply change it in the singleton. Without a singleton I simply change it in the original pdo declaration. Or should every one of my classes maintain their own personal db connection? it seems smeeellllyyyyyyy – Abraham Brookes Aug 24 '18 at 02:01
  • @AbrahamBrookes you create one single PDO instance in your bootstrap and pass it to the objects that need it. It's not smelly. It's good practise. Hiding dependencies inside your objects is smelly. – Gordon Aug 24 '18 at 07:55
  • I totally agree hiding dependencies is smelly, but when it's something as ubiquitous as a database connection in an PHP system it seems silly to have to pass it around all the time. I think this is the one exception to the rule. – Abraham Brookes Aug 27 '18 at 02:33
  • @AbrahamBrookes there is nothing difficult or complicated about passing it around, so why not do it the clean way? You don't gain much from hard coupling. – Gordon Aug 27 '18 at 11:23