3

I'm attempting to design my website based on OOP, but I'm having trouble with how to design the database connection. Currently, I'm creating a private static PDO object in an abstract class, Connector. Obviously, anything that needs to interact with the database will extend this class. I've been flipping back and forth on how to make sure that there is only ever one connection, or PDO object, in a script because some pages will need more than one class that extends Connector. Many people seem to recommend a Singleton pattern for this purpose, but the way I currently do it seems to accomplish the same thing.

Here's my current code.

abstract class Connector
{
    private static $dbh;

    public function __construct()
    {
        try
        {
            self::$dbh = new PDO(...);
            self::$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        }
        catch(PDOException $e)
        {
            die($e->getMessage());
        }
    }

    public function getDB()
    {
        return self::$dbh;
    }
}

Then any subclasses would use it like this.

class Subclass extends Connector
{
    public function interactWithDB()
    {
        $stmt = $this->getDB()->prepare(...);
        // etc...
    }
}

Theoretically, each instance of a subclass should always be accessing the same PDO instance, I think. Does this code actually make sense and or am I misunderstanding static properties somehow? Is it bad design/practice and or does Singleton have more advantages?

Comment if something's not clear, thanks!

EDIT:

The Connector class wasn't meant to exist just to hold the PDO object. It's destructor closes the connection(makes it null) and it contains functions such isValueTaken, which checks if a value is already in the database. It has the following abstract functions

abstract function retrieveData();
abstract function setData();

For example I have a User class that extends Connector. It's defines setData() to register a user in the database. I don't know if this makes a difference to the response.

