Update website

This commit is contained in:
Guilhem Lavaux 2024-11-19 08:02:04 +01:00
parent 4413528994
commit 1d90fbf296
6865 changed files with 1091082 additions and 0 deletions

View file

@ -0,0 +1,141 @@
<?php
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionError;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use mysqli;
use mysqli_sql_exception;
final class Connection implements ServerInfoAwareConnection
{
/**
* Name of the option to set connection flags
*/
public const OPTION_FLAGS = 'flags';
private mysqli $connection;
/** @internal The connection can be only instantiated by its driver. */
public function __construct(mysqli $connection)
{
$this->connection = $connection;
}
/**
* Retrieves mysqli native resource handle.
*
* Could be used if part of your application is not using DBAL.
*
* @deprecated Call {@see getNativeConnection()} instead.
*/
public function getWrappedResourceHandle(): mysqli
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5037',
'%s is deprecated, call getNativeConnection() instead.',
__METHOD__,
);
return $this->getNativeConnection();
}
public function getServerVersion(): string
{
return $this->connection->get_server_info();
}
public function prepare(string $sql): DriverStatement
{
try {
$stmt = $this->connection->prepare($sql);
} catch (mysqli_sql_exception $e) {
throw ConnectionError::upcast($e);
}
if ($stmt === false) {
throw ConnectionError::new($this->connection);
}
return new Statement($stmt);
}
public function query(string $sql): ResultInterface
{
return $this->prepare($sql)->execute();
}
/**
* {@inheritDoc}
*/
public function quote($value, $type = ParameterType::STRING)
{
return "'" . $this->connection->escape_string($value) . "'";
}
public function exec(string $sql): int
{
try {
$result = $this->connection->query($sql);
} catch (mysqli_sql_exception $e) {
throw ConnectionError::upcast($e);
}
if ($result === false) {
throw ConnectionError::new($this->connection);
}
return $this->connection->affected_rows;
}
/**
* {@inheritDoc}
*/
public function lastInsertId($name = null)
{
if ($name !== null) {
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/issues/4687',
'The usage of Connection::lastInsertId() with a sequence name is deprecated.',
);
}
return $this->connection->insert_id;
}
public function beginTransaction(): bool
{
$this->connection->begin_transaction();
return true;
}
public function commit(): bool
{
try {
return $this->connection->commit();
} catch (mysqli_sql_exception $e) {
return false;
}
}
public function rollBack(): bool
{
try {
return $this->connection->rollback();
} catch (mysqli_sql_exception $e) {
return false;
}
}
public function getNativeConnection(): mysqli
{
return $this->connection;
}
}

View file

@ -0,0 +1,117 @@
<?php
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\AbstractMySQLDriver;
use Doctrine\DBAL\Driver\Mysqli\Exception\ConnectionFailed;
use Doctrine\DBAL\Driver\Mysqli\Exception\HostRequired;
use Doctrine\DBAL\Driver\Mysqli\Initializer\Charset;
use Doctrine\DBAL\Driver\Mysqli\Initializer\Options;
use Doctrine\DBAL\Driver\Mysqli\Initializer\Secure;
use Generator;
use mysqli;
use mysqli_sql_exception;
use SensitiveParameter;
final class Driver extends AbstractMySQLDriver
{
/**
* {@inheritDoc}
*
* @return Connection
*/
public function connect(
#[SensitiveParameter]
array $params
) {
if (! empty($params['persistent'])) {
if (! isset($params['host'])) {
throw HostRequired::forPersistentConnection();
}
$host = 'p:' . $params['host'];
} else {
$host = $params['host'] ?? null;
}
$connection = new mysqli();
foreach ($this->compilePreInitializers($params) as $initializer) {
$initializer->initialize($connection);
}
try {
$success = @$connection->real_connect(
$host,
$params['user'] ?? null,
$params['password'] ?? null,
$params['dbname'] ?? null,
$params['port'] ?? null,
$params['unix_socket'] ?? null,
$params['driverOptions'][Connection::OPTION_FLAGS] ?? 0,
);
} catch (mysqli_sql_exception $e) {
throw ConnectionFailed::upcast($e);
}
if (! $success) {
throw ConnectionFailed::new($connection);
}
foreach ($this->compilePostInitializers($params) as $initializer) {
$initializer->initialize($connection);
}
return new Connection($connection);
}
/**
* @param array<string, mixed> $params
*
* @return Generator<int, Initializer>
*/
private function compilePreInitializers(
#[SensitiveParameter]
array $params
): Generator {
unset($params['driverOptions'][Connection::OPTION_FLAGS]);
if (isset($params['driverOptions']) && $params['driverOptions'] !== []) {
yield new Options($params['driverOptions']);
}
if (
! isset($params['ssl_key']) &&
! isset($params['ssl_cert']) &&
! isset($params['ssl_ca']) &&
! isset($params['ssl_capath']) &&
! isset($params['ssl_cipher'])
) {
return;
}
yield new Secure(
$params['ssl_key'] ?? '',
$params['ssl_cert'] ?? '',
$params['ssl_ca'] ?? '',
$params['ssl_capath'] ?? '',
$params['ssl_cipher'] ?? '',
);
}
/**
* @param array<string, mixed> $params
*
* @return Generator<int, Initializer>
*/
private function compilePostInitializers(
#[SensitiveParameter]
array $params
): Generator {
if (! isset($params['charset'])) {
return;
}
yield new Charset($params['charset']);
}
}

