Merge pull request #18023 from MauricioFauth/dbal-statement

Create `Dbal/Statement` interface
This commit is contained in:
Maurício Meneghini Fauth 2023-01-20 07:22:12 -03:00 committed by GitHub
commit 6ca4c24848
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 132 additions and 43 deletions

View File

@ -12,6 +12,7 @@ use PhpMyAdmin\Dbal\DbalInterface;
use PhpMyAdmin\Dbal\DbiExtension;
use PhpMyAdmin\Dbal\DbiMysqli;
use PhpMyAdmin\Dbal\ResultInterface;
use PhpMyAdmin\Dbal\Statement;
use PhpMyAdmin\Dbal\Warning;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Query\Cache;
@ -2092,10 +2093,8 @@ class DatabaseInterface implements DbalInterface
*
* @param string $query The query, as a string.
* @psalm-param ConnectionType $connectionType
*
* @return object|false A statement object or false.
*/
public function prepare(string $query, int $connectionType = Connection::TYPE_USER)
public function prepare(string $query, int $connectionType = Connection::TYPE_USER): ?Statement
{
return $this->extension->prepare($this->connections[$connectionType], $query);
}

View File

@ -679,8 +679,6 @@ interface DbalInterface
*
* @param string $query The query, as a string.
* @psalm-param ConnectionType $connectionType
*
* @return object|false A statement object or false.
*/
public function prepare(string $query, int $connectionType = Connection::TYPE_USER);
public function prepare(string $query, int $connectionType = Connection::TYPE_USER): ?Statement;
}

View File

