diff --git a/.gitignore b/.gitignore index f4686f8c..10bd3af2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /.idea/ /vendor/ /composer.lock +composer.phar diff --git a/src/Codeception/Lib/Driver/Db.php b/src/Codeception/Lib/Driver/Db.php index fb1a2228..c08b042d 100755 --- a/src/Codeception/Lib/Driver/Db.php +++ b/src/Codeception/Lib/Driver/Db.php @@ -32,10 +32,36 @@ class Db * @var array */ protected $primaryKeys = []; + /** + * @var string|null + */ + protected $pdo_class; + + /** + * @param string|null $pdo_class + * @return string + */ + private static function pdoClass($pdo_class){ + if (!$pdo_class){ + // If empty or null we use regular PDO + return \PDO::class; + } + + if (!class_exists($pdo_class)){ + throw new ModuleException( + 'Codeception\Module\Db', + "The class with provided config value 'pdo_class' ($pdo_class) does not exist" + ); + } - public static function connect($dsn, $user, $password, $options = null) + return $pdo_class; + } + + public static function connect($dsn, $user, $password, $options = null, $pdo_class = null) { - $dbh = new \PDO($dsn, $user, $password, $options); + $class_name = self::pdoClass($pdo_class); + $dbh = new $class_name($dsn, $user, $password, $options); + self::assertIsPdo($dbh, $pdo_class); $dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); return $dbh; @@ -48,31 +74,32 @@ public static function connect($dsn, $user, $password, $options = null) * @param $user * @param $password * @param [optional] $options + * @param [optional] $pdo_class * * @see http://php.net/manual/en/pdo.construct.php * @see http://php.net/manual/de/ref.pdo-mysql.php#pdo-mysql.constants * * @return Db|SqlSrv|MySql|Oci|PostgreSql|Sqlite */ - public static function create($dsn, $user, $password, $options = null) + public static function create($dsn, $user, $password, $options = null, $pdo_class = null) { $provider = self::getProvider($dsn); switch ($provider) { case 'sqlite': - return new Sqlite($dsn, $user, $password, $options); + return new Sqlite($dsn, $user, $password, $options, $pdo_class); case 'mysql': - return new MySql($dsn, $user, $password, $options); + return new MySql($dsn, $user, $password, $options, $pdo_class); case 'pgsql': - return new PostgreSql($dsn, $user, $password, $options); + return new PostgreSql($dsn, $user, $password, $options, $pdo_class); case 'mssql': case 'dblib': case 'sqlsrv': - return new SqlSrv($dsn, $user, $password, $options); + return new SqlSrv($dsn, $user, $password, $options, $pdo_class); case 'oci': - return new Oci($dsn, $user, $password, $options); + return new Oci($dsn, $user, $password, $options, $pdo_class); default: - return new Db($dsn, $user, $password, $options); + return new Db($dsn, $user, $password, $options, $pdo_class); } } @@ -86,19 +113,37 @@ public static function getProvider($dsn) * @param $user * @param $password * @param [optional] $options + * @param [optional] $pdo_class * * @see http://php.net/manual/en/pdo.construct.php * @see http://php.net/manual/de/ref.pdo-mysql.php#pdo-mysql.constants */ - public function __construct($dsn, $user, $password, $options = null) + public function __construct($dsn, $user, $password, $options = null, $pdo_class = null) { - $this->dbh = new \PDO($dsn, $user, $password, $options); + $class_name = self::pdoClass($pdo_class); + $this->dbh = new $class_name($dsn, $user, $password, $options); + self::assertIsPdo($this->dbh, $pdo_class); $this->dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $this->dsn = $dsn; $this->user = $user; $this->password = $password; $this->options = $options; + $this->pdo_class = $pdo_class; + } + + /** + * @param $dbh + * @param string|null $pdo_class + */ + private static function assertIsPdo($dbh, $pdo_class) + { + if (!$dbh instanceof \PDO){ + throw new ModuleException( + 'Codeception\Module\Db', + "The provided config value 'pdo_class' ($pdo_class) did not resolve to a class that implements \\PDO" + ); + } } public function __destruct() diff --git a/src/Codeception/Lib/Driver/Sqlite.php b/src/Codeception/Lib/Driver/Sqlite.php index d33c26be..607df466 100644 --- a/src/Codeception/Lib/Driver/Sqlite.php +++ b/src/Codeception/Lib/Driver/Sqlite.php @@ -10,7 +10,7 @@ class Sqlite extends Db protected $filename = ''; protected $con = null; - public function __construct($dsn, $user, $password, $options = null) + public function __construct($dsn, $user, $password, $options = null, $pdo_class = null) { $filename = substr($dsn, 7); if ($filename === ':memory:') { @@ -19,7 +19,7 @@ public function __construct($dsn, $user, $password, $options = null) $this->filename = Configuration::projectDir() . $filename; $this->dsn = 'sqlite:' . $this->filename; - parent::__construct($this->dsn, $user, $password, $options); + parent::__construct($this->dsn, $user, $password, $options, $pdo_class); } public function cleanup() @@ -27,7 +27,7 @@ public function cleanup() $this->dbh = null; gc_collect_cycles(); file_put_contents($this->filename, ''); - $this->dbh = self::connect($this->dsn, $this->user, $this->password); + $this->dbh = self::connect($this->dsn, $this->user, $this->password, $this->pdo_class); } public function load($sql) diff --git a/src/Codeception/Module/Db.php b/src/Codeception/Module/Db.php index 9414278b..f37d586f 100644 --- a/src/Codeception/Module/Db.php +++ b/src/Codeception/Module/Db.php @@ -55,6 +55,7 @@ * * ssl_cipher - list of one or more permissible ciphers to use for SSL encryption (MySQL specific, @see http://php.net/manual/de/ref.pdo-mysql.php#pdo.constants.mysql-attr-cipher) * * databases - include more database configs and switch between them in tests. * * initial_queries - list of queries to be executed right after connection to the database has been initiated, i.e. creating the database if it does not exist or preparing the database collation + * * pdo_class - a fully qualified class name of a class which extends \PDO. This allows for custom stubbing of PDO to provide customised interaction with the database, or alternative implementations which may aid in test debugging or test speed * * ## Example * @@ -570,7 +571,8 @@ private function connect($databaseKey, $databaseConfig) try { $this->debugSection('Connecting To Db', ['config' => $databaseConfig, 'options' => $options]); - $this->drivers[$databaseKey] = Driver::create($databaseConfig['dsn'], $databaseConfig['user'], $databaseConfig['password'], $options); + $pdo_class = array_key_exists('pdo_class', $databaseConfig) ? $databaseConfig['pdo_class'] : null; + $this->drivers[$databaseKey] = Driver::create($databaseConfig['dsn'], $databaseConfig['user'], $databaseConfig['password'], $options, $pdo_class); } catch (\PDOException $e) { $message = $e->getMessage(); if ($message === 'could not find driver') { diff --git a/tests/data/sqlite.db b/tests/data/sqlite.db index 6c7c70e4..e43da89c 100644 Binary files a/tests/data/sqlite.db and b/tests/data/sqlite.db differ