View file

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use mysqli;
use mysqli_sql_exception;
use ReflectionProperty;
/**
* @internal
*
* @psalm-immutable
*/
final class ConnectionError extends AbstractException
{
public static function new(mysqli $connection): self
{
return new self($connection->error, $connection->sqlstate, $connection->errno);
}
public static function upcast(mysqli_sql_exception $exception): self
{
$p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate');
$p->setAccessible(true);
return new self($exception->getMessage(), $p->getValue($exception), (int) $exception->getCode(), $exception);
}
}

View file

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use mysqli;
use mysqli_sql_exception;
use ReflectionProperty;
use function assert;
/**
* @internal
*
* @psalm-immutable
*/
final class ConnectionFailed extends AbstractException
{
public static function new(mysqli $connection): self
{
$error = $connection->connect_error;
assert($error !== null);
return new self($error, 'HY000', $connection->connect_errno);
}
public static function upcast(mysqli_sql_exception $exception): self
{
$p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate');
$p->setAccessible(true);
return new self($exception->getMessage(), $p->getValue($exception), (int) $exception->getCode(), $exception);
}
}

View file

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use function sprintf;
/**
* @internal
*
* @psalm-immutable
*/
final class FailedReadingStreamOffset extends AbstractException
{
public static function new(int $parameter): self
{
return new self(sprintf('Failed reading the stream resource for parameter #%d.', $parameter));
}
}

View file

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\AbstractException;
/**
* @internal
*
* @psalm-immutable
*/
final class HostRequired extends AbstractException
{
public static function forPersistentConnection(): self
{
return new self('The "host" parameter is required for a persistent connection');
}
}

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use mysqli;
use mysqli_sql_exception;
use ReflectionProperty;
use function sprintf;
/**
* @internal
*
* @psalm-immutable
*/
final class InvalidCharset extends AbstractException
{
public static function fromCharset(mysqli $connection, string $charset): self
{
return new self(
sprintf('Failed to set charset "%s": %s', $charset, $connection->error),
$connection->sqlstate,
$connection->errno,
);
}
public static function upcast(mysqli_sql_exception $exception, string $charset): self
{
$p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate');
$p->setAccessible(true);
return new self(
sprintf('Failed to set charset "%s": %s', $charset, $exception->getMessage()),
$p->getValue($exception),
(int) $exception->getCode(),
$exception,
);
}
}

View file

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use function sprintf;
/**
* @internal
*
* @psalm-immutable
*/
final class InvalidOption extends AbstractException
{
/** @param mixed $value */
public static function fromOption(int $option, $value): self
{
return new self(
sprintf('Failed to set option %d with value "%s"', $option, $value),
);
}
}