shaqb4
  • 283
  • 1
  • 3
  • 11
  • 8
    "Obviously, anything that needs to interact with the database will extend this class." --- it's not obvious at all. You need to use a spoon, you don't need to be a spoon for that, do you? Hint: google for dependency injection and delegation. PS: http://pimple.sensiolabs.org/ – zerkms Nov 07 '13 at 22:55
  • 1
    I would recommend to use something like [this approach](http://stackoverflow.com/a/11369679/727208) instead. – tereško Nov 07 '13 at 23:49
  • @zerkms pimple implements a service provider, which is another anti-pattern. And service provider **is not** DI container. – tereško Nov 07 '13 at 23:52
  • 1
    @tereško: any "real" opensource php DI container's available around? – zerkms Nov 07 '13 at 23:57
  • @zerkms https://github.com/rdlowrey/Auryn – PeeHaa Nov 08 '13 at 00:00
  • @zerkms, [Auryn](https://github.com/rdlowrey/Auryn) seems a good implementation. Please look into what is the difference between *service locator* and *dependency injection container*. It should give you some understanding of why I called it an anti-pattern and what are the limitations of both. – tereško Nov 08 '13 at 00:01
  • 1
    @tereško: I do realize the difference. And I can live with "limitations" of sf2 service container – zerkms Nov 08 '13 at 00:15

3 Answers3

7

Obviously, anything that needs to interact with the database will extend this class.

This really makes no sense from an OOP perspective. When some class extends another class that implies an "is a" relationship. If you go this route you are going to have a hard time not violating OCP which is one of the letters in SOLID.

I've been flipping back and forth on how to make sure that there is only ever one connection, or PDO object, in a script because some pages will need more than one class that extends Connector.

Easy! Just create one instance.

Many people seem to recommend a Singleton pattern for this purpose, but the way I currently do it seems to accomplish the same thing.

Many people like that have no clue about OOP principles. Using an singleton just introduces a "fancy" global instance / state.

Does this code actually make sense and or am I misunderstanding static properties somehow?

To be honest it is more a misunderstanding of OOP.

Is it bad design/practice and or does Singleton have more advantages?

See above.


What you should do (in OOP) is inject the database connection into the classes that need it. This makes your code loosely coupled which in turn makes your code better maintainable, testable, debuggable and flexible.

Also I don't really see why you need to create a database class for a pdo connection, because the PDO API itself is already OOP. So unless you have a real reason to write a adapter (can be the case, because there are some) for PDO I would just drop it.

My €0.02

--

In response to your edit:

The Connector class wasn't meant to exist just to hold the PDO object. It's destructor closes the connection(makes it null).

There is often no need at all to close the connection. The connection will automatically be closed once the request is handled (unless we are talking about a persistent connection).

and it contains functions such isValueTaken, which checks if a value is already in the database. It has the following abstract functions

That sounds like a job for another class.

For example I have a User class that extends Connector. It's defines setData() to register a user in the database. I don't know if this makes a difference to the response.

No my point still stands. There is no need for a user to inherit from a database. Doesn't that sounds strange. A user inheriting from a database (I don't want to meet that person). You should inject the database connection into the User if it is needed.

Community
  • 1
  • 1
PeeHaa
  • 71,436
  • 58
  • 190
  • 262
  • I edited my question to contain more information. If your answer still applies, would it make sense to keep the abstract class, but pass the PDO object as an argument to any functions that need it rather than keep it as a property? – shaqb4 Nov 07 '13 at 23:43
  • Other than attempting to keep the PDO instance in scope any time it's required for dependency injection, have you got any tips on how to make it available? – Phil Nov 07 '13 at 23:59
  • For almost all of my projects I can get away with just keeping it "in scope". There are some times I have used a DIC. And when I say DIC I don't mean pass the entire container into everything and turn it into a service locator, but rather a DIC in the way of wire up my stuff for me. – PeeHaa Nov 08 '13 at 00:02
3

Preface

The singleton approach is generally frowned upon. You will be set upon by raptors.


What you're essentially asking is how to configure a connection and make it globally available. This is generally referred to as global state. You can achieve this using a container class and static methods.

Here's an example

namespace Persistence\Connection\Config;

interface PDOConfig {
    public function getDSN();
    public function getUsername();
    public function getPassword();
    public function getDriverOptions();
}

class MySqlConfig implements PDOConfig {
    private $username;
    private $password;
    private $db;
    private $host = 'localhost';
    private $charset = 'utf8';

    public function __construct($username, $password, $db) {
        $this->username = $username;
        $this->password = $password;
        $this->db = $db;
    }

    // getters and setters, etc

    public function getDSN() {
        return sprintf('mysql:host=%s;dbname=%s;charset=%s',
            $this->host, $this->db, $this->charset);
    }

    public function getDriverOptions() {
        return [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_EMULATE_PREPARES => false,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
        ];
    }
}

namespace Persistence\Connection;

use Persistence\Connection\Config\PDOConfig;

class Registry {
    private static $connection;
    private static $config;

    public static function setConfig(PDOConfig $config) {
        self::$config = $config;
    }

    public static getConnection() {
        if (self::$connection === null) {
            if (self::$config === null) {
                throw new RuntimeException('No config set, cannot create connection');
            }
            $config = self::$config;
            self::$connection = new \PDO($config->getDSN(), $config->getUsername(),
                $config->getPassword(), $config->getDriverOptions());
        }
        return self::$connection;
    }
}

Then, at some point (early) in your application execution cycle

use Persistence\Connection\Config\MySqlConfig;
use Persistence\Connection\Registry;

$config = new MySqlConfig('username', 'password', 'dbname');
Registry::setConfig($config);

Then, you can use

Registry::getConnection();

anywhere in your code to retrieve the PDO instance.

Phil
  • 157,677
  • 23
  • 242
  • 245
  • 1
    "I know sometimes singleton is a dirty word but in any case, you're in need of some sort of global state. You could also use a registry pattern but that's really up to you." Wut? No and no. – PeeHaa Nov 07 '13 at 23:18
  • @PeeHaa OP is asking how to configure a global state. Look at any popular framework (and not just PHP) and you'll see some form of static, connection pool / registry. Otherwise, you'd need to implement a service locator or DI container and that's an even bigger task – Phil Nov 07 '13 at 23:21
  • 1
    OP is asking about OOP. Neither a singleton nor a registry has anything to do with OOP. And yes you are correct most frameworks also have not much to do with OOP. – PeeHaa Nov 07 '13 at 23:23
  • @zerkms I've used it extensively. The [service container](http://symfony.com/doc/current/book/service_container.html) is the same sort of thing. Just another globally available container / registry – Phil Nov 07 '13 at 23:36
  • @Phil: it's not a registry, a static storage or a service locator in any way. It's a pure DI container. PS: it's not "globally available", it's passed as a dependency – zerkms Nov 07 '13 at 23:37
  • @zerkms now you're making me dig through code. I will find it :) – Phil Nov 07 '13 at 23:37
  • @zerkms Ok, maybe not *globally available* but available to anything that extends `ContainerAware` which gets the service container injected by the `Kernel`. In this case, the `Kernel` manages the application's state. As I doubt the OP wants to go to this level, I see nothing wrong with a simple, static PDO registry – Phil Nov 07 '13 at 23:41
  • I don't see anything terribly wrong with it as well, especially for not experienced developers. – zerkms Nov 07 '13 at 23:46
  • 1
    @Phil a static registry with "on purpose" is a singleton. – tereško Nov 07 '13 at 23:50
  • @zerkms It is indeed however the OP would still run into the issue of *"how do I get my `$container` into all my other classes / scopes / etc"* – Phil Nov 07 '13 at 23:53
  • 1
    @tereško I was going to say it's not a singleton but a static, lazy loading instantiator. Then I realised, that is in fact, a singleton :) – Phil Nov 07 '13 at 23:56
1

Just a side note if anyone is reading this, if anybody is using the above code snippet by Phil, remember to use a black slash infront of PDO inside getDriverOptions() so as to refer to the global namespace. it should look like this.

public function getDriverOptions() {
      return [
          \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
          \PDO::ATTR_EMULATE_PREPARES => false,
          \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC
      ];
  }
Bruce Tong
  • 1,366
  • 15
  • 14