init
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Doctrine\WPDB;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Doctrine\WPDB\Exceptions\ConnectionException;
|
||||
use MailPoet\Doctrine\WPDB\Exceptions\QueryException;
|
||||
use MailPoetVendor\Doctrine\DBAL\Driver\ServerInfoAwareConnection;
|
||||
use MailPoetVendor\Doctrine\DBAL\ParameterType;
|
||||
use mysqli;
|
||||
use PDO;
|
||||
use PDOException;
|
||||
use Throwable;
|
||||
use wpdb;
|
||||
|
||||
/**
|
||||
* @phpcs:disable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
*/
|
||||
class Connection implements ServerInfoAwareConnection {
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
if (!$wpdb instanceof wpdb) {
|
||||
throw new ConnectionException('WPDB is not initialized.');
|
||||
}
|
||||
}
|
||||
|
||||
public function prepare(string $sql): Statement {
|
||||
return new Statement($this, $sql);
|
||||
}
|
||||
|
||||
public function query(string $sql): Result {
|
||||
global $wpdb;
|
||||
$value = $this->runQuery($sql);
|
||||
$result = $wpdb->last_result;
|
||||
return new Result($result, is_int($value) ? $value : 0);
|
||||
}
|
||||
|
||||
public function exec(string $sql): int {
|
||||
global $wpdb;
|
||||
$this->runQuery($sql);
|
||||
return $wpdb->rows_affected;
|
||||
}
|
||||
|
||||
public function beginTransaction(): bool {
|
||||
$this->runQuery('START TRANSACTION');
|
||||
return true;
|
||||
}
|
||||
|
||||
public function commit(): bool {
|
||||
$this->runQuery('COMMIT');
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rollBack(): bool {
|
||||
$this->runQuery('ROLLBACK');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quotes a string for use in a query.
|
||||
* The type hint parameter is not needed for WPDB (mysqli).
|
||||
* See also Doctrine\DBAL\Driver\Mysqli\Connection::quote().
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param int $type
|
||||
*/
|
||||
public function quote($value, $type = ParameterType::STRING): string {
|
||||
global $wpdb;
|
||||
return "'" . $wpdb->_escape($value) . "'";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $name
|
||||
*/
|
||||
public function lastInsertId($name = null): int {
|
||||
global $wpdb;
|
||||
return $wpdb->insert_id;
|
||||
}
|
||||
|
||||
public function getServerVersion(): string {
|
||||
global $wpdb;
|
||||
return $wpdb->db_server_info();
|
||||
}
|
||||
|
||||
/**
|
||||
* MySQL — returns an instance of mysqli.
|
||||
* SQLite — returns an instance of PDO.
|
||||
*
|
||||
* @return mysqli|PDO|false|null
|
||||
*/
|
||||
public function getNativeConnection() {
|
||||
global $wpdb;
|
||||
|
||||
// WPDB keeps connection instance (mysqli) in a protected property $dbh.
|
||||
// We can access it using a closure that is bound to the $wpdb instance.
|
||||
$getDbh = function () {
|
||||
return $this->dbh; // @phpstan-ignore-line -- PHPStan doesn't know the binding context
|
||||
};
|
||||
$dbh = $getDbh->call($wpdb);
|
||||
if (is_object($dbh) && method_exists($dbh, 'get_pdo')) {
|
||||
return $dbh->get_pdo();
|
||||
}
|
||||
return $getDbh->call($wpdb);
|
||||
}
|
||||
|
||||
public static function isSQLite(): bool {
|
||||
return defined('DB_ENGINE') && DB_ENGINE === 'sqlite';
|
||||
}
|
||||
|
||||
private function runQuery(string $sql) {
|
||||
global $wpdb;
|
||||
try {
|
||||
$value = $wpdb->query($sql); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
} catch (Throwable $e) {
|
||||
if ($e instanceof PDOException) {
|
||||
throw new QueryException($e->getMessage(), $e->errorInfo[0] ?? null, $e->errorInfo[1] ?? 0);
|
||||
}
|
||||
throw new QueryException($e->getMessage(), null, 0, $e);
|
||||
}
|
||||
if ($value === false) {
|
||||
$this->handleQueryError();
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function handleQueryError(): void {
|
||||
global $wpdb;
|
||||
$nativeConnection = $this->getNativeConnection();
|
||||
if ($nativeConnection instanceof mysqli) {
|
||||
throw new QueryException($wpdb->last_error, $nativeConnection->sqlstate, $nativeConnection->errno);
|
||||
} elseif ($nativeConnection instanceof PDO) {
|
||||
$info = $nativeConnection->errorInfo();
|
||||
throw new QueryException($wpdb->last_error, $info[0] ?? null, $info[1] ?? 0);
|
||||
}
|
||||
throw new QueryException($wpdb->last_error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php declare (strict_types = 1);
|
||||
|
||||
namespace MailPoet\Doctrine\WPDB;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Doctrine\WPDB\Exceptions\MissingParameterException;
|
||||
use MailPoetVendor\Doctrine\DBAL\ParameterType;
|
||||
use MailPoetVendor\Doctrine\DBAL\SQL\Parser\Visitor;
|
||||
|
||||
class ConvertParameters implements Visitor {
|
||||
private const PARAM_TYPE_MAP = [
|
||||
ParameterType::STRING => '%s',
|
||||
ParameterType::INTEGER => '%d',
|
||||
ParameterType::ASCII => '%s',
|
||||
ParameterType::BINARY => '%s',
|
||||
ParameterType::BOOLEAN => '%d',
|
||||
ParameterType::NULL => '%s',
|
||||
ParameterType::LARGE_OBJECT => '%s',
|
||||
];
|
||||
|
||||
/** @var list<string> */
|
||||
private array $buffer = [];
|
||||
|
||||
/** @var array<array-key, array{0: string, 1: mixed, 2: int}> */
|
||||
private array $params;
|
||||
|
||||
private array $values = [];
|
||||
|
||||
private int $cursor = 1;
|
||||
|
||||
public function __construct(
|
||||
array $params
|
||||
) {
|
||||
$this->params = $params;
|
||||
}
|
||||
|
||||
public function acceptPositionalParameter(string $sql): void {
|
||||
$position = $this->cursor++;
|
||||
$this->acceptParameter($position);
|
||||
}
|
||||
|
||||
public function acceptNamedParameter(string $sql): void {
|
||||
$this->acceptParameter(trim($sql, ':'));
|
||||
}
|
||||
|
||||
public function acceptOther(string $sql): void {
|
||||
$this->buffer[] = $sql;
|
||||
}
|
||||
|
||||
public function getSQL(): string {
|
||||
return implode('', $this->buffer);
|
||||
}
|
||||
|
||||
public function getValues(): array {
|
||||
return $this->values;
|
||||
}
|
||||
|
||||
/** @param array-key $key */
|
||||
private function acceptParameter($key): void {
|
||||
if (!array_key_exists($key, $this->params)) {
|
||||
throw new MissingParameterException(sprintf("Parameter '%s' was defined in the query, but not provided.", $key));
|
||||
}
|
||||
[, $value, $type] = $this->params[$key];
|
||||
|
||||
// WPDB doesn't support NULL values. We need to handle them explicitly.
|
||||
if ($value === null) {
|
||||
$this->buffer[] = 'NULL';
|
||||
return;
|
||||
}
|
||||
|
||||
// WPDB doesn't accept non-scalar values. We need to cast them (PDO-like behavior).
|
||||
if (!is_scalar($value)) {
|
||||
if ($type === ParameterType::INTEGER) {
|
||||
$value = (int)$value; // @phpstan-ignore-line -- cast may fail and that's OK
|
||||
} elseif ($type === ParameterType::BOOLEAN) {
|
||||
$value = (bool)$value;
|
||||
} else {
|
||||
$value = (string)$value; // @phpstan-ignore-line -- cast may fail and that's OK
|
||||
}
|
||||
}
|
||||
|
||||
$this->values[] = $value;
|
||||
$this->buffer[] = self::PARAM_TYPE_MAP[$type] ?? '%s';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Doctrine\WPDB;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoetVendor\Doctrine\DBAL\Driver\AbstractMySQLDriver;
|
||||
|
||||
class Driver extends AbstractMySQLDriver {
|
||||
public function connect(array $params): Connection {
|
||||
return new Connection();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Doctrine\WPDB\Exceptions;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoetVendor\Doctrine\DBAL\Driver\AbstractException;
|
||||
|
||||
class ConnectionException extends AbstractException {
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Doctrine\WPDB\Exceptions;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use Exception;
|
||||
|
||||
class MissingParameterException extends Exception {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Doctrine\WPDB\Exceptions;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use Exception;
|
||||
|
||||
class NotSupportedException extends Exception {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Doctrine\WPDB\Exceptions;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoetVendor\Doctrine\DBAL\Driver\AbstractException;
|
||||
|
||||
class QueryException extends AbstractException {
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Doctrine\WPDB;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoetVendor\Doctrine\DBAL\Driver\Result as ResultInterface;
|
||||
|
||||
/**
|
||||
* WPDB fetches all results from the underlying database driver,
|
||||
* so we need to implement the result methods on in-memory data.
|
||||
*/
|
||||
class Result implements ResultInterface {
|
||||
/** @var array[] */
|
||||
private array $result = [];
|
||||
private int $rowCount;
|
||||
private int $cursor = 0;
|
||||
|
||||
public function __construct(
|
||||
array $result,
|
||||
int $rowCount
|
||||
) {
|
||||
foreach ($result as $row) {
|
||||
$this->result[] = (array)$row;
|
||||
}
|
||||
$this->rowCount = $rowCount;
|
||||
}
|
||||
|
||||
public function fetchNumeric() {
|
||||
$row = $this->result[$this->cursor++] ?? null;
|
||||
return $row === null ? false : array_values($row);
|
||||
}
|
||||
|
||||
public function fetchAssociative() {
|
||||
return $this->result[$this->cursor++] ?? false;
|
||||
}
|
||||
|
||||
public function fetchOne() {
|
||||
$row = $this->result[$this->cursor++] ?? null;
|
||||
return $row === null ? false : reset($row);
|
||||
}
|
||||
|
||||
public function fetchAllNumeric(): array {
|
||||
$result = [];
|
||||
foreach ($this->result as $row) {
|
||||
$result[] = array_values($row);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function fetchAllAssociative(): array {
|
||||
return $this->result;
|
||||
}
|
||||
|
||||
public function fetchFirstColumn(): array {
|
||||
$result = [];
|
||||
foreach ($this->result as $row) {
|
||||
$result[] = reset($row);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function rowCount(): int {
|
||||
return $this->rowCount;
|
||||
}
|
||||
|
||||
public function columnCount(): int {
|
||||
return count($this->result[0] ?? []);
|
||||
}
|
||||
|
||||
public function free(): void {
|
||||
$this->cursor = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Doctrine\WPDB;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Doctrine\WPDB\Exceptions\NotSupportedException;
|
||||
use MailPoetVendor\Doctrine\DBAL\Driver\Result;
|
||||
use MailPoetVendor\Doctrine\DBAL\Driver\Statement as StatementInterface;
|
||||
use MailPoetVendor\Doctrine\DBAL\ParameterType;
|
||||
use MailPoetVendor\Doctrine\DBAL\SQL\Parser;
|
||||
|
||||
class Statement implements StatementInterface {
|
||||
private Connection $connection;
|
||||
private Parser $parser;
|
||||
private string $sql;
|
||||
private array $params = [];
|
||||
|
||||
public function __construct(
|
||||
Connection $connection,
|
||||
string $sql
|
||||
) {
|
||||
$this->connection = $connection;
|
||||
$this->parser = new Parser(false);
|
||||
$this->sql = $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int $param
|
||||
* @param mixed $value
|
||||
* @param int $type
|
||||
* @return true
|
||||
*/
|
||||
public function bindValue($param, $value, $type = ParameterType::STRING) {
|
||||
$this->params[$param] = [$param, $value, $type];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int $param
|
||||
* @param mixed $variable
|
||||
* @param int $type
|
||||
* @param int|null $length
|
||||
* @return true
|
||||
*/
|
||||
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) {
|
||||
throw new NotSupportedException(
|
||||
'Statement::bindParam() is deprecated in Doctrine and not implemented in WPDB driver. Use Statement::bindValue() instead.'
|
||||
);
|
||||
}
|
||||
|
||||
public function execute($params = null): Result {
|
||||
if ($params !== null) {
|
||||
throw new NotSupportedException(
|
||||
'Statement::execute() with parameters is deprecated in Doctrine and not implemented in WPDB driver. Use Statement::bindValue() instead.'
|
||||
);
|
||||
}
|
||||
|
||||
// Convert '?' parameters to WPDB format (sprintf-like: '%s', '%d', ...),
|
||||
// and add support for named parameters that are not supported by mysqli.
|
||||
$visitor = new ConvertParameters($this->params);
|
||||
$this->parser->parse($this->sql, $visitor);
|
||||
$sql = $visitor->getSQL();
|
||||
$values = $visitor->getValues();
|
||||
|
||||
global $wpdb;
|
||||
$query = count($values) > 0
|
||||
? $wpdb->prepare($sql, $values) // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
: $sql;
|
||||
return $this->connection->query($query);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
Reference in New Issue
Block a user