0
\$\begingroup\$

I'm writing one custom class for PDO connection.

I want these advantages:

  • Protect credentials: user, password ...
  • Facilitate maintenance: facing future password changes for example
  • Facilitate connection: $pdo=new MyPDO();
  • Provide my class with all the PDO functionalities

I write my class as you can see here:

For protecting credentials I saved them into an ini file:

<?php return; ?>
; credentials
host=localhost
user=myUser
pass="my/very/secure/password.../UqMsN[)VPn&gunmv3KzE?3Q&Qw/..."
dbname=myDataBase

The class

class MyPDO extends PDO
{

    public function __construct()
    {
        $iniData = parse_ini_file("//home/.credentials/db.php.ini");
        $host=$iniData["host"];
        $dbname=$iniData["dbname"];
        $user=$iniData["user"];
        $pass=$iniData["pass"];
        $dsn = "mysql:host=$host;dbname=$dbname";


        $options = array(
            PDO::ATTR_PERSISTENT => FALSE, 
            PDO::ATTR_EMULATE_PREPARES => FALSE, 
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'"
        );

        try {
                parent::__construct($dsn, $user, $pass, $options);
        } catch (PDOException $e) {
            error_log($this->error = $e->getMessage(),0);
        }
    }
}
?>

Usage example

include_once('MyPDO.php');
$pdo=new MyPDO();
/*Use any PDO method*/

$pdo->query(...);
$pdo->prepare(...);
$pdo->execute(...);
\$\endgroup\$
  • \$\begingroup\$ So you actually exposed your credentials to anyone who wold have a whim of navigating to .credentials/db.php.ini \$\endgroup\$ – Your Common Sense Dec 12 '18 at 12:38
  • 1
    \$\begingroup\$ @YourCommonSense thaks for your comment. The credentials are not exposed. 1º. The folder is out of public_html, then, non navigation possible (i'm using shared hosting). 2º. The first line: <?php return; ?> prevents to show the file content in case of access to fila via browser. I think the info is protected so. \$\endgroup\$ – A. Cedano Dec 12 '18 at 12:42
2
\$\begingroup\$

There are several areas for improvement.

  • First of all, it violates the Liskov substitution principle. I am guilty for doing it too, so I cannot blame you too much but if you want your code to follow the best practices, it's better to make your class not extend PDO but either
    • make a PDO instance a property of your class publicly accessible through a property or a method.
    • or - if you want to have a class with PDO's functionality but a different constructor, you have to re-create in your class all the functionality supported by PDO. Although sounds too laborious, it is not that hard as it seems but it pays back in the future.
  • Next, error reporting for the connection is rather inflexible. An exception is a precious thing that can be handled in many different ways, logging included. So I would rather re-throw a new exception, like

    try {
        parent::__construct($dsn, $user, $pass, $options);
    } catch (PDOException $e) {
        throw new \PDOException($e->getMessage(), (int)$e->getCode());
    }
    

    so it won't expose the connection credentials in the stack trace but still it can be caught elsewhere or simply logged if a corresponding PHP configuration directive says so.

  • Connection encoding is better to be set in the DSN, as it's going to be more generic and supported by all drivers.
  • reading the configuration right in the class violates the Single responsibility principle. I would make this class to accept an array of parameters, as to where these parameters are taken from - an .ini, .yml or .env file - is a distinct matter.
  • "Facilitate maintenance" option is somewhat defeated by the hardcoding of the configuration file name. What if you want to change it in the future as well? A typical web application has dozens of configuration options, it's just impractical to put them in different files whose names are hardcoded in different modules. To make a maintainable application it's better to have a distinct module for configuration that will supply different options to all other modules.
  • And all the hassle related to protecting the configuration should be delegated elsewhere. After all, database credentials are not only settings that have to be protected - there are admin email, salt, API keys, etc.
  • protecting an ini file adding .php as one of its extensions is too risky. It would work merely by accident (It would work only under Apache web-server if a certain configuration option is set). Why not to name it straight settings.php and thus make sure it will be always interpreted as PHP as long as it is called through a web-server with PHP support?
  • I would also add a possibility to add/override the PDO options

So I would make your class

class DB
{
    protected $connection;
    public function __construct($config)
    {
        $dsn = "mysql:host=$config[host];dbname=$config[dbname];charset=$config[charset]";

        $options = array(
            PDO::ATTR_PERSISTENT => FALSE,
            PDO::ATTR_EMULATE_PREPARES => FALSE,
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        );
        if (isset($config['options'])) {
            $options = array_merge($options, $config['options']);
        }

        try {
            $this->connection = new \PDO ($dsn, $config['user'], $config['pass'], $options);
        } catch (\PDOException $e) {
            throw new \PDOException($e->getMessage(), (int)$e->getCode());
        }
    }
    public function conn() {
        return $this->connection;
    }
}

used as

$db = new DB($config);
$stmt = $db->conn()->query('SELECT * FROM users');
\$\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.