@ -109,10 +109,8 @@ interface DbiExtension
* Prepare an SQL statement for execution.
*
* @param string $query The query, as a string.
*
* @return object|false A statement object or false.
*/
public function prepare(Connection $connection, string $query);
public function prepare(Connection $connection, string $query): ?Statement;
/**
* Returns the number of warnings from the last query.

View File

@ -9,7 +9,6 @@ namespace PhpMyAdmin\Dbal;
use mysqli;
use mysqli_sql_exception;
use mysqli_stmt;
use PhpMyAdmin\Config\Settings\Server;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Query\Utilities;
@ -346,15 +345,17 @@ class DbiMysqli implements DbiExtension
* Prepare an SQL statement for execution.
*
* @param string $query The query, as a string.
*
* @return mysqli_stmt|false A statement object or false.
*/
public function prepare(Connection $connection, string $query)
public function prepare(Connection $connection, string $query): ?Statement
{
/** @var mysqli $mysqli */
$mysqli = $connection->connection;
$statement = $mysqli->prepare($query);
if ($statement === false) {
return null;
}
return $mysqli->prepare($query);
return new MysqliStatement($statement);
}
/**

View File

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Dbal;
use mysqli_stmt;
use function count;
use function str_repeat;
use const PHP_VERSION_ID;
final class MysqliStatement implements Statement
{
/** @var mysqli_stmt */
private $statement;
public function __construct(mysqli_stmt $statement)
{
$this->statement = $statement;
}
/**
* Executes a prepared statement.
*
* @param list<string> $params
*/
public function execute(array $params): bool
{
$paramCount = $this->statement->param_count;
if (count($params) !== $paramCount) {
return false;
}
if (PHP_VERSION_ID >= 80100) {
/**
* @psalm-suppress TooManyArguments
* @phpstan-ignore-next-line
*/
return $this->statement->execute($params);
}
$types = str_repeat('s', $paramCount);
if (! $this->statement->bind_param($types, ...$params)) {
return false;
}
return $this->statement->execute();
}
/**
* Gets a result set from a prepared statement.
*/
public function getResult(): ResultInterface
{
return new MysqliResult($this->statement->get_result());
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Dbal;
interface Statement
{
/**
* Executes a prepared statement.
*
* @param list<string> $params
*/
public function execute(array $params): bool;
/**
* Gets a result set from a prepared statement.
*/
public function getResult(): ResultInterface;
}

View File

@ -7,7 +7,6 @@ declare(strict_types=1);
namespace PhpMyAdmin\Server;
use mysqli_stmt;
use PhpMyAdmin\ConfigStorage\Features\ConfigurableMenusFeature;
use PhpMyAdmin\ConfigStorage\Relation;
use PhpMyAdmin\ConfigStorage\RelationCleanup;
@ -15,7 +14,6 @@ use PhpMyAdmin\Database\Routines;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Dbal\Connection;
use PhpMyAdmin\Dbal\DatabaseName;
use PhpMyAdmin\Dbal\MysqliResult;
use PhpMyAdmin\Dbal\ResultInterface;
use PhpMyAdmin\Dbal\TableName;
use PhpMyAdmin\Html\Generator;
@ -1425,13 +1423,12 @@ class Privileges
NOT (`Table_priv` = \'\' AND Column_priv = \'\')
ORDER BY `User` ASC, `Host` ASC, `Db` ASC, `Table_priv` ASC;
';
/** @var mysqli_stmt|false $statement */
$statement = $this->dbi->prepare($query);
if ($statement === false || ! $statement->bind_param('ss', $db, $table) || ! $statement->execute()) {
if ($statement === null || ! $statement->execute([$db->getName(), $table->getName()])) {
return [];
}
$result = new MysqliResult($statement->get_result());
$result = $statement->getResult();
return $result->fetchAllAssoc();
}
@ -3769,13 +3766,12 @@ class Privileges
private function getUserPrivileges(string $user, string $host, bool $hasAccountLocking): ?array
{
$query = 'SELECT * FROM `mysql`.`user` WHERE `User` = ? AND `Host` = ?;';
/** @var mysqli_stmt|false $statement */
$statement = $this->dbi->prepare($query);
if ($statement === false || ! $statement->bind_param('ss', $user, $host) || ! $statement->execute()) {
if ($statement === null || ! $statement->execute([$user, $host])) {
return null;
}
$result = new MysqliResult($statement->get_result());
$result = $statement->getResult();
/** @var array<string, string|null>|null $userPrivileges */
$userPrivileges = $result->fetchAssoc();
if ($userPrivileges === []) {
@ -3789,13 +3785,12 @@ class Privileges
$userPrivileges['account_locked'] = 'N';
$query = 'SELECT * FROM `mysql`.`global_priv` WHERE `User` = ? AND `Host` = ?;';
/** @var mysqli_stmt|false $statement */
$statement = $this->dbi->prepare($query);
if ($statement === false || ! $statement->bind_param('ss', $user, $host) || ! $statement->execute()) {
if ($statement === null || ! $statement->execute([$user, $host])) {
return $userPrivileges;
}
$result = new MysqliResult($statement->get_result());
$result = $statement->getResult();
/** @var array<string, string|null>|null $globalPrivileges */
$globalPrivileges = $result->fetchAssoc();
if ($globalPrivileges === []) {

View File

@ -4,12 +4,12 @@ declare(strict_types=1);
namespace PhpMyAdmin\Tests;
use mysqli_stmt;
use PhpMyAdmin\ConfigStorage\Relation;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Dbal\Connection;
use PhpMyAdmin\Dbal\DbiExtension;
use PhpMyAdmin\Dbal\ResultInterface;
use PhpMyAdmin\Dbal\Statement;
use PhpMyAdmin\LanguageManager;
use PhpMyAdmin\Query\Utilities;
use PhpMyAdmin\SqlParser\Context;
@ -826,7 +826,7 @@ class DatabaseInterfaceTest extends AbstractTestCase
public function testPrepare(): void
{
$query = 'SELECT * FROM `mysql`.`user` WHERE `User` = ? AND `Host` = ?;';
$stmtStub = $this->createStub(mysqli_stmt::class);
$stmtStub = $this->createStub(Statement::class);
$dummyDbi = $this->createMock(DbiExtension::class);
$dummyDbi->expects($this->once())->method('prepare')
->with($this->isType('object'), $this->equalTo($query))

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Tests\Dbal;
use mysqli_stmt;
use PhpMyAdmin\Dbal\MysqliResult;
use PhpMyAdmin\Dbal\MysqliStatement;
use PHPUnit\Framework\TestCase;
/**
* @covers \PhpMyAdmin\Dbal\MysqliStatement
* @covers \PhpMyAdmin\Dbal\MysqliResult
*/
class MysqliStatementTest extends TestCase
{
public function testGetResult(): void
{
$mysqliStmt = $this->createMock(mysqli_stmt::class);
$mysqliStmt->expects($this->once())->method('get_result')->willReturn(false);
$statement = new MysqliStatement($mysqliStmt);
$result = $statement->getResult();
$this->assertInstanceOf(MysqliResult::class, $result);
}
}

View File

@ -4,12 +4,12 @@ declare(strict_types=1);
namespace PhpMyAdmin\Tests\Server;
use mysqli_result;
use mysqli_stmt;
use PhpMyAdmin\ConfigStorage\Relation;
use PhpMyAdmin\ConfigStorage\RelationCleanup;
use PhpMyAdmin\ConfigStorage\RelationParameters;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Dbal\ResultInterface;
use PhpMyAdmin\Dbal\Statement;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Message;
use PhpMyAdmin\Server\Plugins;
@ -1906,13 +1906,10 @@ class PrivilegesTest extends AbstractTestCase
public function testGetUserPrivileges(): void
{
$mysqliResultStub = $this->createMock(mysqli_result::class);
$mysqliStmtStub = $this->createMock(mysqli_stmt::class);
$mysqliStmtStub->expects($this->exactly(2))->method('bind_param')->willReturn(true);
$mysqliResultStub = $this->createMock(ResultInterface::class);
$mysqliStmtStub = $this->createMock(Statement::class);
$mysqliStmtStub->expects($this->exactly(2))->method('execute')->willReturn(true);
$mysqliStmtStub->expects($this->exactly(2))
->method('get_result')
->willReturn($mysqliResultStub);
$mysqliStmtStub->expects($this->exactly(2))->method('getResult')->willReturn($mysqliResultStub);
$dbi = $this->createMock(DatabaseInterface::class);
$dbi->expects($this->once())->method('isMariaDB')->willReturn(true);
@ -1924,7 +1921,7 @@ class PrivilegesTest extends AbstractTestCase
)
->willReturn($mysqliStmtStub);
$mysqliResultStub->expects($this->exactly(2))
->method('fetch_assoc')
->method('fetchAssoc')
->willReturnOnConsecutiveCalls(
['Host' => 'test.host', 'User' => 'test.user'],
['Host' => 'test.host', 'User' => 'test.user', 'Priv' => '{"account_locked":true}']

View File

@ -16,6 +16,7 @@ use PhpMyAdmin\Dbal\Connection;
use PhpMyAdmin\Dbal\DatabaseName;
use PhpMyAdmin\Dbal\DbiExtension;
use PhpMyAdmin\Dbal\ResultInterface;
use PhpMyAdmin\Dbal\Statement;
use PhpMyAdmin\FieldMetadata;
use PHPUnit\Framework\Assert;
use stdClass;
@ -495,14 +496,9 @@ class DbiDummy implements DbiExtension
$this->dummyQueries = [];
}
/**
* @param string $query query
*
* @return object|false
*/
public function prepare(Connection $connection, string $query)
public function prepare(Connection $connection, string $query): ?Statement
{
return false;
return null;
}
/**