From 1eec70d3e7a70fc1bd5095683a48280d7f3f370a Mon Sep 17 00:00:00 2001 From: m1ke Date: Wed, 3 Feb 2021 09:24:16 +0000 Subject: [PATCH 01/10] Add fake pdo option to driver --- src/Codeception/Lib/Driver/Db.php | 42 +++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/src/Codeception/Lib/Driver/Db.php b/src/Codeception/Lib/Driver/Db.php index fb1a2228..25e48fe7 100755 --- a/src/Codeception/Lib/Driver/Db.php +++ b/src/Codeception/Lib/Driver/Db.php @@ -3,9 +3,12 @@ namespace Codeception\Lib\Driver; use Codeception\Exception\ModuleException; +use Vimeo\MysqlEngine\FakePdo; class Db { + const FAKE_PDO = 'fake:'; + /** * @var \PDO */ @@ -35,8 +38,14 @@ class Db public static function connect($dsn, $user, $password, $options = null) { - $dbh = new \PDO($dsn, $user, $password, $options); - $dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + list($real_dsn, $fake) = self::checkForFakePdo($dsn); + if ($fake){ + $dbh = new FakePdo($real_dsn, $user, $password, $options); + } + else { + $dbh = new \PDO($dsn, $user, $password, $options); + $dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + } return $dbh; } @@ -92,10 +101,17 @@ public static function getProvider($dsn) */ public function __construct($dsn, $user, $password, $options = null) { - $this->dbh = new \PDO($dsn, $user, $password, $options); - $this->dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + list($real_dsn, $fake) = self::checkForFakePdo($dsn); + + if ($fake){ + $this->dbh = new FakePdo($real_dsn, $user, $password, $options); + } + else { + $this->dbh = new \PDO($dsn, $user, $password, $options); + $this->dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + } - $this->dsn = $dsn; + $this->dsn = $real_dsn; $this->user = $user; $this->password = $password; $this->options = $options; @@ -353,4 +369,20 @@ public function getOptions() { return $this->options; } + + /** + * @param string $dsn + * + * @return array{string, bool} + */ + private static function checkForFakePdo($dsn) + { + if (strpos($dsn, self::FAKE_PDO)===0){ + $real_dsn = substr($dsn, strlen(self::FAKE_PDO)); + + return [$real_dsn, true]; + } + + return [$dsn, false]; + } } From a47ec7c18f1e892708347e8203cd1edf8b3eeb66 Mon Sep 17 00:00:00 2001 From: m1ke Date: Wed, 3 Feb 2021 09:24:33 +0000 Subject: [PATCH 02/10] ignore composer.phar in gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f4686f8c..10bd3af2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /.idea/ /vendor/ /composer.lock +composer.phar From d25537da569a1f6859952ec1d521f04af18612b8 Mon Sep 17 00:00:00 2001 From: m1ke Date: Wed, 3 Feb 2021 09:39:32 +0000 Subject: [PATCH 03/10] Ensure correct sub-driver is used when fake is chosen --- src/Codeception/Lib/Driver/Db.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Codeception/Lib/Driver/Db.php b/src/Codeception/Lib/Driver/Db.php index 25e48fe7..d863beee 100755 --- a/src/Codeception/Lib/Driver/Db.php +++ b/src/Codeception/Lib/Driver/Db.php @@ -65,7 +65,8 @@ public static function connect($dsn, $user, $password, $options = null) */ public static function create($dsn, $user, $password, $options = null) { - $provider = self::getProvider($dsn); + list($real_dsn) = self::checkForFakePdo($dsn); + $provider = self::getProvider($real_dsn); switch ($provider) { case 'sqlite': From d4e77bdbd8b745dfe8043797918eb4a339cbb1ff Mon Sep 17 00:00:00 2001 From: m1ke Date: Wed, 3 Feb 2021 11:44:17 +0000 Subject: [PATCH 04/10] Spaces instead of tabs --- src/Codeception/Lib/Driver/Db.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Codeception/Lib/Driver/Db.php b/src/Codeception/Lib/Driver/Db.php index d863beee..342d962f 100755 --- a/src/Codeception/Lib/Driver/Db.php +++ b/src/Codeception/Lib/Driver/Db.php @@ -65,7 +65,7 @@ public static function connect($dsn, $user, $password, $options = null) */ public static function create($dsn, $user, $password, $options = null) { - list($real_dsn) = self::checkForFakePdo($dsn); + list($real_dsn) = self::checkForFakePdo($dsn); $provider = self::getProvider($real_dsn); switch ($provider) { @@ -102,7 +102,7 @@ public static function getProvider($dsn) */ public function __construct($dsn, $user, $password, $options = null) { - list($real_dsn, $fake) = self::checkForFakePdo($dsn); + list($real_dsn, $fake) = self::checkForFakePdo($dsn); if ($fake){ $this->dbh = new FakePdo($real_dsn, $user, $password, $options); @@ -372,18 +372,18 @@ public function getOptions() } /** - * @param string $dsn - * - * @return array{string, bool} - */ + * @param string $dsn + * + * @return array{string, bool} + */ private static function checkForFakePdo($dsn) - { - if (strpos($dsn, self::FAKE_PDO)===0){ - $real_dsn = substr($dsn, strlen(self::FAKE_PDO)); + { + if (strpos($dsn, self::FAKE_PDO)===0){ + $real_dsn = substr($dsn, strlen(self::FAKE_PDO)); - return [$real_dsn, true]; - } + return [$real_dsn, true]; + } - return [$dsn, false]; - } + return [$dsn, false]; + } } From de7413c01154e98ba32d5d3ecad214613a5c403b Mon Sep 17 00:00:00 2001 From: m1ke Date: Wed, 3 Feb 2021 11:45:19 +0000 Subject: [PATCH 05/10] Spaces instead of tabs (again) --- src/Codeception/Lib/Driver/Db.php | 34 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Codeception/Lib/Driver/Db.php b/src/Codeception/Lib/Driver/Db.php index 342d962f..78bfbe13 100755 --- a/src/Codeception/Lib/Driver/Db.php +++ b/src/Codeception/Lib/Driver/Db.php @@ -7,7 +7,7 @@ class Db { - const FAKE_PDO = 'fake:'; + const FAKE_PDO = 'fake:'; /** * @var \PDO @@ -38,14 +38,14 @@ class Db public static function connect($dsn, $user, $password, $options = null) { - list($real_dsn, $fake) = self::checkForFakePdo($dsn); - if ($fake){ - $dbh = new FakePdo($real_dsn, $user, $password, $options); - } - else { - $dbh = new \PDO($dsn, $user, $password, $options); - $dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); - } + list($real_dsn, $fake) = self::checkForFakePdo($dsn); + if ($fake){ + $dbh = new FakePdo($real_dsn, $user, $password, $options); + } + else { + $dbh = new \PDO($dsn, $user, $password, $options); + $dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + } return $dbh; } @@ -104,13 +104,13 @@ public function __construct($dsn, $user, $password, $options = null) { list($real_dsn, $fake) = self::checkForFakePdo($dsn); - if ($fake){ - $this->dbh = new FakePdo($real_dsn, $user, $password, $options); - } - else { - $this->dbh = new \PDO($dsn, $user, $password, $options); - $this->dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); - } + if ($fake){ + $this->dbh = new FakePdo($real_dsn, $user, $password, $options); + } + else { + $this->dbh = new \PDO($dsn, $user, $password, $options); + $this->dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + } $this->dsn = $real_dsn; $this->user = $user; @@ -376,7 +376,7 @@ public function getOptions() * * @return array{string, bool} */ - private static function checkForFakePdo($dsn) + private static function checkForFakePdo($dsn) { if (strpos($dsn, self::FAKE_PDO)===0){ $real_dsn = substr($dsn, strlen(self::FAKE_PDO)); From 322f5abaa79716d9176faa4efd14f6ec3c6ebbf0 Mon Sep 17 00:00:00 2001 From: m1ke Date: Thu, 4 Feb 2021 09:34:08 +0000 Subject: [PATCH 06/10] Rebuild concept around pdo injection to allow class fqn to be provided --- src/Codeception/Lib/Driver/Db.php | 87 ++++++++++++--------------- src/Codeception/Lib/Driver/Sqlite.php | 6 +- src/Codeception/Module/Db.php | 2 +- 3 files changed, 44 insertions(+), 51 deletions(-) diff --git a/src/Codeception/Lib/Driver/Db.php b/src/Codeception/Lib/Driver/Db.php index 78bfbe13..68af5b3c 100755 --- a/src/Codeception/Lib/Driver/Db.php +++ b/src/Codeception/Lib/Driver/Db.php @@ -3,12 +3,9 @@ namespace Codeception\Lib\Driver; use Codeception\Exception\ModuleException; -use Vimeo\MysqlEngine\FakePdo; class Db { - const FAKE_PDO = 'fake:'; - /** * @var \PDO */ @@ -35,18 +32,34 @@ class Db * @var array */ protected $primaryKeys = []; + /** + * @var string|null + */ + protected $pdo_class; - public static function connect($dsn, $user, $password, $options = null) - { - list($real_dsn, $fake) = self::checkForFakePdo($dsn); - if ($fake){ - $dbh = new FakePdo($real_dsn, $user, $password, $options); + /** + * @param $pdo_class + * @return string + */ + private static function pdoClass($pdo_class){ + $fallback = \PDO::class; + if (!$pdo_class){ + return $fallback; } - else { - $dbh = new \PDO($dsn, $user, $password, $options); - $dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + + if (!class_exists($pdo_class)){ + return $fallback; } + return $pdo_class; + } + + public static function connect($dsn, $user, $password, $options = null, $pdo_class = null) + { + $class_name = self::pdoClass($pdo_class); + $dbh = new $class_name($dsn, $user, $password, $options); + $dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + return $dbh; } @@ -57,32 +70,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) { - list($real_dsn) = self::checkForFakePdo($dsn); - $provider = self::getProvider($real_dsn); + $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); } } @@ -96,26 +109,22 @@ 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) { - list($real_dsn, $fake) = self::checkForFakePdo($dsn); + $class_name = self::pdoClass($pdo_class); + $this->dbh = new $class_name($dsn, $user, $password, $options); + $this->dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); - if ($fake){ - $this->dbh = new FakePdo($real_dsn, $user, $password, $options); - } - else { - $this->dbh = new \PDO($dsn, $user, $password, $options); - $this->dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); - } - - $this->dsn = $real_dsn; + $this->dsn = $dsn; $this->user = $user; $this->password = $password; $this->options = $options; + $this->pdo_class = $pdo_class; } public function __destruct() @@ -370,20 +379,4 @@ public function getOptions() { return $this->options; } - - /** - * @param string $dsn - * - * @return array{string, bool} - */ - private static function checkForFakePdo($dsn) - { - if (strpos($dsn, self::FAKE_PDO)===0){ - $real_dsn = substr($dsn, strlen(self::FAKE_PDO)); - - return [$real_dsn, true]; - } - - return [$dsn, false]; - } } 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..aa0c2113 100644 --- a/src/Codeception/Module/Db.php +++ b/src/Codeception/Module/Db.php @@ -570,7 +570,7 @@ 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); + $this->drivers[$databaseKey] = Driver::create($databaseConfig['dsn'], $databaseConfig['user'], $databaseConfig['password'], $options, $databaseConfig['pdo']); } catch (\PDOException $e) { $message = $e->getMessage(); if ($message === 'could not find driver') { From 762375a0edee1a24e9eadf5ac06b28d321458e9a Mon Sep 17 00:00:00 2001 From: m1ke Date: Thu, 4 Feb 2021 09:44:12 +0000 Subject: [PATCH 07/10] Add asserts to ensure we get an instance of PDO --- src/Codeception/Lib/Driver/Db.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Codeception/Lib/Driver/Db.php b/src/Codeception/Lib/Driver/Db.php index 68af5b3c..651fd305 100755 --- a/src/Codeception/Lib/Driver/Db.php +++ b/src/Codeception/Lib/Driver/Db.php @@ -58,6 +58,7 @@ public static function connect($dsn, $user, $password, $options = null, $pdo_cla { $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; @@ -118,6 +119,7 @@ public function __construct($dsn, $user, $password, $options = null, $pdo_class { $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; @@ -127,6 +129,20 @@ public function __construct($dsn, $user, $password, $options = null, $pdo_class $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' ($pdo_class) did not resolve to a class that implements \\PDO" + ); + } + } + public function __destruct() { if ($this->dbh->inTransaction()) { From 36bea6952eb09949c00c46a271364fb2adb92baa Mon Sep 17 00:00:00 2001 From: m1ke Date: Thu, 4 Feb 2021 09:46:22 +0000 Subject: [PATCH 08/10] Changed config 'pdo' to 'pdo_class' to make it clearer that it is a class name. Also add option to config docs at the top --- src/Codeception/Module/Db.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Codeception/Module/Db.php b/src/Codeception/Module/Db.php index aa0c2113..194f9028 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,7 @@ 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, $databaseConfig['pdo']); + $this->drivers[$databaseKey] = Driver::create($databaseConfig['dsn'], $databaseConfig['user'], $databaseConfig['password'], $options, $databaseConfig['pdo_class']); } catch (\PDOException $e) { $message = $e->getMessage(); if ($message === 'could not find driver') { From 36d39c68a7beb30d5958d73b2aa330e33b6cd75a Mon Sep 17 00:00:00 2001 From: m1ke Date: Thu, 4 Feb 2021 09:52:13 +0000 Subject: [PATCH 09/10] Ensure phpunit tests pass by declaring optional config value, and also modify class not exists to throw exception --- src/Codeception/Lib/Driver/Db.php | 13 ++++++++----- src/Codeception/Module/Db.php | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Codeception/Lib/Driver/Db.php b/src/Codeception/Lib/Driver/Db.php index 651fd305..c08b042d 100755 --- a/src/Codeception/Lib/Driver/Db.php +++ b/src/Codeception/Lib/Driver/Db.php @@ -38,17 +38,20 @@ class Db protected $pdo_class; /** - * @param $pdo_class + * @param string|null $pdo_class * @return string */ private static function pdoClass($pdo_class){ - $fallback = \PDO::class; if (!$pdo_class){ - return $fallback; + // If empty or null we use regular PDO + return \PDO::class; } if (!class_exists($pdo_class)){ - return $fallback; + throw new ModuleException( + 'Codeception\Module\Db', + "The class with provided config value 'pdo_class' ($pdo_class) does not exist" + ); } return $pdo_class; @@ -138,7 +141,7 @@ private static function assertIsPdo($dbh, $pdo_class) if (!$dbh instanceof \PDO){ throw new ModuleException( 'Codeception\Module\Db', - "The provided config value 'pdo' ($pdo_class) did not resolve to a class that implements \\PDO" + "The provided config value 'pdo_class' ($pdo_class) did not resolve to a class that implements \\PDO" ); } } diff --git a/src/Codeception/Module/Db.php b/src/Codeception/Module/Db.php index 194f9028..a719dc28 100644 --- a/src/Codeception/Module/Db.php +++ b/src/Codeception/Module/Db.php @@ -288,6 +288,7 @@ protected function getDatabases() 'waitlock' => 0, 'dump' => null, 'populator' => null, + 'pdo_class' => null, ], $databaseConfig); } } From 4f15f4d9f3f949b96b1d8b8ee8ff50646369fa0e Mon Sep 17 00:00:00 2001 From: m1ke Date: Thu, 4 Feb 2021 09:58:24 +0000 Subject: [PATCH 10/10] Another attempt to fix config issues, passes locally --- src/Codeception/Module/Db.php | 4 ++-- tests/data/sqlite.db | Bin 36864 -> 36864 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Codeception/Module/Db.php b/src/Codeception/Module/Db.php index a719dc28..f37d586f 100644 --- a/src/Codeception/Module/Db.php +++ b/src/Codeception/Module/Db.php @@ -288,7 +288,6 @@ protected function getDatabases() 'waitlock' => 0, 'dump' => null, 'populator' => null, - 'pdo_class' => null, ], $databaseConfig); } } @@ -572,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, $databaseConfig['pdo_class']); + $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 6c7c70e451b5db94d66ed1dcfe7bf6074fa51875..e43da89c6a159803f17965688bc16b50a37326a1 100644 GIT binary patch delta 15 WcmZozz|^pSX+jc{{mqRjbNT@=`38{y delta 15 WcmZozz|^pSX+jc{%!Z9AbNT@