View file

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use function sprintf;
/**
* @internal
*
* @psalm-immutable
*/
final class NonStreamResourceUsedAsLargeObject extends AbstractException
{
public static function new(int $parameter): self
{
return new self(
sprintf('The resource passed as a LARGE_OBJECT parameter #%d must be of type "stream"', $parameter),
);
}
}

View file

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Exception;
use Doctrine\DBAL\Driver\AbstractException;
use mysqli_sql_exception;
use mysqli_stmt;
use ReflectionProperty;
/**
* @internal
*
* @psalm-immutable
*/
final class StatementError extends AbstractException
{
public static function new(mysqli_stmt $statement): self
{
return new self($statement->error, $statement->sqlstate, $statement->errno);
}
public static function upcast(mysqli_sql_exception $exception): self
{
$p = new ReflectionProperty(mysqli_sql_exception::class, 'sqlstate');
$p->setAccessible(true);
return new self($exception->getMessage(), $p->getValue($exception), (int) $exception->getCode(), $exception);
}
}

View file

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\Exception;
use mysqli;
interface Initializer
{
/** @throws Exception */
public function initialize(mysqli $connection): void;
}

View file

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Initializer;
use Doctrine\DBAL\Driver\Mysqli\Exception\InvalidCharset;
use Doctrine\DBAL\Driver\Mysqli\Initializer;
use mysqli;
use mysqli_sql_exception;
final class Charset implements Initializer
{
private string $charset;
public function __construct(string $charset)
{
$this->charset = $charset;
}
public function initialize(mysqli $connection): void
{
try {
$success = $connection->set_charset($this->charset);
} catch (mysqli_sql_exception $e) {
throw InvalidCharset::upcast($e, $this->charset);
}
if ($success) {
return;
}
throw InvalidCharset::fromCharset($connection, $this->charset);
}
}

View file

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Initializer;
use Doctrine\DBAL\Driver\Mysqli\Exception\InvalidOption;
use Doctrine\DBAL\Driver\Mysqli\Initializer;
use mysqli;
use function mysqli_options;
final class Options implements Initializer
{
/** @var array<int,mixed> */
private array $options;
/** @param array<int,mixed> $options */
public function __construct(array $options)
{
$this->options = $options;
}
public function initialize(mysqli $connection): void
{
foreach ($this->options as $option => $value) {
if (! mysqli_options($connection, $option, $value)) {
throw InvalidOption::fromOption($option, $value);
}
}
}
}

View file

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli\Initializer;
use Doctrine\DBAL\Driver\Mysqli\Initializer;
use mysqli;
use SensitiveParameter;
final class Secure implements Initializer
{
private string $key;
private string $cert;
private string $ca;
private string $capath;
private string $cipher;
public function __construct(
#[SensitiveParameter]
string $key,
string $cert,
string $ca,
string $capath,
string $cipher
) {
$this->key = $key;
$this->cert = $cert;
$this->ca = $ca;
$this->capath = $capath;
$this->cipher = $cipher;
}
public function initialize(mysqli $connection): void
{
$connection->ssl_set($this->key, $this->cert, $this->ca, $this->capath, $this->cipher);
}
}

View file

