1
\$\begingroup\$

I think many people for some reason use a singleton class to handle the PDO connection (?) but I was thinking it would be nice to have a parent abstract class initializing the connection and the child DAO classes would just have to extend that class and use a $db variable. Code below (I didn't test it so sorry if there are some syntax mistakes) and more explanation below the code.

abstract class AbstractDao {
    protected static $_db = NULL;

    public function __construct() {
        if(is_null(self::$_db)) {
            throw new MyDBException('No DB connection available');
        }
    }

    public static function loadDB($config) {
        static $lastConfig = NULL;
        if($config == $lastConfig) return self::$_db;
        self::$_db = self::connect($config);
        $lastConfig = $config;
        return self::$_db;
    }

    private static function connect($config) {
        $dsn = sprintf('mysql:dbname=%s;host=%s;port=%d;', $config['database'], $config['hostname'], $config['port']);
        $db = new PDO($dsn, $user, $pass);  
        $db->exec("SET NAMES utf8");
        return $db;
    }
}

The $_db variable is static because I want to avoid to open many connections if I initialize many DAO classes (and thus if the $_db variable is static, I'll have one open connection for all my DAO classes). On the other hand it confuses me a bit because I have seen no one doing something like self::$_db->prepare(...). Maybe I am doing something wrong ?

Note also that loadDB with a default config array needs to be called in the application bootstrap.

\$\endgroup\$
  • \$\begingroup\$ First remove all static keyword you don't need them (static is evil untestable, unreadable and anyone can change a static instance state). If you wan't to use only one database bridge make your classes the way they can accept constructor arguments for example a PDO instance (and use a dependency injection container if needed). \$\endgroup\$ – Peter Kiss Jun 4 '13 at 13:10
  • \$\begingroup\$ But if I remove the static keyword, every child class that I instantiate and that inherit from this parent class, will start a new connection. The static keyword was to avoid that and ensure that one request will create only one connection. What do you think about that? \$\endgroup\$ – Matthew Jun 4 '13 at 14:58
  • \$\begingroup\$ Hah I think you meant I need to create a class somehow containing the connection, and pass this class to my DAO objects? \$\endgroup\$ – Matthew Jun 4 '13 at 15:03
  • \$\begingroup\$ Correct, Peter is saying construct the connection in a dependency container, and have the container ensure that any object requesting a database connection gets the same connection. See my answer. \$\endgroup\$ – Johntron Jun 4 '13 at 15:27
  • \$\begingroup\$ Where is your answer?:) \$\endgroup\$ – Matthew Jun 4 '13 at 15:35
3
\$\begingroup\$

Continuing on Peter Kiss's answer, the static keyword is indeed hard to test, but you don't have to worry about it when it's in a dependency container. If you use Inversion of Control and have your objects ask for a DB connection, rather than getting one, you don't have to worry about using singletons or static class properties – you just let the dependency layer manage connections, and this can be done with a simple static variable inside a closure (see below).

This answer is verbose, because I'm going to assume ignorance and try to explain Dependency Injection in as simple terms as possible (I still haven't found a concise explanation of DI). Afterwards, I'll explain how you can use DI to ensure no more than one DB connection is created.

You need to create a very generic container object to contain dependencies. At runtime, you insert these dependencies into this container (typically before you do anything else). Then, you pass this container to the constructor of any object with a dependency. The object then retrieves the dependency it needs from the container.

This allows the dependent object to be completely ignorant of how the dependency was constructed: e.g. a database object could have been constructed from PDO rather than MySQLi (though you'd want them to share the same API). This aids in making your code much easier to test by keeping object graph construction and application logic separate.

If you're using PHP 5.3+, I'd recommend looking into PIMPLE. Here's how you'd set things up.

// Construct the container and all dependencies
// This is usually done in a separate file

$container = new Pimple();
$container['db'] = function() {
    $db = ...; // Construct the DB connection
    return $db;
}

// Define a class that depends on the DB
class SomeObjectThatUsesDB {
    function __construct($container) {
        $this->container = $container;

        // No `new` keyword – this is a good thing!
    }

    function doSomething() {
        $db = $this->container['db'];
        // Do something with the DB
    }
}

// Use the class to do some work with the DB
$obj = new SomeObjectThatUsesDB($container);
$obj->doSomething();

The advantage here is $container['db'] can be anything as long as it shares the same API.

OK, so now let's talk about ensuring only one DB connection exists at any given time. Since the DI dependencies are just anonymous functions, you can just use the static keyword inside of the dependency definition; however, Pimple makes this even easier by providing a share() method that does this for you. As per the documentation:

By default, each time you get an object, Pimple returns a new instance of it. If you want the same instance to be returned for all calls, wrap your anonymous function with the share() method

Here's what it looks like:

$container['db'] = $container->share(function($container) {
    $db = ...; // Construct the DB connection
    return $db;
});

This will make sure that sure only one $db is ever created. Here's the source code from Pimple's share() method for demonstration purposes:

/**
 * Returns a closure that stores the result of the given closure for
 * uniqueness in the scope of this instance of Pimple.
 *
 * @param Closure $callable A closure to wrap for uniqueness
 *
 * @return Closure The wrapped closure
 */
public static function share(Closure $callable)
{
    return function ($c) use ($callable) {
        static $object;

        if (null === $object) {
            $object = $callable($c);
        }

        return $object;
    };
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.