From b23bf6c055946dd75ecc0d5d28ef71164fe9f47d Mon Sep 17 00:00:00 2001 From: Jakub Vrana Date: Thu, 27 Mar 2025 18:27:51 +0100 Subject: [PATCH] PHPStan: Fix more errors --- adminer/drivers/mysql.inc.php | 11 ++++++----- adminer/drivers/pgsql.inc.php | 2 +- adminer/drivers/sqlite.inc.php | 13 +++++++------ adminer/include/bootstrap.inc.php | 4 ++-- adminer/include/editing.inc.php | 14 +++++++------- adminer/include/functions.inc.php | 13 ++++++++----- adminer/include/html.inc.php | 4 ++-- adminer/include/pdo.inc.php | 8 +++++--- adminer/select.inc.php | 5 ++++- adminer/sql.inc.php | 4 ++-- phpstan.neon | 19 ++++++++++--------- 11 files changed, 54 insertions(+), 43 deletions(-) diff --git a/adminer/drivers/mysql.inc.php b/adminer/drivers/mysql.inc.php index 26bd220e..3047d68e 100644 --- a/adminer/drivers/mysql.inc.php +++ b/adminer/drivers/mysql.inc.php @@ -783,17 +783,18 @@ if (!defined('Adminer\DRIVER')) { /** Run commands to alter indexes * @param string escaped table name - * @param array{string, string, 'DROP'|list} of ["index type", "name", ["column definition", ...]] or ["index type", "name", "DROP"] + * @param list}> of ["index type", "name", ["column definition", ...]] or ["index type", "name", "DROP"] * @return Result|bool */ function alter_indexes($table, $alter) { - foreach ($alter as $key => $val) { - $alter[$key] = ($val[2] == "DROP" + $changes = array(); + foreach ($alter as $val) { + $changes[] = ($val[2] == "DROP" ? "\nDROP INDEX " . idf_escape($val[1]) : "\nADD $val[0] " . ($val[0] == "PRIMARY" ? "KEY " : "") . ($val[1] != "" ? idf_escape($val[1]) . " " : "") . "(" . implode(", ", $val[2]) . ")" ); } - return queries("ALTER TABLE " . table($table) . implode(",", $alter)); + return queries("ALTER TABLE " . table($table) . implode(",", $changes)); } /** Run commands to truncate tables @@ -925,7 +926,7 @@ if (!defined('Adminer\DRIVER')) { /** Get information about stored routine * @param string - * @param string "FUNCTION" or "PROCEDURE" + * @param 'FUNCTION'|'PROCEDURE' * @return Routine */ function routine($name, $type) { diff --git a/adminer/drivers/pgsql.inc.php b/adminer/drivers/pgsql.inc.php index 253d60ca..5b931e80 100644 --- a/adminer/drivers/pgsql.inc.php +++ b/adminer/drivers/pgsql.inc.php @@ -844,7 +844,7 @@ AND typelem = 0" foreach ($fields as $field) { $part = idf_escape($field['field']) . ' ' . $field['full_type'] . default_value($field) - . ($field['attnotnull'] ? " NOT NULL" : ""); + . ($field['null'] ? "" : " NOT NULL"); $return_parts[] = $part; // sequences for fields diff --git a/adminer/drivers/sqlite.inc.php b/adminer/drivers/sqlite.inc.php index c0f52c8f..7826a52a 100644 --- a/adminer/drivers/sqlite.inc.php +++ b/adminer/drivers/sqlite.inc.php @@ -521,23 +521,24 @@ if (isset($_GET["sqlite"])) { } queries("BEGIN"); } - foreach ($fields as $key => $field) { + $changes = array(); + foreach ($fields as $field) { if (preg_match('~GENERATED~', $field[3])) { unset($originals[array_search($field[0], $originals)]); } - $fields[$key] = " " . implode($field); + $changes[] = " " . implode($field); } - $fields = array_merge($fields, array_filter($foreign)); + $changes = array_merge($changes, array_filter($foreign)); foreach ($driver->checkConstraints($table) as $check) { if ($check != $drop_check) { - $fields[] = " CHECK ($check)"; + $changes[] = " CHECK ($check)"; } } if ($add_check) { - $fields[] = " CHECK ($add_check)"; + $changes[] = " CHECK ($add_check)"; } $temp_name = ($table == $name ? "adminer_$name" : $name); - if (!queries("CREATE TABLE " . table($temp_name) . " (\n" . implode(",\n", $fields) . "\n)")) { + if (!queries("CREATE TABLE " . table($temp_name) . " (\n" . implode(",\n", $changes) . "\n)")) { // implicit ROLLBACK to not overwrite $connection->error return false; } diff --git a/adminer/include/bootstrap.inc.php b/adminer/include/bootstrap.inc.php index 848d487c..3fdf2266 100644 --- a/adminer/include/bootstrap.inc.php +++ b/adminer/include/bootstrap.inc.php @@ -52,7 +52,7 @@ if ($_SERVER["HTTP_X_FORWARDED_PREFIX"]) { } $HTTPS = ($_SERVER["HTTPS"] && strcasecmp($_SERVER["HTTPS"], "off")) || ini_bool("session.cookie_secure"); // session.cookie_secure could be set on HTTP if we are behind a reverse proxy -@ini_set("session.use_trans_sid", false); // protect links in export, @ - may be disabled +@ini_set("session.use_trans_sid", '0'); // protect links in export, @ - may be disabled if (!defined("SID")) { session_cache_limiter(""); // to allow restarting session session_name("adminer_sid"); // use specific session name to get own namespace @@ -66,7 +66,7 @@ if (function_exists("get_magic_quotes_runtime") && get_magic_quotes_runtime()) { set_magic_quotes_runtime(false); } @set_time_limit(0); // @ - can be disabled -@ini_set("precision", 15); // @ - can be disabled, 15 - internal PHP precision +@ini_set("precision", '15'); // @ - can be disabled, 15 - internal PHP precision include "../adminer/include/lang.inc.php"; include "../adminer/lang/$LANG.inc.php"; diff --git a/adminer/include/editing.inc.php b/adminer/include/editing.inc.php index c0ad3ab3..8603d033 100644 --- a/adminer/include/editing.inc.php +++ b/adminer/include/editing.inc.php @@ -102,7 +102,7 @@ function select($result, $connection2 = null, $orgtables = array(), $limit = 0) /** Get referencable tables with single column primary key except self * @param string -* @return Field[] [$table_name => $field] +* @return array [$table_name => $field] */ function referencable_primary($self) { $return = array(); // table_name => field @@ -180,7 +180,7 @@ function json_row($key, $val = null) { * @param string * @param Field * @param list -* @param Field[] returned by referencable_primary() +* @param string[] * @param list extra types to prepend * @return void */ @@ -246,7 +246,7 @@ function process_length($length) { } /** Create SQL string from field type -* @param Field +* @param FieldType * @param string * @return string */ @@ -317,10 +317,10 @@ function type_class($type) { } /** Print table interior for fields editing -* @param Field[] +* @param (Field|RoutineField)[] * @param list -* @param string TABLE or PROCEDURE -* @param Field[] returned by referencable_primary() +* @param 'TABLE'|'PROCEDURE' +* @param string[] * @return void */ function edit_fields($fields, $collations, $type = "TABLE", $foreign_keys = array()) { @@ -502,7 +502,7 @@ function create_trigger($on, $row) { } /** Generate SQL query for creating routine -* @param string "PROCEDURE" or "FUNCTION" +* @param 'PROCEDURE'|'FUNCTION' * @param Routine result of routine() * @return string */ diff --git a/adminer/include/functions.inc.php b/adminer/include/functions.inc.php index 1890290b..790df98d 100644 --- a/adminer/include/functions.inc.php +++ b/adminer/include/functions.inc.php @@ -354,7 +354,7 @@ function where_link($i, $column, $value, $operator = "=") { } /** Get select clause for convertible fields -* @param string[] +* @param mixed[] only keys are used * @param Field[] * @param list * @return string @@ -436,7 +436,7 @@ function stop_session($force = false) { $use_cookies = ini_bool("session.use_cookies"); if (!$use_cookies || $force) { session_write_close(); // improves concurrency if a user opens several pages at once, may be restarted later - if ($use_cookies && @ini_set("session.use_cookies", false) === false) { // @ - may be disabled + if ($use_cookies && @ini_set("session.use_cookies", '0') === false) { // @ - may be disabled session_start(); } } @@ -999,9 +999,12 @@ function slow_query($query) { $timeout = $adminer->queryTimeout(); $slow_query = $driver->slowQuery($query, $timeout); $connection2 = null; - if (!$slow_query && support("kill") && is_object($connection2 = connect($adminer->credentials())) && ($db == "" || $connection2->select_db($db))) { - $kill = $connection2->result(connection_id()); // MySQL and MySQLi can use thread_id but it's not in PDO_MySQL - echo script("const timeout = setTimeout(() => { ajax('" . js_escape(ME) . "script=kill', function () {}, 'kill=$kill&token=$token'); }, 1000 * $timeout);"); + if (!$slow_query && support("kill")) { + $connection2 = connect($adminer->credentials()); + if (is_object($connection2) && ($db == "" || $connection2->select_db($db))) { + $kill = $connection2->result(connection_id()); // MySQL and MySQLi can use thread_id but it's not in PDO_MySQL + echo script("const timeout = setTimeout(() => { ajax('" . js_escape(ME) . "script=kill', function () {}, 'kill=$kill&token=$token'); }, 1000 * $timeout);"); + } } ob_flush(); flush(); diff --git a/adminer/include/html.inc.php b/adminer/include/html.inc.php index dcaa345a..ea548e9c 100644 --- a/adminer/include/html.inc.php +++ b/adminer/include/html.inc.php @@ -247,7 +247,7 @@ function enum_input($type, $attrs, $field, $value, $empty = null) { } /** Print edit input field -* @param Field one field from fields() +* @param Field|RoutineField one field from fields() * @param mixed * @param string * @param bool @@ -343,7 +343,7 @@ function input($field, $value, $function, $autofocus = false) { } /** Process edit input field -* @param Field one field from fields() +* @param Field|RoutineField one field from fields() * @return mixed false to leave the original value */ function process_input($field) { diff --git a/adminer/include/pdo.inc.php b/adminer/include/pdo.inc.php index 2c71ca95..1447a1e3 100644 --- a/adminer/include/pdo.inc.php +++ b/adminer/include/pdo.inc.php @@ -59,11 +59,13 @@ if (extension_loaded('pdo')) { } function next_result() { - if (!is_object($this->multi)) { + /** @var PdoResult|bool */ + $result = $this->multi; + if (!is_object($result)) { return false; } - $this->multi->_offset = 0; - return @$this->multi->nextRowset(); // @ - PDO_PgSQL doesn't support it + $result->_offset = 0; + return @$result->nextRowset(); // @ - PDO_PgSQL doesn't support it } } diff --git a/adminer/select.inc.php b/adminer/select.inc.php index 0a56766f..b6f7f03a 100644 --- a/adminer/select.inc.php +++ b/adminer/select.inc.php @@ -127,7 +127,10 @@ if ($_POST && !$error) { : $driver->update($TABLE, $set, $where_check) ) ); - $affected = $connection->affected_rows + (is_object($result) ? $result->num_rows : 0); // PostgreSQL with RETURNING fills num_rows + $affected = $connection->affected_rows; + if (is_object($result)) { // PostgreSQL with RETURNING fills num_rows + $affected += $result->num_rows; + } } else { foreach ((array) $_POST["check"] as $val) { // where is not unique so OR can't be used diff --git a/adminer/sql.inc.php b/adminer/sql.inc.php index 67ded633..fe034a32 100644 --- a/adminer/sql.inc.php +++ b/adminer/sql.inc.php @@ -37,7 +37,7 @@ if (!$error && $_POST) { if (is_string($query)) { // get_file() returns error as number, fread() as false if (function_exists('memory_get_usage') && ($memory_limit = ini_bytes("memory_limit")) != "-1") { - @ini_set("memory_limit", max($memory_limit, 2 * strlen($query) + memory_get_usage() + 8e6)); // @ - may be disabled, 2 - substr and trim, 8e6 - other variables + @ini_set("memory_limit", max($memory_limit, strval(2 * strlen($query) + memory_get_usage() + 8e6))); // @ - may be disabled, 2 - substr and trim, 8e6 - other variables } if ($query != "" && strlen($query) < 1e6) { // don't add big queries @@ -182,7 +182,7 @@ if (!$error && $_POST) { stop_session(); } if (!$_POST["only_errors"]) { - echo "

" . lang('Query executed OK, %d row(s) affected.', $affected) . "$time\n"; + echo "

" . lang('Query executed OK, %d row(s) affected.', $affected) . "$time\n"; } } echo ($warnings ? "

\n" : ""); diff --git a/phpstan.neon b/phpstan.neon index d6a2399a..99bc9d14 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,18 +6,14 @@ parameters: # need to fix - "~^Function Adminer\\\\fields_from_edit\\(\\) should return~" # Mongo and SimpleDB - "~Adminer\\\\Result.*mysqli_result~" # mysqli_result - - "~expects array~" # different shape of array # not real problems - - identifier: include.fileNotFound # relative includes + - identifier: include.fileNotFound # includes in include/ relative from index.php - identifier: includeOnce.fileNotFound # ./adminer-plugins.php - "~^Function (set_magic_quotes_runtime|mysql_)~" # PHP < 7 functions - "~an unknown class OCI-?Lob~" # this looks like PHPStan bug - "~^Variable \\$(adminer|connection|driver|drivers|error|HTTPS|LANG|langs|permanent|has_token|token|translations|VERSION) might not be defined~" # declared in bootstrap.inc.php - "~^Method Adminer\\\\Plugins::\\w+\\(\\) with return type void~" # we use the same pattern for all methods - - "~Call to function is_object\\(\\) with Adminer\\\\Db\\|string will always evaluate to false~" # is_object(Db) is true - - "~^Comparison operation \"==\" between \\(array\\|float\\|int\\) and 1~" # it thinks that $affected could be an array - - "~^Parameter #2 \\$newvalue of function ini_set expects string~" # it expects string|int|float|bool|null since PHP 8.1 - "~expects int, float given~" # this will work - "~expects bool~" # truthy values - "~fread expects int<1, max>, 100000~" # 1e6 @@ -28,6 +24,10 @@ parameters: paths: - adminer/include/pdo.inc.php - adminer/drivers/* + + - + message: "~ to an undefined ~" # PostgreSQL has this in its version of Db + path: adminer/drivers/pgsql.inc.php # it probably doesn't like $ar[$key] instead of isset($ar[$key]) and thinks that $ar[$key] is always set - identifier: identical.alwaysFalse @@ -64,10 +64,11 @@ parameters: typeAliases: TableStatus: "array{Name:string, Engine?:?string, Comment?:string, Oid?:numeric-string, Rows?:?numeric-string, Collation?:string, Auto_increment?:?numeric-string, Data_length?:numeric-string, Index_length?:numeric-string, Data_free?:numeric-string, Create_options?:string, nspname?:string}" - Field: "array{field:?string, full_type:string, type:string, length:numeric-string, unsigned:string, default:?string, null:bool, auto_increment:bool, collation:string, privileges:int[], comment:string, primary:bool, generated:string, orig?:string, on_update:?string, on_delete?:string, inout?:string}" + Field: "array{field?:string, full_type:string, type:string, length:numeric-string, unsigned:string, default?:string, null:bool, auto_increment:bool, collation:string, privileges:int[], comment:string, primary:bool, generated:string, orig?:string, on_update?:string, on_delete?:string, default_constraint?: string}" + FieldType: "array{type:string, length:numeric-string, unsigned:string, collation:string}" # subset of RoutineField and Field + RoutineField: "array{field:string, type:string, length:numeric-string, unsigned:string, null:bool, full_type:string, collation:string, inout?:string}" Index: "array{type:string, columns:list, lengths:list, descs:list}" - ForeignKey: "array{db:string, ns?:string, table:string, source:list, target:list, on_delete:string, on_update:string}" + ForeignKey: "array{db?:string, ns?:string, table:string, source:list, target:list, on_delete:string, on_update?:string, definition?:string, deferrable?:string}" Trigger: "array{Trigger?:string, Timing?:string, Event?:string, Of?:string, Type?:string, Statement?:string}" - RoutineField: "array{field:string, type:string, length:?string, unsigned:string, null:bool, full_type:string, inout:string, collation:string}" - Routine: "array{name?:string, fields:list, comment:string, returns?:array{type:string, length:string, unsigned:string, collation:string}, definition:string, language?:string}" + Routine: "array{name?:string, fields:list, comment:string, returns?:FieldType, definition:string, language?:string}" BackwardKey: "array{name:string, keys:string[][]}"