@ -0,0 +1,179 @@
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Mysqli\Exception\StatementError;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use mysqli_sql_exception;
use mysqli_stmt;
use function array_column;
use function array_combine;
use function array_fill;
use function count;
final class Result implements ResultInterface
{
private mysqli_stmt $statement;
/**
* Whether the statement result has columns. The property should be used only after the result metadata
* has been fetched ({@see $metadataFetched}). Otherwise, the property value is undetermined.
*/
private bool $hasColumns = false;
/**
* Mapping of statement result column indexes to their names. The property should be used only
* if the statement result has columns ({@see $hasColumns}). Otherwise, the property value is undetermined.
*
* @var array<int,string>
*/
private array $columnNames = [];
/** @var mixed[] */
private array $boundValues = [];
/**
* @internal The result can be only instantiated by its driver connection or statement.
*
* @throws Exception
*/
public function __construct(mysqli_stmt $statement)
{
$this->statement = $statement;
$meta = $statement->result_metadata();
if ($meta === false) {
return;
}
$this->hasColumns = true;
$this->columnNames = array_column($meta->fetch_fields(), 'name');
$meta->free();
// Store result of every execution which has it. Otherwise it will be impossible
// to execute a new statement in case if the previous one has non-fetched rows
// @link http://dev.mysql.com/doc/refman/5.7/en/commands-out-of-sync.html
$this->statement->store_result();
// Bind row values _after_ storing the result. Otherwise, if mysqli is compiled with libmysql,
// it will have to allocate as much memory as it may be needed for the given column type
// (e.g. for a LONGBLOB column it's 4 gigabytes)
// @link https://bugs.php.net/bug.php?id=51386#1270673122
//
// Make sure that the values are bound after each execution. Otherwise, if free() has been
// previously called on the result, the values are unbound making the statement unusable.
//
// It's also important that row values are bound after _each_ call to store_result(). Otherwise,
// if mysqli is compiled with libmysql, subsequently fetched string values will get truncated
// to the length of the ones fetched during the previous execution.
$this->boundValues = array_fill(0, count($this->columnNames), null);
// The following is necessary as PHP cannot handle references to properties properly
$refs = &$this->boundValues;
if (! $this->statement->bind_result(...$refs)) {
throw StatementError::new($this->statement);
}
}
/**
* {@inheritDoc}
*/
public function fetchNumeric()
{
try {
$ret = $this->statement->fetch();
} catch (mysqli_sql_exception $e) {
throw StatementError::upcast($e);
}
if ($ret === false) {
throw StatementError::new($this->statement);
}
if ($ret === null) {
return false;
}
$values = [];
foreach ($this->boundValues as $v) {
$values[] = $v;
}
return $values;
}
/**
* {@inheritDoc}
*/
public function fetchAssociative()
{
$values = $this->fetchNumeric();
if ($values === false) {
return false;
}
return array_combine($this->columnNames, $values);
}
/**
* {@inheritDoc}
*/
public function fetchOne()
{
return FetchUtils::fetchOne($this);
}
/**
* {@inheritDoc}
*/
public function fetchAllNumeric(): array
{
return FetchUtils::fetchAllNumeric($this);
}
/**
* {@inheritDoc}
*/
public function fetchAllAssociative(): array
{
return FetchUtils::fetchAllAssociative($this);
}
/**
* {@inheritDoc}
*/
public function fetchFirstColumn(): array
{
return FetchUtils::fetchFirstColumn($this);
}
public function rowCount(): int
{
if ($this->hasColumns) {
return $this->statement->num_rows;
}
return $this->statement->affected_rows;
}
public function columnCount(): int
{
return $this->statement->field_count;
}
public function free(): void
{
$this->statement->free_result();
}
}

View file

