diff --git a/arsse.php b/arsse.php index 7e13cc39..f5f26732 100644 --- a/arsse.php +++ b/arsse.php @@ -13,7 +13,7 @@ require_once BASE."vendor".DIRECTORY_SEPARATOR."autoload.php"; ignore_user_abort(true); ini_set("memory_limit", "-1"); ini_set("max_execution_time", "0"); -// FIXME: This is required by a dependency of Picofeed +// FIXME: This is required because various dependencies have yet to adjust to PHP 8.1 error_reporting(\E_ALL & ~\E_DEPRECATED); if (\PHP_SAPI === "cli") { diff --git a/composer.json b/composer.json index afab259e..97aedd38 100644 --- a/composer.json +++ b/composer.json @@ -40,6 +40,9 @@ "config": { "platform": { "php": "7.1.33" + }, + "allow-plugins": { + "bamarni/composer-bin-plugin": true } }, "scripts": { diff --git a/lib/Db/MySQL/Driver.php b/lib/Db/MySQL/Driver.php index 9acd1eaa..9aca8189 100644 --- a/lib/Db/MySQL/Driver.php +++ b/lib/Db/MySQL/Driver.php @@ -163,6 +163,8 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver { } protected function makeConnection(string $db, string $user, string $password, string $host, int $port, string $socket): void { + $drv = new \mysqli_driver; + $drv->report_mode = \MYSQLI_REPORT_OFF; $this->db = mysqli_init(); $this->db->options(\MYSQLI_SET_CHARSET_NAME, "utf8mb4"); $this->db->options(\MYSQLI_OPT_INT_AND_FLOAT_NATIVE, false); diff --git a/lib/Db/MySQL/PDODriver.php b/lib/Db/MySQL/PDODriver.php index e669fca7..18cda0be 100644 --- a/lib/Db/MySQL/PDODriver.php +++ b/lib/Db/MySQL/PDODriver.php @@ -28,7 +28,8 @@ class PDODriver extends Driver { ]); try { $this->db = new \PDO($dsn, $user, $password, [ - \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + \PDO::ATTR_STRINGIFY_FETCHES => true, ]); } catch (\PDOException $e) { $msg = $e->getMessage(); diff --git a/lib/Db/PostgreSQL/Driver.php b/lib/Db/PostgreSQL/Driver.php index c22f0963..f78855cf 100644 --- a/lib/Db/PostgreSQL/Driver.php +++ b/lib/Db/PostgreSQL/Driver.php @@ -211,7 +211,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver { public function query(string $query): \JKingWeb\Arsse\Db\Result { $r = $this->dispatchQuery($query); - if (is_resource($r)) { + if (is_resource($r) || $r instanceof \PgSql\Result) { //class since PHP 8.1 return new Result($this->db, $r); } else { [$excClass, $excMsg, $excData] = $r; diff --git a/lib/Db/PostgreSQL/Statement.php b/lib/Db/PostgreSQL/Statement.php index 4472e8e5..acb14a3b 100644 --- a/lib/Db/PostgreSQL/Statement.php +++ b/lib/Db/PostgreSQL/Statement.php @@ -35,7 +35,7 @@ class Statement extends \JKingWeb\Arsse\Db\AbstractStatement { $this->bindValues($values); $r = $this->dispatchQuery($this->qMunged, $this->in); $this->in = []; - if (is_resource($r)) { + if (is_resource($r) || $r instanceof \PgSql\Result) { //class since PHP 8.1 return new Result($this->db, $r); } else { [$excClass, $excMsg, $excData] = $r; diff --git a/lib/Db/Result.php b/lib/Db/Result.php index 88c908b5..8240afd4 100644 --- a/lib/Db/Result.php +++ b/lib/Db/Result.php @@ -7,10 +7,15 @@ declare(strict_types=1); namespace JKingWeb\Arsse\Db; interface Result extends \Iterator { + #[\ReturnTypeWillChange] public function current(); + #[\ReturnTypeWillChange] public function key(); + #[\ReturnTypeWillChange] public function next(); + #[\ReturnTypeWillChange] public function rewind(); + #[\ReturnTypeWillChange] public function valid(); public function getRow(); diff --git a/lib/Db/SQLite3/PDODriver.php b/lib/Db/SQLite3/PDODriver.php index 506c46a2..86ab1cdc 100644 --- a/lib/Db/SQLite3/PDODriver.php +++ b/lib/Db/SQLite3/PDODriver.php @@ -18,7 +18,8 @@ class PDODriver extends AbstractPDODriver { protected function makeConnection(string $file, string $key): void { $this->db = new \PDO("sqlite:".$file, "", "", [ - \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + \PDO::ATTR_STRINGIFY_FETCHES => true, ]); } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index e17ef0f9..379a7137 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -12,9 +12,7 @@ const DOCROOT = BASE."tests".DIRECTORY_SEPARATOR."docroot".DIRECTORY_SEPARATOR; ini_set("memory_limit", "-1"); ini_set("zend.assertions", "1"); ini_set("assert.exception", "true"); -// FIXME: Workaround for a bug in PCRE2 10.37 -ini_set("pcre.jit", "0"); -// FIXME: This is required by a dependency of Picofeed +// FIXME: This is required because various dependencies have yet to adjust to PHP 8.1 error_reporting(\E_ALL & ~\E_DEPRECATED); require_once BASE."vendor".DIRECTORY_SEPARATOR."autoload.php"; diff --git a/tests/lib/DatabaseDrivers/MySQL.php b/tests/lib/DatabaseDrivers/MySQL.php index 9363a59f..2b6c0164 100644 --- a/tests/lib/DatabaseDrivers/MySQL.php +++ b/tests/lib/DatabaseDrivers/MySQL.php @@ -7,9 +7,10 @@ declare(strict_types=1); namespace JKingWeb\Arsse\Test\DatabaseDrivers; use JKingWeb\Arsse\Arsse; -use JKingWeb\Arsse\Db\Driver; trait MySQL { + use MySQLCommon; + protected static $implementation = "MySQL"; protected static $backend = "MySQL"; protected static $dbResultClass = \JKingWeb\Arsse\Db\MySQL\Result::class; @@ -21,6 +22,8 @@ trait MySQL { if (!class_exists("mysqli")) { return null; } + $drv = new \mysqli_driver; + $drv->report_mode = \MYSQLI_REPORT_OFF; $d = mysqli_init(); $d->options(\MYSQLI_OPT_INT_AND_FLOAT_NATIVE, false); $d->options(\MYSQLI_SET_CHARSET_NAME, "utf8mb4"); @@ -34,53 +37,4 @@ trait MySQL { } return $d; } - - public static function dbTableList($db): array { - $listTables = "SELECT table_name as name from information_schema.tables where table_schema = database() and table_name like 'arsse_%'"; - if ($db instanceof Driver) { - $tables = $db->query($listTables)->getAll(); - } elseif ($db instanceof \PDO) { - $tables = $db->query($listTables)->fetchAll(\PDO::FETCH_ASSOC); - } else { - $tables = $db->query($listTables)->fetch_all(\MYSQLI_ASSOC); - } - $tables = sizeof($tables) ? array_column($tables, "name") : []; - return $tables; - } - - public static function dbTruncate($db, array $afterStatements = []): void { - // rollback any pending transaction - try { - $db->query("UNLOCK TABLES; ROLLBACK"); - } catch (\Throwable $e) { - } - $db->query("SET FOREIGN_KEY_CHECKS=0"); - foreach (self::dbTableList($db) as $table) { - if ($table === "arsse_meta") { - $db->query("DELETE FROM $table where `key` <> 'schema_version'"); - } else { - $db->query("TRUNCATE TABLE $table"); - } - } - foreach ($afterStatements as $st) { - $db->query($st); - } - $db->query("SET FOREIGN_KEY_CHECKS=1"); - } - - public static function dbRaze($db, array $afterStatements = []): void { - // rollback any pending transaction - try { - $db->query("UNLOCK TABLES; ROLLBACK"); - } catch (\Throwable $e) { - } - $db->query("SET FOREIGN_KEY_CHECKS=0"); - foreach (self::dbTableList($db) as $table) { - $db->query("DROP TABLE IF EXISTS $table"); - } - foreach ($afterStatements as $st) { - $db->query($st); - } - $db->query("SET FOREIGN_KEY_CHECKS=1"); - } } diff --git a/tests/lib/DatabaseDrivers/MySQLCommon.php b/tests/lib/DatabaseDrivers/MySQLCommon.php new file mode 100644 index 00000000..f9ea18d4 --- /dev/null +++ b/tests/lib/DatabaseDrivers/MySQLCommon.php @@ -0,0 +1,60 @@ +query($listTables)->getAll(); + } elseif ($db instanceof \PDO) { + $tables = $db->query($listTables)->fetchAll(\PDO::FETCH_ASSOC); + } else { + $tables = $db->query($listTables)->fetch_all(\MYSQLI_ASSOC); + } + $tables = sizeof($tables) ? array_column($tables, "name") : []; + return $tables; + } + + public static function dbTruncate($db, array $afterStatements = []): void { + // rollback any pending transaction + try { + $db->query("UNLOCK TABLES; ROLLBACK"); + } catch (\Throwable $e) { + } + $db->query("SET FOREIGN_KEY_CHECKS=0"); + foreach (self::dbTableList($db) as $table) { + if ($table === "arsse_meta") { + $db->query("DELETE FROM $table where `key` <> 'schema_version'"); + } else { + $db->query("TRUNCATE TABLE $table"); + } + } + foreach ($afterStatements as $st) { + $db->query($st); + } + $db->query("SET FOREIGN_KEY_CHECKS=1"); + } + + public static function dbRaze($db, array $afterStatements = []): void { + // rollback any pending transaction + try { + $db->query("UNLOCK TABLES; ROLLBACK"); + } catch (\Throwable $e) { + } + $db->query("SET FOREIGN_KEY_CHECKS=0"); + foreach (self::dbTableList($db) as $table) { + $db->query("DROP TABLE IF EXISTS $table"); + } + foreach ($afterStatements as $st) { + $db->query($st); + } + $db->query("SET FOREIGN_KEY_CHECKS=1"); + } +} \ No newline at end of file diff --git a/tests/lib/DatabaseDrivers/MySQLPDO.php b/tests/lib/DatabaseDrivers/MySQLPDO.php index bbaf8abb..46248684 100644 --- a/tests/lib/DatabaseDrivers/MySQLPDO.php +++ b/tests/lib/DatabaseDrivers/MySQLPDO.php @@ -9,6 +9,8 @@ namespace JKingWeb\Arsse\Test\DatabaseDrivers; use JKingWeb\Arsse\Arsse; trait MySQLPDO { + use MySQLCommon; + protected static $implementation = "PDO MySQL"; protected static $backend = "MySQL"; protected static $dbResultClass = \JKingWeb\Arsse\Db\PDOResult::class; @@ -31,6 +33,7 @@ trait MySQLPDO { $dsn = "mysql:".implode(";", $dsn); $d = new \PDO($dsn, Arsse::$conf->dbMySQLUser, Arsse::$conf->dbMySQLPass, [ \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + \PDO::ATTR_STRINGIFY_FETCHES => true, \PDO::MYSQL_ATTR_MULTI_STATEMENTS => false, ]); foreach (\JKingWeb\Arsse\Db\MySQL\PDODriver::makeSetupQueries() as $q) { @@ -41,16 +44,4 @@ trait MySQLPDO { return; } } - - public static function dbTableList($db): array { - return MySQL::dbTableList($db); - } - - public static function dbTruncate($db, array $afterStatements = []): void { - MySQL::dbTruncate($db, $afterStatements); - } - - public static function dbRaze($db, array $afterStatements = []): void { - MySQL::dbRaze($db, $afterStatements); - } } diff --git a/tests/lib/DatabaseDrivers/PostgreSQL.php b/tests/lib/DatabaseDrivers/PostgreSQL.php index edc75493..efd3d12a 100644 --- a/tests/lib/DatabaseDrivers/PostgreSQL.php +++ b/tests/lib/DatabaseDrivers/PostgreSQL.php @@ -10,6 +10,8 @@ use JKingWeb\Arsse\Arsse; use JKingWeb\Arsse\Db\Driver; trait PostgreSQL { + use PostgreSQLCommon; + protected static $implementation = "PostgreSQL"; protected static $backend = "PostgreSQL"; protected static $dbResultClass = \JKingWeb\Arsse\Db\PostgreSQL\Result::class; @@ -28,61 +30,4 @@ trait PostgreSQL { return; } } - - public static function dbExec($db, $q): void { - if ($db instanceof Driver) { - $db->exec($q); - } elseif ($db instanceof \PDO) { - $db->exec($q); - } else { - pg_query($db, $q); - } - } - - public static function dbTableList($db): array { - $listObjects = "SELECT table_name as name, 'TABLE' as type from information_schema.tables where table_schema = current_schema() and table_name like 'arsse_%' union SELECT collation_name as name, 'COLLATION' as type from information_schema.collations where collation_schema = current_schema()"; - if ($db instanceof Driver) { - return $db->query($listObjects)->getAll(); - } elseif ($db instanceof \PDO) { - return $db->query($listObjects)->fetchAll(\PDO::FETCH_ASSOC); - } else { - $r = @pg_query($db, $listObjects); - $out = $r ? pg_fetch_all($r) : false; - return $out ? $out : []; - } - } - - public static function dbTruncate($db, array $afterStatements = []): void { - // rollback any pending transaction - try { - @self::dbExec($db, "ROLLBACK"); - } catch (\Throwable $e) { - } - foreach (self::dbTableList($db) as $obj) { - if ($obj['type'] !== "TABLE") { - continue; - } elseif ($obj['name'] === "arsse_meta") { - self::dbExec($db, "DELETE FROM {$obj['name']} where key <> 'schema_version'"); - } else { - self::dbExec($db, "TRUNCATE TABLE {$obj['name']} restart identity cascade"); - } - } - foreach ($afterStatements as $st) { - self::dbExec($db, $st); - } - } - - public static function dbRaze($db, array $afterStatements = []): void { - // rollback any pending transaction - try { - @self::dbExec($db, "ROLLBACK"); - } catch (\Throwable $e) { - } - foreach (self::dbTableList($db) as $obj) { - self::dbExec($db, "DROP {$obj['type']} IF EXISTS {$obj['name']} cascade"); - } - foreach ($afterStatements as $st) { - self::dbExec($db, $st); - } - } } diff --git a/tests/lib/DatabaseDrivers/PostgreSQLCommon.php b/tests/lib/DatabaseDrivers/PostgreSQLCommon.php new file mode 100644 index 00000000..def4c5ff --- /dev/null +++ b/tests/lib/DatabaseDrivers/PostgreSQLCommon.php @@ -0,0 +1,69 @@ +exec($q); + } elseif ($db instanceof \PDO) { + $db->exec($q); + } else { + pg_query($db, $q); + } + } + + public static function dbTableList($db): array { + $listObjects = "SELECT table_name as name, 'TABLE' as type from information_schema.tables where table_schema = current_schema() and table_name like 'arsse_%' union SELECT collation_name as name, 'COLLATION' as type from information_schema.collations where collation_schema = current_schema()"; + if ($db instanceof Driver) { + return $db->query($listObjects)->getAll(); + } elseif ($db instanceof \PDO) { + return $db->query($listObjects)->fetchAll(\PDO::FETCH_ASSOC); + } else { + $r = @pg_query($db, $listObjects); + $out = $r ? pg_fetch_all($r) : false; + return $out ? $out : []; + } + } + + public static function dbTruncate($db, array $afterStatements = []): void { + // rollback any pending transaction + try { + @self::dbExec($db, "ROLLBACK"); + } catch (\Throwable $e) { + } + foreach (self::dbTableList($db) as $obj) { + if ($obj['type'] !== "TABLE") { + continue; + } elseif ($obj['name'] === "arsse_meta") { + self::dbExec($db, "DELETE FROM {$obj['name']} where key <> 'schema_version'"); + } else { + self::dbExec($db, "TRUNCATE TABLE {$obj['name']} restart identity cascade"); + } + } + foreach ($afterStatements as $st) { + self::dbExec($db, $st); + } + } + + public static function dbRaze($db, array $afterStatements = []): void { + // rollback any pending transaction + try { + @self::dbExec($db, "ROLLBACK"); + } catch (\Throwable $e) { + } + foreach (self::dbTableList($db) as $obj) { + self::dbExec($db, "DROP {$obj['type']} IF EXISTS {$obj['name']} cascade"); + } + foreach ($afterStatements as $st) { + self::dbExec($db, $st); + } + } +} \ No newline at end of file diff --git a/tests/lib/DatabaseDrivers/PostgreSQLPDO.php b/tests/lib/DatabaseDrivers/PostgreSQLPDO.php index 116c3b23..6ef101b4 100644 --- a/tests/lib/DatabaseDrivers/PostgreSQLPDO.php +++ b/tests/lib/DatabaseDrivers/PostgreSQLPDO.php @@ -9,6 +9,8 @@ namespace JKingWeb\Arsse\Test\DatabaseDrivers; use JKingWeb\Arsse\Arsse; trait PostgreSQLPDO { + use PostgreSQLCommon; + protected static $implementation = "PDO PostgreSQL"; protected static $backend = "PostgreSQL"; protected static $dbResultClass = \JKingWeb\Arsse\Db\PostgreSQL\PDOResult::class; @@ -28,16 +30,4 @@ trait PostgreSQLPDO { } return $d; } - - public static function dbTableList($db): array { - return PostgreSQL::dbTableList($db); - } - - public static function dbTruncate($db, array $afterStatements = []): void { - PostgreSQL::dbTruncate($db, $afterStatements); - } - - public static function dbRaze($db, array $afterStatements = []): void { - PostgreSQL::dbRaze($db, $afterStatements); - } } diff --git a/tests/lib/DatabaseDrivers/SQLite3.php b/tests/lib/DatabaseDrivers/SQLite3.php index 70633722..721a440f 100644 --- a/tests/lib/DatabaseDrivers/SQLite3.php +++ b/tests/lib/DatabaseDrivers/SQLite3.php @@ -10,6 +10,8 @@ use JKingWeb\Arsse\Arsse; use JKingWeb\Arsse\Db\Driver; trait SQLite3 { + use SQLite3Common; + protected static $implementation = "SQLite 3"; protected static $backend = "SQLite 3"; protected static $dbResultClass = \JKingWeb\Arsse\Db\SQLite3\Result::class; @@ -26,63 +28,4 @@ trait SQLite3 { $d->enableExceptions(true); return $d; } - - public static function dbTableList($db): array { - $listTables = "SELECT name from sqlite_master where type = 'table' and name like 'arsse^_%' escape '^'"; - if ($db instanceof Driver) { - $tables = $db->query($listTables)->getAll(); - $tables = sizeof($tables) ? array_column($tables, "name") : []; - } elseif ($db instanceof \PDO) { - retry: - try { - $tables = $db->query($listTables)->fetchAll(\PDO::FETCH_ASSOC); - } catch (\PDOException $e) { - goto retry; - } - $tables = sizeof($tables) ? array_column($tables, "name") : []; - } else { - $tables = []; - $result = $db->query($listTables); - while ($r = $result->fetchArray(\SQLITE3_ASSOC)) { - $tables[] = $r['name']; - } - $result->finalize(); - } - return $tables; - } - - public static function dbTruncate($db, array $afterStatements = []): void { - // rollback any pending transaction - try { - $db->exec("ROLLBACK"); - } catch (\Throwable $e) { - } - foreach (self::dbTableList($db) as $table) { - if ($table === "arsse_meta") { - $db->exec("DELETE FROM $table where key <> 'schema_version'"); - } else { - $db->exec("DELETE FROM $table"); - } - } - foreach ($afterStatements as $st) { - $db->exec($st); - } - } - - public static function dbRaze($db, array $afterStatements = []): void { - // rollback any pending transaction - try { - $db->exec("ROLLBACK"); - } catch (\Throwable $e) { - } - $db->exec("PRAGMA foreign_keys=0"); - foreach (self::dbTableList($db) as $table) { - $db->exec("DROP TABLE IF EXISTS $table"); - } - $db->exec("PRAGMA user_version=0"); - $db->exec("PRAGMA foreign_keys=1"); - foreach ($afterStatements as $st) { - $db->exec($st); - } - } } diff --git a/tests/lib/DatabaseDrivers/SQLite3Common.php b/tests/lib/DatabaseDrivers/SQLite3Common.php new file mode 100644 index 00000000..b180cc34 --- /dev/null +++ b/tests/lib/DatabaseDrivers/SQLite3Common.php @@ -0,0 +1,70 @@ +query($listTables)->getAll(); + $tables = sizeof($tables) ? array_column($tables, "name") : []; + } elseif ($db instanceof \PDO) { + retry: + try { + $tables = $db->query($listTables)->fetchAll(\PDO::FETCH_ASSOC); + } catch (\PDOException $e) { + goto retry; + } + $tables = sizeof($tables) ? array_column($tables, "name") : []; + } else { + $tables = []; + $result = $db->query($listTables); + while ($r = $result->fetchArray(\SQLITE3_ASSOC)) { + $tables[] = $r['name']; + } + $result->finalize(); + } + return $tables; + } + + public static function dbTruncate($db, array $afterStatements = []): void { + // rollback any pending transaction + try { + $db->exec("ROLLBACK"); + } catch (\Throwable $e) { + } + foreach (self::dbTableList($db) as $table) { + if ($table === "arsse_meta") { + $db->exec("DELETE FROM $table where key <> 'schema_version'"); + } else { + $db->exec("DELETE FROM $table"); + } + } + foreach ($afterStatements as $st) { + $db->exec($st); + } + } + + public static function dbRaze($db, array $afterStatements = []): void { + // rollback any pending transaction + try { + $db->exec("ROLLBACK"); + } catch (\Throwable $e) { + } + $db->exec("PRAGMA foreign_keys=0"); + foreach (self::dbTableList($db) as $table) { + $db->exec("DROP TABLE IF EXISTS $table"); + } + $db->exec("PRAGMA user_version=0"); + $db->exec("PRAGMA foreign_keys=1"); + foreach ($afterStatements as $st) { + $db->exec($st); + } + } +} \ No newline at end of file diff --git a/tests/lib/DatabaseDrivers/SQLite3PDO.php b/tests/lib/DatabaseDrivers/SQLite3PDO.php index 0a81eb86..b141cda1 100644 --- a/tests/lib/DatabaseDrivers/SQLite3PDO.php +++ b/tests/lib/DatabaseDrivers/SQLite3PDO.php @@ -9,6 +9,8 @@ namespace JKingWeb\Arsse\Test\DatabaseDrivers; use JKingWeb\Arsse\Arsse; trait SQLite3PDO { + use SQLite3Common; + protected static $implementation = "PDO SQLite 3"; protected static $backend = "SQLite 3"; protected static $dbResultClass = \JKingWeb\Arsse\Db\PDOResult::class; @@ -18,23 +20,14 @@ trait SQLite3PDO { public static function dbInterface() { try { - $d = new \PDO("sqlite:".Arsse::$conf->dbSQLite3File, "", "", [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]); + $d = new \PDO("sqlite:".Arsse::$conf->dbSQLite3File, "", "", [ + \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + \PDO::ATTR_STRINGIFY_FETCHES => true, + ]); $d->exec("PRAGMA busy_timeout=0"); return $d; } catch (\Throwable $e) { return; } } - - public static function dbTableList($db): array { - return SQLite3::dbTableList($db); - } - - public static function dbTruncate($db, array $afterStatements = []): void { - SQLite3::dbTruncate($db, $afterStatements); - } - - public static function dbRaze($db, array $afterStatements = []): void { - SQLite3::dbRaze($db, $afterStatements); - } }