4
\$\begingroup\$

I'm just trying to master the art of using classes in PHP and have come across a concern.

For security reasons, I sometimes use two database connections in my application; one with read-only privileges and one with full read/write. Unfortunately, I wasn't really thinking about this when I started to build a few classes.

So, I have a database class, which is essentially a pointless PDO wrapper (pointless because PDO is a wrapper), but thought it'd be good practice to write one anyway and I may extend PDO later too. This is what I did:

<?php
class Database {

    private $dbh;

    public function __construct($accessLevel = NULL, $credentials = NULL) {
        if (gettype($credentials) === 'array') {
            $dsn = 'mysql:host='.$credentials['dbHost'].';dbname='.$credentials['dbName'];
            $options = array (
                PDO::ATTR_PERSISTENT => false,
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
            );
            if ($accessLevel == "public") {
                $this->dbh = new PDO($dsn, $credentials['publicUser'], $credentials['publicPassword'], $options);
            } else if ($accessLevel == "private") {
                $this->dbh = new PDO($dsn, $credentials['privateUser'], $credentials['privatePassword'], $options);
            }
        }
    }

    // other database functions

}
?>

For a public connection (read/write), I simply used this:

$db = new Database('public', config['dbConfig']);

... or for a private connection (read-only), I used this:

$db = new Database('private', config['dbConfig']);

... and when I wanted to use the connection in another class, I simply injected the connection, like so:

$user = new User($db);

Works fine, but then I realised that I may need two connections inside that class, one for reading only and one for all. So I got a little confused, but had a go regardless. This is what I did:

Instead of calling the class with either of the connections, I called the class twice with both, like so:

$publicDB = new Database('public', $config['db']);
$privateDB = new Database('private', $config['db']);

and then injected both of those to my other class:

$user = new User($publicDB, $privateDB);

Inside this User class, this how I used the two connections:

<?php
class User {

    private $dbh;
    private $dbhp;

    public function __construct($publicDatabase = NULL, $privateDatabase = NULL) {
        $this->dbh = $publicDatabase;
        $this->dbhp = $privateDatabase;
    }

    public function doSomething() {
        $this->dbh->query("INSERT INTO......");
        $this->dbh->bind(':userInput', $userInput);
        $this->dbh->execute();
    }

    public function doSomethingSafely() {
        $this->dbhp->query("SELECT * FROM......");
        $this->dbhp->bind(':userInput', $userInput);
        $results = $this->dbhp->resultset();
    }
}
?>

Right, this actually works fine but I'm worried it's not the acceptable method or it may cause problems later down the road. I have a few questions:

  1. Is using two connections with different privileges still considered good practice? Even though I'm properly escaping and validating my data and binding the values using PDO?

  2. If yes for above, is there a better way to manage the two connections for using in many classes or is what I have done acceptable?

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

You want two classes. They extends Database. PublicDatabase and PrivateDatabase, you instantiate whichever you need, and inject whichever you need. That way you can even have the same signature in the constructor in both types.

If you don't have a preference, you just need a database connection in a function, just ask for Database, and either will fit.

Also

  • You don't need to use gettype, you can hint in the function's signature for the type, it works for all Objects and array. So for example:

    public function __construct(array $accessLevel, array $credentials) {
    
  • Don't give defaults if you can't handle defaults. Your object won't work if $credentials is empty, don't let it be.

  • What do your Database object solve? What kind of abstraction it provides? Usually such 'utility' classes are pointless.

Better approach

Your User shouldn't be responsible to save/fetch itself. In fact, if you have a User which doesn't care where it came from, you have a perfectly reusable object, a User that can come from a database, a file, a session, a form, whatever, and work the same.

You should have two objects: the User and the UserMapper, the UserMapper is responsible for fetching information about users and fill them into User objects, as well as saving information from the User object back into the database.

The idea of OOP is separation of responsibilities.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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