@ -0,0 +1,239 @@
<?php
namespace Doctrine\DBAL\Driver\Mysqli;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\Exception\UnknownParameterType;
use Doctrine\DBAL\Driver\Mysqli\Exception\FailedReadingStreamOffset;
use Doctrine\DBAL\Driver\Mysqli\Exception\NonStreamResourceUsedAsLargeObject;
use Doctrine\DBAL\Driver\Mysqli\Exception\StatementError;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\Statement as StatementInterface;
use Doctrine\DBAL\ParameterType;
use Doctrine\Deprecations\Deprecation;
use mysqli_sql_exception;
use mysqli_stmt;
use function array_fill;
use function assert;
use function count;
use function feof;
use function fread;
use function func_num_args;
use function get_resource_type;
use function is_int;
use function is_resource;
use function str_repeat;
final class Statement implements StatementInterface
{
private const PARAM_TYPE_MAP = [
ParameterType::ASCII => 's',
ParameterType::STRING => 's',
ParameterType::BINARY => 's',
ParameterType::BOOLEAN => 'i',
ParameterType::NULL => 's',
ParameterType::INTEGER => 'i',
ParameterType::LARGE_OBJECT => 'b',
];
private mysqli_stmt $stmt;
/** @var mixed[] */
private array $boundValues;
private string $types;
/**
* Contains ref values for bindValue().
*
* @var mixed[]
*/
private array $values = [];
/** @internal The statement can be only instantiated by its driver connection. */
public function __construct(mysqli_stmt $stmt)
{
$this->stmt = $stmt;
$paramCount = $this->stmt->param_count;
$this->types = str_repeat('s', $paramCount);
$this->boundValues = array_fill(1, $paramCount, null);
}
/**
* @deprecated Use {@see bindValue()} instead.
*
* {@inheritDoc}
*
* @psalm-assert ParameterType::* $type
*/
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool
{
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5563',
'%s is deprecated. Use bindValue() instead.',
__METHOD__,
);
assert(is_int($param));
if (func_num_args() < 3) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5558',
'Not passing $type to Statement::bindParam() is deprecated.'
. ' Pass the type corresponding to the parameter being bound.',
);
}
if (! isset(self::PARAM_TYPE_MAP[$type])) {
throw UnknownParameterType::new($type);
}
$this->boundValues[$param] =& $variable;
$this->types[$param - 1] = self::PARAM_TYPE_MAP[$type];
return true;
}
/**
* {@inheritDoc}
*
* @psalm-assert ParameterType::* $type
*/
public function bindValue($param, $value, $type = ParameterType::STRING): bool
{
assert(is_int($param));
if (func_num_args() < 3) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5558',
'Not passing $type to Statement::bindValue() is deprecated.'
. ' Pass the type corresponding to the parameter being bound.',
);
}
if (! isset(self::PARAM_TYPE_MAP[$type])) {
throw UnknownParameterType::new($type);
}
$this->values[$param] = $value;
$this->boundValues[$param] =& $this->values[$param];
$this->types[$param - 1] = self::PARAM_TYPE_MAP[$type];
return true;
}
/**
* {@inheritDoc}
*/
public function execute($params = null): ResultInterface
{
if ($params !== null) {
Deprecation::trigger(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/5556',
'Passing $params to Statement::execute() is deprecated. Bind parameters using'
. ' Statement::bindParam() or Statement::bindValue() instead.',
);
}
if ($params !== null && count($params) > 0) {
if (! $this->bindUntypedValues($params)) {
throw StatementError::new($this->stmt);
}
} elseif (count($this->boundValues) > 0) {
$this->bindTypedParameters();
}
try {
$result = $this->stmt->execute();
} catch (mysqli_sql_exception $e) {
throw StatementError::upcast($e);
}
if (! $result) {
throw StatementError::new($this->stmt);
}
return new Result($this->stmt);
}
/**
* Binds parameters with known types previously bound to the statement
*
* @throws Exception
*/
private function bindTypedParameters(): void
{
$streams = $values = [];
$types = $this->types;
foreach ($this->boundValues as $parameter => $value) {
assert(is_int($parameter));
if (! isset($types[$parameter - 1])) {
$types[$parameter - 1] = self::PARAM_TYPE_MAP[ParameterType::STRING];
}
if ($types[$parameter - 1] === self::PARAM_TYPE_MAP[ParameterType::LARGE_OBJECT]) {
if (is_resource($value)) {
if (get_resource_type($value) !== 'stream') {
throw NonStreamResourceUsedAsLargeObject::new($parameter);
}
$streams[$parameter] = $value;
$values[$parameter] = null;
continue;
}
$types[$parameter - 1] = self::PARAM_TYPE_MAP[ParameterType::STRING];
}
$values[$parameter] = $value;
}
if (! $this->stmt->bind_param($types, ...$values)) {
throw StatementError::new($this->stmt);
}
$this->sendLongData($streams);
}
/**
* Handle $this->_longData after regular query parameters have been bound
*
* @param array<int, resource> $streams
*
* @throws Exception
*/
private function sendLongData(array $streams): void
{
foreach ($streams as $paramNr => $stream) {
while (! feof($stream)) {
$chunk = fread($stream, 8192);
if ($chunk === false) {
throw FailedReadingStreamOffset::new($paramNr);
}
if (! $this->stmt->send_long_data($paramNr - 1, $chunk)) {
throw StatementError::new($this->stmt);
}
}
}
}
/**
* Binds a array of values to bound parameters.
*
* @param mixed[] $values
*/
private function bindUntypedValues(array $values): bool
{
return $this->stmt->bind_param(str_repeat('s', count($values)), ...$values);
}
}