diff --git a/.github/workflows/php-security.yml b/.github/workflows/php-security.yml index 98491b4f..34183a40 100644 --- a/.github/workflows/php-security.yml +++ b/.github/workflows/php-security.yml @@ -17,6 +17,7 @@ jobs: php-version: - "7.3" - "7.4" + - "8.0" operating-system: - "ubuntu-latest" diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 19b9e820..2e6e7cbb 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -21,6 +21,7 @@ jobs: php-version: - "7.3" - "7.4" + - "8.0" operating-system: - "ubuntu-latest" diff --git a/.gitignore b/.gitignore index d1502b08..156dbfd9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ vendor/ composer.lock +.phpunit.result.cache +instances/example/config/test.sqlite3 diff --git a/composer.json b/composer.json index 2fbaa6db..2b579a03 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ } ], "require": { - "php": "^7.3|^7.4", + "php": "^7.3|^7.4|^8.0", "adodb/adodb-php": "^5.21", "geoip/geoip": "^1.17", "neutron/sphinxsearch-api": "^2.0", @@ -40,8 +40,9 @@ "weotch/phpthumb": "^1.0.5" }, "require-dev": { + "ext-pdo_sqlite": "*", "adlawson/vfs": "^0.12.1", - "mikey179/vfsstream": "^1.6", + "mikey179/vfsstream": "^1.6.10", "phpcompatibility/php-compatibility": "^9.3", "phpunit/phpunit": "^9.5", "sifophp/sifo-common-instance": "@dev", @@ -51,6 +52,9 @@ }, "scripts": { "cs-check": "phpcs src --colors", + "cs-check:php73": "phpcs -p ./src --standard=vendor/phpcompatibility/php-compatibility/PHPCompatibility --runtime-set testVersion 7.3", + "cs-check:php74": "phpcs -p ./src --standard=vendor/phpcompatibility/php-compatibility/PHPCompatibility --runtime-set testVersion 7.4", + "cs-check:php8": "phpcs -p ./src --standard=vendor/phpcompatibility/php-compatibility/PHPCompatibility --runtime-set testVersion 8.0", "cs-fix": "phpcbf src --colors" }, "config": { diff --git a/instances/example/config/domains.config.php b/instances/example/config/domains.config.php index a53d11c5..eb685374 100644 --- a/instances/example/config/domains.config.php +++ b/instances/example/config/domains.config.php @@ -20,12 +20,8 @@ 'static_host' => 'http://static.sifo.local', 'media_host' => 'http://static.sifo.local', // Alternative static content (media). Comment to disable. 'database' => array( - 'db_driver' => 'mysql', // To use transactions you must use mysqli driver. - 'db_host' => '127.0.0.1', - 'db_user' => 'root', - 'db_password' => 'root', - 'db_name' => 'yourdatabase', - 'db_init_commands' => array( 'SET NAMES utf8' ) // Commands launched before the queries. + 'db_driver' => 'sqlite', // To use transactions you must use mysqli driver. + 'db_dsn' => sprintf('sqlite:/%s/test.sqlite3', __DIR__), ), 'php_ini_sets' => array( // Empty array if you don't want any php.ini overriden. 'log_errors' => 'On', diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 559aa519..d7afa5c9 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -7,7 +7,6 @@ convertWarningsToExceptions="true" beStrictAboutCoversAnnotation="true" beStrictAboutOutputDuringTests="true" - enforceTimeLimit="true" beStrictAboutTestsThatDoNotTestAnything="false" processIsolation="false" stopOnFailure="false" diff --git a/src/Sifo/Debug/Mysql.php b/src/Sifo/Debug/Mysql.php index 9abb00bf..a0420474 100644 --- a/src/Sifo/Debug/Mysql.php +++ b/src/Sifo/Debug/Mysql.php @@ -21,12 +21,10 @@ namespace Sifo; use PDO,PDOStatement; -include_once ROOT_PATH . '/vendor/sifophp/sifo/src/Sifo/Mysql.php'; - /** * DbDebugStatement class that is extended for debugging purposes. */ -class DebugMysqlStatement extends MysqlStatement +abstract class PDOBadAbstractionDebugMysqlStatement extends MysqlStatement { /** * Binded parameters ( not binded values ). @@ -83,8 +81,11 @@ public function execute($parameters = null) $query_string = $this->_replacePreparedParameters( $query_string, $parameters ); preg_match('/\/\* (.*?) \*\/\n(.*)/s', $query_string, $matches); - $context = $matches[1]; - $query_string = $matches[2]; + $context = 'unknow'; + if (false === empty($matches)) { + $context = $matches[1]; + $query_string = $matches[2]; + } DebugMysql::setDebug( $query_string, $query_time, $context, $this, $this->db_params ); @@ -152,32 +153,64 @@ public function fetch( $fetch_style = PDO::FETCH_ASSOC, $cursor_orientation = PD return parent::fetch( $fetch_style, $cursor_orientation, $cursor_offset ); } +} - /** - * Returns an array containing all of the result set rows. - * - * @param integer $fetch_style Controls the contents of the returned array as documented in PDOStatement::fetch(). - * @param mixed $fetch_argument This argument have a different meaning depending on the value of the fetch_style parameter. - * @param array $ctor_args Arguments of custom class constructor when the fetch_style parameter is PDO::FETCH_CLASS. - * @return array - */ - public function fetchAll( $fetch_style = PDO::FETCH_ASSOC, $fetch_argument = null, $ctor_args = array() ) - { - if ($this->result !== null) +if (version_compare(PHP_VERSION, '8.0.0') >= 0) { + class DebugMysqlStatement extends PDOBadAbstractionDebugMysqlStatement + { + /** + * Returns an array containing all of the result set rows. + * + * @param integer $fetch_style Controls the contents of the returned array as documented in PDOStatement::fetch(). + * @param mixed $fetch_argument This argument have a different meaning depending on the value of the fetch_style parameter. + * @param array $ctor_args Arguments of custom class constructor when the fetch_style parameter is PDO::FETCH_CLASS. + * @return array + */ + public function fetchAll(int $fetch_style = PDO::FETCH_ASSOC, $fetch_argument = null, mixed ...$ctor_args): array { - $results = $this->result; - $this->result = null; + if ($this->result !== null) { + $results = $this->result; + $this->result = null; + + return $results; + } + + if ($fetch_argument === null) { + return $this->result = parent::fetchAll($fetch_style); + } - return $results; + return $this->result = parent::fetchAll($fetch_style, $fetch_argument, $ctor_args); } + } +} - if ( $fetch_argument === null ) - { - return $this->result = parent::fetchAll( $fetch_style ); - } +if (version_compare(PHP_VERSION, '8.0.0') < 0) { + class DebugMysqlStatement extends PDOBadAbstractionDebugMysqlStatement + { + /** + * Returns an array containing all of the result set rows. + * + * @param integer $fetch_style Controls the contents of the returned array as documented in PDOStatement::fetch(). + * @param mixed $fetch_argument This argument have a different meaning depending on the value of the fetch_style parameter. + * @param array $ctor_args Arguments of custom class constructor when the fetch_style parameter is PDO::FETCH_CLASS. + * @return array + */ + public function fetchAll($fetch_style = PDO::FETCH_ASSOC, $fetch_argument = null, $ctor_args = []): array + { + if ($this->result !== null) { + $results = $this->result; + $this->result = null; - return $this->result = parent::fetchAll( $fetch_style, $fetch_argument, $ctor_args ); - } + return $results; + } + + if ($fetch_argument === null) { + return $this->result = parent::fetchAll($fetch_style); + } + + return $this->result = parent::fetchAll($fetch_style, $fetch_argument, $ctor_args); + } + } } /** @@ -302,20 +335,30 @@ public static function setDebug( $statement, $query_time, $context, $resultset, } $sql = '/* ' . $context . ' */' . PHP_EOL . $statement; - $debug_query = array( + if (isset($db_params['db_dsn'])) { + $database = [ + 'dsn' => $db_params['db_dsn'] + ]; + + } else { + $database = [ + 'host' => $db_params['db_host'], + 'database' => $db_params['db_name'], + 'user' => $db_params['db_user'], + ]; + } + + $debug_query = array_merge([ "tag" => $context, "sql" => $sql, "type" => ( ( 0 === stripos( $statement, 'SELECT' ) ) ? 'read' : 'write' ), - "host" => $db_params['db_host'], - "database" => $db_params['db_name'], - "user" => $db_params['db_user'], // phpcs:ignore "trace" => DebugMysql::generateTrace( debug_backtrace( false ) ), // Show a table with the method name and number (functions: Affected_Rows, Last_InsertID "resultset" => $resultset_array, "time" => $query_time, "error" => ( isset( $error[2] ) !== false ) ? $error[2] : false - ); + ], $database); $debug_query['rows_num'] = $rows_num; diff --git a/src/Sifo/Mysql.php b/src/Sifo/Mysql.php index 3285d69b..adab183b 100644 --- a/src/Sifo/Mysql.php +++ b/src/Sifo/Mysql.php @@ -21,88 +21,123 @@ namespace Sifo; use PDO,PDOStatement; -/** - * DbStatement class that is extended to customize some PDO functionality. - */ -class MysqlStatement extends PDOStatement + +abstract class PDOBadAbstractionStatement extends PDOStatement { - /** - * The pdo object instance. - * - * @var PDO Object. - */ - public $dbh; + /** + * The pdo object instance. + * + * @var PDO Object. + */ + public $dbh; - /** - * The domains.config params related to the database. - * - * @var array - */ - protected $db_params; + /** + * The domains.config params related to the database. + * + * @var array + */ + protected $db_params; - /** - * Construction method. Sets the pdo object and the db parameters. - * - * @param PDO $dbh The pdo instance executing the statement. - * @param string $profile The profile being used for this statement. - */ + /** + * Construction method. Sets the pdo object and the db parameters. + * + * @param PDO $dbh The pdo instance executing the statement. + * @param string $profile The profile being used for this statement. + */ protected function __construct( $dbh, $profile ) - { + { $this->dbh = $dbh; - $params = Domains::getInstance()->getDatabaseParams(); + $params = Domains::getInstance()->getDatabaseParams(); - if (!array_key_exists($profile, $params)) - { - $params[$profile] = $params; - } + if (!array_key_exists($profile, $params)) + { + $params[$profile] = $params; + } - $this->db_params = $params[$profile]; + $this->db_params = $params[$profile]; } - /** - * Executes the current statement. - * - * @param array $parameters The array of parameters to be replaced in the statement. - * + /** + * Executes the current statement. + * + * @param array $parameters The array of parameters to be replaced in the statement. + * * @return bool True if everything went OK, false otherwise. - */ - public function execute($parameters = null) - { + */ + public function execute($parameters = null) + { return parent::execute($parameters ); - } + } - /** - * Fetches the resultset. Extended to make PDO::FETCH_ASSOC as default $fetch_style. - * - * @param integer $fetch_style Controls how the next row will be returned to the caller. This value must be one of the PDO::FETCH_* constants, defaulting to PDO::FETCH_ASSOC. - * @param integer $cursor_orientation For a PDOStatement object representing a scrollable cursor, this value determines which row will be returned to the caller. This value must be one of the PDO::FETCH_ORI_* constants, defaulting to PDO::FETCH_ORI_NEXT. To request a scrollable cursor for your PDOStatement object, you must set the PDO::ATTR_CURSOR attribute to PDO::CURSOR_SCROLL when you prepare the SQL statement with PDO::prepare(). - * @param integer $cursor_offset For a PDOStatement object representing a scrollable cursor for which the cursor_orientation parameter is set to PDO::FETCH_ORI_ABS, this value specifies the absolute number of the row in the result set that shall be fetched. - * @return mixed - */ - public function fetch( $fetch_style = PDO::FETCH_ASSOC, $cursor_orientation = PDO::FETCH_ORI_NEXT, $cursor_offset = 0 ) - { - return parent::fetch( $fetch_style, $cursor_orientation, $cursor_offset ); - } + /** + * Fetches the resultset. Extended to make PDO::FETCH_ASSOC as default $fetch_style. + * + * @param integer $fetch_style Controls how the next row will be returned to the caller. This value must be one of the PDO::FETCH_* constants, defaulting to PDO::FETCH_ASSOC. + * @param integer $cursor_orientation For a PDOStatement object representing a scrollable cursor, this value determines which row will be returned to the caller. This value must be one of the PDO::FETCH_ORI_* constants, defaulting to PDO::FETCH_ORI_NEXT. To request a scrollable cursor for your PDOStatement object, you must set the PDO::ATTR_CURSOR attribute to PDO::CURSOR_SCROLL when you prepare the SQL statement with PDO::prepare(). + * @param integer $cursor_offset For a PDOStatement object representing a scrollable cursor for which the cursor_orientation parameter is set to PDO::FETCH_ORI_ABS, this value specifies the absolute number of the row in the result set that shall be fetched. + * @return mixed + */ + public function fetch( $fetch_style = PDO::FETCH_ASSOC, $cursor_orientation = PDO::FETCH_ORI_NEXT, $cursor_offset = 0 ) + { + return parent::fetch( $fetch_style, $cursor_orientation, $cursor_offset ); + } - /** - * Returns an array containing all of the result set rows. - * - * @param integer $fetch_style Controls the contents of the returned array as documented in PDOStatement::fetch(). - * @param mixed $fetch_argument This argument have a different meaning depending on the value of the fetch_style parameter. - * @param array $ctor_args Arguments of custom class constructor when the fetch_style parameter is PDO::FETCH_CLASS. - * @return array - */ - public function fetchAll( $fetch_style = PDO::FETCH_ASSOC, $fetch_argument = null, $ctor_args = array() ) - { - if ( $fetch_argument === null ) - { - return parent::fetchAll( $fetch_style ); - } +} - return parent::fetchAll( $fetch_style, $fetch_argument, $ctor_args ); - } +if (version_compare(PHP_VERSION, '8.0.0') >= 0) { + /** + * DbStatement class that is extended to customize some PDO functionality. + */ + class MysqlStatement extends PDOBadAbstractionStatement + { + /** + * Returns an array containing all of the result set rows. + * + * @param integer $fetch_style Controls the contents of the returned array as documented in PDOStatement::fetch(). + * @param mixed $fetch_argument This argument have a different meaning depending on the value of the fetch_style parameter. + * @param array $ctor_args Arguments of custom class constructor when the fetch_style parameter is PDO::FETCH_CLASS. + * @return array + */ + public function fetchAll( int $fetch_style = PDO::FETCH_ASSOC, $fetch_argument = null, mixed ...$ctor_args ): array + { + if ( $fetch_argument === null ) + { + return parent::fetchAll( $fetch_style ); + } + + return parent::fetchAll( $fetch_style, $fetch_argument, $ctor_args ); + } + } + +} + +if (version_compare(PHP_VERSION, '8.0.0') < 0) { + /** + * DbStatement class that is extended to customize some PDO functionality. + */ + class MysqlStatement extends PDOBadAbstractionStatement + { + /** + * Returns an array containing all of the result set rows. + * + * @param integer $fetch_style Controls the contents of the returned array as documented in PDOStatement::fetch(). + * @param mixed $fetch_argument This argument have a different meaning depending on the value of the fetch_style parameter. + * @param array $ctor_args Arguments of custom class constructor when the fetch_style parameter is PDO::FETCH_CLASS. + * @return array + */ + public function fetchAll( $fetch_style = PDO::FETCH_ASSOC, $fetch_argument = null, $ctor_args = [] ): array + { + if ( $fetch_argument === null ) + { + return parent::fetchAll( $fetch_style ); + } + + return parent::fetchAll( $fetch_style, $fetch_argument, $ctor_args ); + } + } } + /** * Database class. Uses PDO. */ @@ -144,17 +179,24 @@ class Mysql public function __construct( $profile ) { $this->db_params = Domains::getInstance()->getDatabaseParams(); - $init_commands = array(); + $init_commands = []; - if ( !empty( $this->db_params['db_init_commands'] ) ) - { - $init_commands = array( PDO::MYSQL_ATTR_INIT_COMMAND => implode( ';', $this->db_params['db_init_commands'] ) ); + if (!empty($this->db_params['db_init_commands'])) { + $init_commands = [ + PDO::MYSQL_ATTR_INIT_COMMAND => implode( ';', $this->db_params['db_init_commands']) + ]; } + if ($this->db_params['db_driver'] === 'sqlite') { + $dsn = $this->db_params['db_dsn']; + } else { + $dsn = "mysql:host={$this->db_params['db_host']};dbname={$this->db_params['db_name']}"; + } + $this->pdo = new PDO( - "mysql:host={$this->db_params['db_host']};dbname={$this->db_params['db_name']}", - $this->db_params['db_user'], - $this->db_params['db_password'], + $dsn, + $this->db_params['db_user'] ?? null, + $this->db_params['db_password'] ?? null, $init_commands ); diff --git a/test/CryptTest.php b/test/CryptTest.php index 58af4d50..5bc2f406 100644 --- a/test/CryptTest.php +++ b/test/CryptTest.php @@ -13,7 +13,7 @@ class CryptTest extends TestCase /** @var string */ private $crypted_text; - public function tearDown(): void + protected function tearDown(): void { $this->text = null; $this->crypted_text = null; diff --git a/test/Sifo/Debug/DebugMysqlTest.php b/test/Sifo/Debug/DebugMysqlTest.php new file mode 100644 index 00000000..71ee4a70 --- /dev/null +++ b/test/Sifo/Debug/DebugMysqlTest.php @@ -0,0 +1,71 @@ +prepare( + <<execute(); + } + + public function testFetchAll(): void + { + $mysql = DebugMysql::getInstance(); + + /** @var \PDOStatement $statement */ + $statement = $mysql->prepare( + <<execute(); + /** @var \PDOStatement $statement */ + $statement = $mysql->prepare( + <<execute(); + + /** @var \PDOStatement $statement */ + $statement = $mysql->prepare( + <<execute(); + + $result = $statement->fetchAll(); + + $this->assertSame([ + [ + "project_id" => "1", + "project_name" => "test" + ], + [ + "project_id" => "2", + "project_name" => "test2" + ] + ], $result); + } +} diff --git a/test/Sifo/MysqlTest.php b/test/Sifo/MysqlTest.php new file mode 100644 index 00000000..6e468625 --- /dev/null +++ b/test/Sifo/MysqlTest.php @@ -0,0 +1,70 @@ +prepare( + <<execute(); + } + + public function testFetchAll(): void + { + $mysql = Mysql::getInstance(); + + /** @var \PDOStatement $statement */ + $statement = $mysql->prepare( + <<execute(); + /** @var \PDOStatement $statement */ + $statement = $mysql->prepare( + <<execute(); + + /** @var \PDOStatement $statement */ + $statement = $mysql->prepare( + <<execute(); + + $result = $statement->fetchAll(); + + $this->assertSame([ + [ + "project_id" => "1", + "project_name" => "test" + ], + [ + "project_id" => "2", + "project_name" => "test2" + ] + ], $result); + } +}