/var/www/vhosts/nabawater/vendor/codeception/base/src/Codeception/Module
Edit: /var/www/vhosts/nabawater/vendor/codeception/base/src/Codeception/Module/Db.php (30822B)
seeInDatabase('users', ['name' => 'Davert', 'email' => 'davert@mail.com']);
*
* ```
* Will generate:
*
* ```sql
* SELECT COUNT(*) FROM `users` WHERE `name` = 'Davert' AND `email` = 'davert@mail.com'
* ```
* Since version 2.1.9 it's possible to use LIKE in a condition, as shown here:
*
* ```php
* seeInDatabase('users', ['name' => 'Davert', 'email like' => 'davert%']);
*
* ```
* Will generate:
*
* ```sql
* SELECT COUNT(*) FROM `users` WHERE `name` = 'Davert' AND `email` LIKE 'davert%'
* ```
* ## Public Properties
* * dbh - contains the PDO connection
* * driver - contains the Connection Driver
*
*/
class Db extends CodeceptionModule implements DbInterface
{
/**
* @var array
*/
protected $config = [
'populate' => false,
'cleanup' => false,
'reconnect' => false,
'waitlock' => 0,
'dump' => null,
'populator' => null,
];
/**
* @var array
*/
protected $requiredFields = ['dsn', 'user', 'password'];
const DEFAULT_DATABASE = 'default';
/**
* @var Driver[]
*/
public $drivers = [];
/**
* @var \PDO[]
*/
public $dbhs = [];
public $databasesPopulated = [];
public $databasesSql = [];
protected $insertedRows = [];
public $currentDatabase = self::DEFAULT_DATABASE;
protected function getDatabases()
{
$databases = [$this->currentDatabase => $this->config];
if (!empty($this->config['databases'])) {
foreach ($this->config['databases'] as $databaseKey => $databaseConfig) {
$databases[$databaseKey] = array_merge([
'populate' => false,
'cleanup' => false,
'reconnect' => false,
'waitlock' => 0,
'dump' => null,
'populator' => null,
], $databaseConfig);
}
}
return $databases;
}
protected function connectToDatabases()
{
foreach ($this->getDatabases() as $databaseKey => $databaseConfig) {
$this->connect($databaseKey, $databaseConfig);
}
}
protected function cleanUpDatabases()
{
foreach ($this->getDatabases() as $databaseKey => $databaseConfig) {
$this->_cleanup($databaseKey, $databaseConfig);
}
}
protected function populateDatabases($configKey)
{
foreach ($this->getDatabases() as $databaseKey => $databaseConfig) {
if ($databaseConfig[$configKey]) {
if (!$databaseConfig['populate']) {
return;
}
if (isset($this->databasesPopulated[$databaseKey]) && $this->databasesPopulated[$databaseKey]) {
return;
}
$this->_loadDump($databaseKey, $databaseConfig);
}
}
}
protected function readSqlForDatabases()
{
foreach ($this->getDatabases() as $databaseKey => $databaseConfig) {
$this->readSql($databaseKey, $databaseConfig);
}
}
protected function removeInsertedForDatabases()
{
foreach ($this->getDatabases() as $databaseKey => $databaseConfig) {
$this->amConnectedToDatabase($databaseKey);
$this->removeInserted($databaseKey);
}
}
protected function disconnectDatabases()
{
foreach ($this->getDatabases() as $databaseKey => $databaseConfig) {
$this->disconnect($databaseKey);
}
}
protected function reconnectDatabases()
{
foreach ($this->getDatabases() as $databaseKey => $databaseConfig) {
if ($databaseConfig['reconnect']) {
$this->disconnect($databaseKey);
$this->connect($databaseKey, $databaseConfig);
}
}
}
public function __get($name)
{
Notification::deprecate("Properties dbh and driver are deprecated in favor of Db::_getDbh and Db::_getDriver", "Db module");
if ($name == 'driver') {
return $this->_getDriver();
}
if ($name == 'dbh') {
return $this->_getDbh();
}
}
/**
* @return Driver
*/
public function _getDriver()
{
return $this->drivers[$this->currentDatabase];
}
public function _getDbh()
{
return $this->dbhs[$this->currentDatabase];
}
/**
* Make sure you are connected to the right database.
*
* ```php
* seeNumRecords(2, 'users'); //executed on default database
* $I->amConnectedToDatabase('db_books');
* $I->seeNumRecords(30, 'books'); //executed on db_books database
* //All the next queries will be on db_books
* ```
* @param $databaseKey
* @throws ModuleConfigException
*/
public function amConnectedToDatabase($databaseKey)
{
if (empty($this->getDatabases()[$databaseKey]) && $databaseKey != self::DEFAULT_DATABASE) {
throw new ModuleConfigException(
__CLASS__,
"\nNo database $databaseKey in the key databases.\n"
);
}
$this->currentDatabase = $databaseKey;
}
/**
* Can be used with a callback if you don't want to change the current database in your test.
*
* ```php
* seeNumRecords(2, 'users'); //executed on default database
* $I->performInDatabase('db_books', function($I) {
* $I->seeNumRecords(30, 'books'); //executed on db_books database
* });
* $I->seeNumRecords(2, 'users'); //executed on default database
* ```
* List of actions can be pragmatically built using `Codeception\Util\ActionSequence`:
*
* ```php
* performInDatabase('db_books', ActionSequence::build()
* ->seeNumRecords(30, 'books')
* );
* ```
* Alternatively an array can be used:
*
* ```php
* $I->performInDatabase('db_books', ['seeNumRecords' => [30, 'books']]);
* ```
*
* Choose the syntax you like the most and use it,
*
* Actions executed from array or ActionSequence will print debug output for actions, and adds an action name to
* exception on failure.
*
* @param $databaseKey
* @param \Codeception\Util\ActionSequence|array|callable $actions
* @throws ModuleConfigException
*/
public function performInDatabase($databaseKey, $actions)
{
$backupDatabase = $this->currentDatabase;
$this->amConnectedToDatabase($databaseKey);
if (is_callable($actions)) {
$actions($this);
$this->amConnectedToDatabase($backupDatabase);
return;
}
if (is_array($actions)) {
$actions = ActionSequence::build()->fromArray($actions);
}
if (!$actions instanceof ActionSequence) {
throw new \InvalidArgumentException("2nd parameter, actions should be callback, ActionSequence or array");
}
$actions->run($this);
$this->amConnectedToDatabase($backupDatabase);
}
public function _initialize()
{
$this->connectToDatabases();
}
public function __destruct()
{
$this->disconnectDatabases();
}
public function _beforeSuite($settings = [])
{
$this->readSqlForDatabases();
$this->connectToDatabases();
$this->cleanUpDatabases();
$this->populateDatabases('populate');
}
private function readSql($databaseKey = null, $databaseConfig = null)
{
if ($databaseConfig['populator']) {
return;
}
if (!$databaseConfig['cleanup'] && !$databaseConfig['populate']) {
return;
}
if (empty($databaseConfig['dump'])) {
return;
}
if (!is_array($databaseConfig['dump'])) {
$databaseConfig['dump'] = [$databaseConfig['dump']];
}
$sql = '';
foreach ($databaseConfig['dump'] as $filePath) {
$sql .= $this->readSqlFile($filePath);
}
if (!empty($sql)) {
// split SQL dump into lines
$this->databasesSql[$databaseKey] = preg_split('/\r\n|\n|\r/', $sql, -1, PREG_SPLIT_NO_EMPTY);
}
}
/**
* @param $filePath
*
* @return bool|null|string|string[]
* @throws \Codeception\Exception\ModuleConfigException
*/
private function readSqlFile($filePath)
{
if (!file_exists(Configuration::projectDir() . $filePath)) {
throw new ModuleConfigException(
__CLASS__,
"\nFile with dump doesn't exist.\n"
. "Please, check path for sql file: "
. $filePath
);
}
$sql = file_get_contents(Configuration::projectDir() . $filePath);
// remove C-style comments (except MySQL directives)
$sql = preg_replace('%/\*(?!!\d+).*?\*/%s', '', $sql);
return $sql;
}
private function connect($databaseKey, $databaseConfig)
{
if (!empty($this->drivers[$databaseKey]) && !empty($this->dbhs[$databaseKey])) {
return;
}
$options = [];
/**
* @see http://php.net/manual/en/pdo.construct.php
* @see http://php.net/manual/de/ref.pdo-mysql.php#pdo-mysql.constants
*/
if (array_key_exists('ssl_key', $databaseConfig)
&& !empty($databaseConfig['ssl_key'])
&& defined('\PDO::MYSQL_ATTR_SSL_KEY')
) {
$options[\PDO::MYSQL_ATTR_SSL_KEY] = (string) $databaseConfig['ssl_key'];
}
if (array_key_exists('ssl_cert', $databaseConfig)
&& !empty($databaseConfig['ssl_cert'])
&& defined('\PDO::MYSQL_ATTR_SSL_CERT')
) {
$options[\PDO::MYSQL_ATTR_SSL_CERT] = (string) $databaseConfig['ssl_cert'];
}
if (array_key_exists('ssl_ca', $databaseConfig)
&& !empty($databaseConfig['ssl_ca'])
&& defined('\PDO::MYSQL_ATTR_SSL_CA')
) {
$options[\PDO::MYSQL_ATTR_SSL_CA] = (string) $databaseConfig['ssl_ca'];
}
if (array_key_exists('ssl_cipher', $databaseConfig)
&& !empty($databaseConfig['ssl_cipher'])
&& defined('\PDO::MYSQL_ATTR_SSL_CIPHER')
) {
$options[\PDO::MYSQL_ATTR_SSL_CIPHER] = (string) $databaseConfig['ssl_cipher'];
}
if (array_key_exists('ssl_verify_server_cert', $databaseConfig)
&& defined('\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT')
) {
$options[\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = (boolean) $databaseConfig[ 'ssl_verify_server_cert' ];
}
try {
$this->debugSection('Connecting To Db', ['config' => $databaseConfig, 'options' => $options]);
$this->drivers[$databaseKey] = Driver::create($databaseConfig['dsn'], $databaseConfig['user'], $databaseConfig['password'], $options);
} catch (\PDOException $e) {
$message = $e->getMessage();
if ($message === 'could not find driver') {
list ($missingDriver, ) = explode(':', $databaseConfig['dsn'], 2);
$message = "could not find $missingDriver driver";
}
throw new ModuleException(__CLASS__, $message . ' while creating PDO connection');
}
if ($databaseConfig['waitlock']) {
$this->_getDriver()->setWaitLock($databaseConfig['waitlock']);
}
$this->debugSection('Db', 'Connected to ' . $databaseKey . ' ' . $this->drivers[$databaseKey]->getDb());
$this->dbhs[$databaseKey] = $this->drivers[$databaseKey]->getDbh();
}
private function disconnect($databaseKey)
{
$this->debugSection('Db', 'Disconnected from ' . $databaseKey);
$this->dbhs[$databaseKey] = null;
$this->drivers[$databaseKey] = null;
}
public function _before(TestInterface $test)
{
$this->reconnectDatabases();
$this->amConnectedToDatabase(self::DEFAULT_DATABASE);
$this->cleanUpDatabases();
$this->populateDatabases('cleanup');
parent::_before($test);
}
public function _after(TestInterface $test)
{
$this->removeInsertedForDatabases();
parent::_after($test);
}
protected function removeInserted($databaseKey = null)
{
$databaseKey = empty($databaseKey) ? self::DEFAULT_DATABASE : $databaseKey;
if (empty($this->insertedRows[$databaseKey])) {
return;
}
foreach (array_reverse($this->insertedRows[$databaseKey]) as $row) {
try {
$this->_getDriver()->deleteQueryByCriteria($row['table'], $row['primary']);
} catch (\Exception $e) {
$this->debug("Couldn't delete record " . json_encode($row['primary']) ." from {$row['table']}");
}
}
$this->insertedRows[$databaseKey] = [];
}
public function _cleanup($databaseKey = null, $databaseConfig = null)
{
$databaseKey = empty($databaseKey) ? self::DEFAULT_DATABASE : $databaseKey;
$databaseConfig = empty($databaseConfig) ? $this->config : $databaseConfig;
if (!$databaseConfig['populate']) {
return;
}
if (!$databaseConfig['cleanup']) {
return;
}
if (isset($this->databasesPopulated[$databaseKey]) && !$this->databasesPopulated[$databaseKey]) {
return;
}
$dbh = $this->dbhs[$databaseKey];
if (!$dbh) {
throw new ModuleConfigException(
__CLASS__,
'No connection to database. Remove this module from config if you don\'t need database repopulation'
);
}
try {
if (false === $this->shouldCleanup($databaseConfig, $databaseKey)) {
return;
}
$this->drivers[$databaseKey]->cleanup();
$this->databasesPopulated[$databaseKey] = false;
} catch (\Exception $e) {
throw new ModuleException(__CLASS__, $e->getMessage());
}
}
/**
* @param array $databaseConfig
* @param string $databaseKey
* @return bool
*/
protected function shouldCleanup($databaseConfig, $databaseKey)
{
// If using populator and it's not empty, clean up regardless
if (!empty($databaseConfig['populator'])) {
return true;
}
// If no sql dump for $databaseKey or sql dump is empty, don't clean up
return !empty($this->databasesSql[$databaseKey]);
}
public function _isPopulated()
{
return $this->databasesPopulated[$this->currentDatabase];
}
public function _loadDump($databaseKey = null, $databaseConfig = null)
{
$databaseKey = empty($databaseKey) ? self::DEFAULT_DATABASE : $databaseKey;
$databaseConfig = empty($databaseConfig) ? $this->config : $databaseConfig;
if ($databaseConfig['populator']) {
$this->loadDumpUsingPopulator($databaseKey, $databaseConfig);
return;
}
$this->loadDumpUsingDriver($databaseKey);
}
protected function loadDumpUsingPopulator($databaseKey, $databaseConfig)
{
$populator = new DbPopulator($databaseConfig);
$this->databasesPopulated[$databaseKey] = $populator->run();
}
protected function loadDumpUsingDriver($databaseKey)
{
if (!isset($this->databasesSql[$databaseKey])) {
return;
}
if (!$this->databasesSql[$databaseKey]) {
$this->debugSection('Db', 'No SQL loaded, loading dump skipped');
return;
}
$this->drivers[$databaseKey]->load($this->databasesSql[$databaseKey]);
$this->databasesPopulated[$databaseKey] = true;
}
/**
* Inserts an SQL record into a database. This record will be erased after the test.
*
* ```php
* haveInDatabase('users', array('name' => 'miles', 'email' => 'miles@davis.com'));
* ?>
* ```
*
* @param string $table
* @param array $data
*
* @return integer $id
*/
public function haveInDatabase($table, array $data)
{
$lastInsertId = $this->_insertInDatabase($table, $data);
$this->addInsertedRow($table, $data, $lastInsertId);
return $lastInsertId;
}
public function _insertInDatabase($table, array $data)
{
$query = $this->_getDriver()->insert($table, $data);
$parameters = array_values($data);
$this->debugSection('Query', $query);
$this->debugSection('Parameters', $parameters);
$this->_getDriver()->executeQuery($query, $parameters);
try {
$lastInsertId = (int)$this->_getDriver()->lastInsertId($table);
} catch (\PDOException $e) {
// ignore errors due to uncommon DB structure,
// such as tables without _id_seq in PGSQL
$lastInsertId = 0;
$this->debugSection('DB error', $e->getMessage());
}
return $lastInsertId;
}
private function addInsertedRow($table, array $row, $id)
{
$primaryKey = $this->_getDriver()->getPrimaryKey($table);
$primary = [];
if ($primaryKey) {
if ($id && count($primaryKey) === 1) {
$primary [$primaryKey[0]] = $id;
} else {
foreach ($primaryKey as $column) {
if (isset($row[$column])) {
$primary[$column] = $row[$column];
} else {
throw new \InvalidArgumentException(
'Primary key field ' . $column . ' is not set for table ' . $table
);
}
}
}
} else {
$primary = $row;
}
$this->insertedRows[$this->currentDatabase][] = [
'table' => $table,
'primary' => $primary,
];
}
public function seeInDatabase($table, $criteria = [])
{
$res = $this->countInDatabase($table, $criteria);
$this->assertGreaterThan(
0,
$res,
'No matching records found for criteria ' . json_encode($criteria) . ' in table ' . $table
);
}
/**
* Asserts that the given number of records were found in the database.
*
* ```php
* seeNumRecords(1, 'users', ['name' => 'davert'])
* ?>
* ```
*
* @param int $expectedNumber Expected number
* @param string $table Table name
* @param array $criteria Search criteria [Optional]
*/
public function seeNumRecords($expectedNumber, $table, array $criteria = [])
{
$actualNumber = $this->countInDatabase($table, $criteria);
$this->assertEquals(
$expectedNumber,
$actualNumber,
sprintf(
'The number of found rows (%d) does not match expected number %d for criteria %s in table %s',
$actualNumber,
$expectedNumber,
json_encode($criteria),
$table
)
);
}
public function dontSeeInDatabase($table, $criteria = [])
{
$count = $this->countInDatabase($table, $criteria);
$this->assertLessThan(
1,
$count,
'Unexpectedly found matching records for criteria ' . json_encode($criteria) . ' in table ' . $table
);
}
/**
* Count rows in a database
*
* @param string $table Table name
* @param array $criteria Search criteria [Optional]
*
* @return int
*/
protected function countInDatabase($table, array $criteria = [])
{
return (int) $this->proceedSeeInDatabase($table, 'count(*)', $criteria);
}
/**
* Fetches all values from the column in database.
* Provide table name, desired column and criteria.
*
* @param string $table
* @param string $column
* @param array $criteria
*
* @return mixed
*/
protected function proceedSeeInDatabase($table, $column, $criteria)
{
$query = $this->_getDriver()->select($column, $table, $criteria);
$parameters = array_values($criteria);
$this->debugSection('Query', $query);
if (!empty($parameters)) {
$this->debugSection('Parameters', $parameters);
}
$sth = $this->_getDriver()->executeQuery($query, $parameters);
return $sth->fetchColumn();
}
/**
* Fetches all values from the column in database.
* Provide table name, desired column and criteria.
*
* ``` php
* grabColumnFromDatabase('users', 'email', array('name' => 'RebOOter'));
* ```
*
* @param string $table
* @param string $column
* @param array $criteria
*
* @return array
*/
public function grabColumnFromDatabase($table, $column, array $criteria = [])
{
$query = $this->_getDriver()->select($column, $table, $criteria);
$parameters = array_values($criteria);
$this->debugSection('Query', $query);
$this->debugSection('Parameters', $parameters);
$sth = $this->_getDriver()->executeQuery($query, $parameters);
return $sth->fetchAll(\PDO::FETCH_COLUMN, 0);
}
/**
* Fetches all values from the column in database.
* Provide table name, desired column and criteria.
*
* ``` php
* grabFromDatabase('users', 'email', array('name' => 'RebOOter'));
* ```
*
* @param string $table
* @param string $column
* @param array $criteria
*
* @return mixed
*/
public function grabFromDatabase($table, $column, $criteria = [])
{
return $this->proceedSeeInDatabase($table, $column, $criteria);
}
/**
* Returns the number of rows in a database
*
* @param string $table Table name
* @param array $criteria Search criteria [Optional]
*
* @return int
*/
public function grabNumRecords($table, array $criteria = [])
{
return $this->countInDatabase($table, $criteria);
}
/**
* Update an SQL record into a database.
*
* ```php
* updateInDatabase('users', array('isAdmin' => true), array('email' => 'miles@davis.com'));
* ?>
* ```
*
* @param string $table
* @param array $data
* @param array $criteria
*/
public function updateInDatabase($table, array $data, array $criteria = [])
{
$query = $this->_getDriver()->update($table, $data, $criteria);
$parameters = array_merge(array_values($data), array_values($criteria));
$this->debugSection('Query', $query);
if (!empty($parameters)) {
$this->debugSection('Parameters', $parameters);
}
$this->_getDriver()->executeQuery($query, $parameters);
}
}