welcome back to dyb-tech
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Portability;
|
||||
|
||||
use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
|
||||
use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware;
|
||||
use Doctrine\DBAL\Driver\Result as DriverResult;
|
||||
use Doctrine\DBAL\Driver\Statement as DriverStatement;
|
||||
|
||||
/**
|
||||
* Portability wrapper for a Connection.
|
||||
*/
|
||||
final class Connection extends AbstractConnectionMiddleware
|
||||
{
|
||||
public const PORTABILITY_ALL = 255;
|
||||
public const PORTABILITY_NONE = 0;
|
||||
public const PORTABILITY_RTRIM = 1;
|
||||
public const PORTABILITY_EMPTY_TO_NULL = 4;
|
||||
public const PORTABILITY_FIX_CASE = 8;
|
||||
|
||||
private Converter $converter;
|
||||
|
||||
public function __construct(ConnectionInterface $connection, Converter $converter)
|
||||
{
|
||||
parent::__construct($connection);
|
||||
|
||||
$this->converter = $converter;
|
||||
}
|
||||
|
||||
public function prepare(string $sql): DriverStatement
|
||||
{
|
||||
return new Statement(
|
||||
parent::prepare($sql),
|
||||
$this->converter,
|
||||
);
|
||||
}
|
||||
|
||||
public function query(string $sql): DriverResult
|
||||
{
|
||||
return new Result(
|
||||
parent::query($sql),
|
||||
$this->converter,
|
||||
);
|
||||
}
|
||||
}
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Portability;
|
||||
|
||||
use function array_change_key_case;
|
||||
use function array_map;
|
||||
use function array_reduce;
|
||||
use function is_string;
|
||||
use function rtrim;
|
||||
|
||||
use const CASE_LOWER;
|
||||
use const CASE_UPPER;
|
||||
|
||||
final class Converter
|
||||
{
|
||||
public const CASE_LOWER = CASE_LOWER;
|
||||
public const CASE_UPPER = CASE_UPPER;
|
||||
|
||||
/** @var callable */
|
||||
private $convertNumeric;
|
||||
|
||||
/** @var callable */
|
||||
private $convertAssociative;
|
||||
|
||||
/** @var callable */
|
||||
private $convertOne;
|
||||
|
||||
/** @var callable */
|
||||
private $convertAllNumeric;
|
||||
|
||||
/** @var callable */
|
||||
private $convertAllAssociative;
|
||||
|
||||
/** @var callable */
|
||||
private $convertFirstColumn;
|
||||
|
||||
/**
|
||||
* @param bool $convertEmptyStringToNull Whether each empty string should
|
||||
* be converted to NULL
|
||||
* @param bool $rightTrimString Whether each string should right-trimmed
|
||||
* @param self::CASE_LOWER|self::CASE_UPPER|null $case Convert the case of the column names
|
||||
* (one of {@see self::CASE_LOWER} and
|
||||
* {@see self::CASE_UPPER})
|
||||
*/
|
||||
public function __construct(bool $convertEmptyStringToNull, bool $rightTrimString, ?int $case)
|
||||
{
|
||||
$convertValue = $this->createConvertValue($convertEmptyStringToNull, $rightTrimString);
|
||||
$convertNumeric = $this->createConvertRow($convertValue, null);
|
||||
$convertAssociative = $this->createConvertRow($convertValue, $case);
|
||||
|
||||
$this->convertNumeric = $this->createConvert($convertNumeric, [self::class, 'id']);
|
||||
$this->convertAssociative = $this->createConvert($convertAssociative, [self::class, 'id']);
|
||||
$this->convertOne = $this->createConvert($convertValue, [self::class, 'id']);
|
||||
|
||||
$this->convertAllNumeric = $this->createConvertAll($convertNumeric, [self::class, 'id']);
|
||||
$this->convertAllAssociative = $this->createConvertAll($convertAssociative, [self::class, 'id']);
|
||||
$this->convertFirstColumn = $this->createConvertAll($convertValue, [self::class, 'id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int,mixed>|false $row
|
||||
*
|
||||
* @return list<mixed>|false
|
||||
*/
|
||||
public function convertNumeric($row)
|
||||
{
|
||||
return ($this->convertNumeric)($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed>|false $row
|
||||
*
|
||||
* @return array<string,mixed>|false
|
||||
*/
|
||||
public function convertAssociative($row)
|
||||
{
|
||||
return ($this->convertAssociative)($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed|false $value
|
||||
*
|
||||
* @return mixed|false
|
||||
*/
|
||||
public function convertOne($value)
|
||||
{
|
||||
return ($this->convertOne)($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<list<mixed>> $data
|
||||
*
|
||||
* @return list<list<mixed>>
|
||||
*/
|
||||
public function convertAllNumeric(array $data): array
|
||||
{
|
||||
return ($this->convertAllNumeric)($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array<string,mixed>> $data
|
||||
*
|
||||
* @return list<array<string,mixed>>
|
||||
*/
|
||||
public function convertAllAssociative(array $data): array
|
||||
{
|
||||
return ($this->convertAllAssociative)($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<mixed> $data
|
||||
*
|
||||
* @return list<mixed>
|
||||
*/
|
||||
public function convertFirstColumn(array $data): array
|
||||
{
|
||||
return ($this->convertFirstColumn)($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param T $value
|
||||
*
|
||||
* @return T
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
private static function id($value)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param T $value
|
||||
*
|
||||
* @return T|null
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
private static function convertEmptyStringToNull($value)
|
||||
{
|
||||
if ($value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param T $value
|
||||
*
|
||||
* @return T|string
|
||||
* @psalm-return (T is string ? string : T)
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
private static function rightTrimString($value)
|
||||
{
|
||||
if (! is_string($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return rtrim($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that will convert each individual value retrieved from the database
|
||||
*
|
||||
* @param bool $convertEmptyStringToNull Whether each empty string should be converted to NULL
|
||||
* @param bool $rightTrimString Whether each string should right-trimmed
|
||||
*
|
||||
* @return callable|null The resulting function or NULL if no conversion is needed
|
||||
*/
|
||||
private function createConvertValue(bool $convertEmptyStringToNull, bool $rightTrimString): ?callable
|
||||
{
|
||||
$functions = [];
|
||||
|
||||
if ($convertEmptyStringToNull) {
|
||||
$functions[] = [self::class, 'convertEmptyStringToNull'];
|
||||
}
|
||||
|
||||
if ($rightTrimString) {
|
||||
$functions[] = [self::class, 'rightTrimString'];
|
||||
}
|
||||
|
||||
return $this->compose(...$functions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that will convert each array-row retrieved from the database
|
||||
*
|
||||
* @param callable|null $function The function that will convert each value
|
||||
* @param self::CASE_LOWER|self::CASE_UPPER|null $case Column name case
|
||||
*
|
||||
* @return callable|null The resulting function or NULL if no conversion is needed
|
||||
*/
|
||||
private function createConvertRow(?callable $function, ?int $case): ?callable
|
||||
{
|
||||
$functions = [];
|
||||
|
||||
if ($function !== null) {
|
||||
$functions[] = $this->createMapper($function);
|
||||
}
|
||||
|
||||
if ($case !== null) {
|
||||
$functions[] = static function (array $row) use ($case): array {
|
||||
return array_change_key_case($row, $case);
|
||||
};
|
||||
}
|
||||
|
||||
return $this->compose(...$functions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that will be applied to the return value of Statement::fetch*()
|
||||
* or an identity function if no conversion is needed
|
||||
*
|
||||
* @param callable|null $function The function that will convert each tow
|
||||
* @param callable $id Identity function
|
||||
*/
|
||||
private function createConvert(?callable $function, callable $id): callable
|
||||
{
|
||||
if ($function === null) {
|
||||
return $id;
|
||||
}
|
||||
|
||||
return /**
|
||||
* @param T $value
|
||||
*
|
||||
* @psalm-return (T is false ? false : T)
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
static function ($value) use ($function) {
|
||||
if ($value === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $function($value);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that will be applied to the return value of Statement::fetchAll*()
|
||||
* or an identity function if no transformation is required
|
||||
*
|
||||
* @param callable|null $function The function that will transform each value
|
||||
* @param callable $id Identity function
|
||||
*/
|
||||
private function createConvertAll(?callable $function, callable $id): callable
|
||||
{
|
||||
if ($function === null) {
|
||||
return $id;
|
||||
}
|
||||
|
||||
return $this->createMapper($function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that maps each value of the array using the given function
|
||||
*
|
||||
* @param callable $function The function that maps each value of the array
|
||||
*/
|
||||
private function createMapper(callable $function): callable
|
||||
{
|
||||
return static function (array $array) use ($function): array {
|
||||
return array_map($function, $array);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a composition of the given set of functions
|
||||
*
|
||||
* @param callable(T):T ...$functions The functions to compose
|
||||
*
|
||||
* @return callable(T):T|null
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
private function compose(callable ...$functions): ?callable
|
||||
{
|
||||
return array_reduce($functions, static function (?callable $carry, callable $item): callable {
|
||||
if ($carry === null) {
|
||||
return $item;
|
||||
}
|
||||
|
||||
return /**
|
||||
* @param T $value
|
||||
*
|
||||
* @return T
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
static function ($value) use ($carry, $item) {
|
||||
return $item($carry($value));
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Portability;
|
||||
|
||||
use Doctrine\DBAL\ColumnCase;
|
||||
use Doctrine\DBAL\Driver as DriverInterface;
|
||||
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
|
||||
use LogicException;
|
||||
use PDO;
|
||||
use SensitiveParameter;
|
||||
|
||||
use function method_exists;
|
||||
|
||||
final class Driver extends AbstractDriverMiddleware
|
||||
{
|
||||
private int $mode;
|
||||
|
||||
/** @var 0|ColumnCase::LOWER|ColumnCase::UPPER */
|
||||
private int $case;
|
||||
|
||||
/**
|
||||
* @param 0|ColumnCase::LOWER|ColumnCase::UPPER $case Determines how the column case will be treated.
|
||||
* 0: The case will be left as is in the database.
|
||||
* {@see ColumnCase::LOWER}: The case will be lowercased.
|
||||
* {@see ColumnCase::UPPER}: The case will be uppercased.
|
||||
*/
|
||||
public function __construct(DriverInterface $driver, int $mode, int $case)
|
||||
{
|
||||
parent::__construct($driver);
|
||||
|
||||
$this->mode = $mode;
|
||||
$this->case = $case;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function connect(
|
||||
#[SensitiveParameter]
|
||||
array $params
|
||||
) {
|
||||
$connection = parent::connect($params);
|
||||
|
||||
$portability = (new OptimizeFlags())(
|
||||
$this->getDatabasePlatform(),
|
||||
$this->mode,
|
||||
);
|
||||
|
||||
$case = null;
|
||||
|
||||
if ($this->case !== 0 && ($portability & Connection::PORTABILITY_FIX_CASE) !== 0) {
|
||||
$nativeConnection = null;
|
||||
if (method_exists($connection, 'getNativeConnection')) {
|
||||
try {
|
||||
$nativeConnection = $connection->getNativeConnection();
|
||||
} catch (LogicException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
if ($nativeConnection instanceof PDO) {
|
||||
$portability &= ~Connection::PORTABILITY_FIX_CASE;
|
||||
$nativeConnection->setAttribute(
|
||||
PDO::ATTR_CASE,
|
||||
$this->case === ColumnCase::LOWER ? PDO::CASE_LOWER : PDO::CASE_UPPER,
|
||||
);
|
||||
} else {
|
||||
$case = $this->case === ColumnCase::LOWER ? Converter::CASE_LOWER : Converter::CASE_UPPER;
|
||||
}
|
||||
}
|
||||
|
||||
$convertEmptyStringToNull = ($portability & Connection::PORTABILITY_EMPTY_TO_NULL) !== 0;
|
||||
$rightTrimString = ($portability & Connection::PORTABILITY_RTRIM) !== 0;
|
||||
|
||||
if (! $convertEmptyStringToNull && ! $rightTrimString && $case === null) {
|
||||
return $connection;
|
||||
}
|
||||
|
||||
return new Connection(
|
||||
$connection,
|
||||
new Converter($convertEmptyStringToNull, $rightTrimString, $case),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Portability;
|
||||
|
||||
use Doctrine\DBAL\ColumnCase;
|
||||
use Doctrine\DBAL\Driver as DriverInterface;
|
||||
use Doctrine\DBAL\Driver\Middleware as MiddlewareInterface;
|
||||
|
||||
final class Middleware implements MiddlewareInterface
|
||||
{
|
||||
private int $mode;
|
||||
|
||||
/** @var 0|ColumnCase::LOWER|ColumnCase::UPPER */
|
||||
private int $case;
|
||||
|
||||
/**
|
||||
* @param 0|ColumnCase::LOWER|ColumnCase::UPPER $case Determines how the column case will be treated.
|
||||
* 0: The case will be left as is in the database.
|
||||
* {@see ColumnCase::LOWER}: The case will be lowercased.
|
||||
* {@see ColumnCase::UPPER}: The case will be uppercased.
|
||||
*/
|
||||
public function __construct(int $mode, int $case)
|
||||
{
|
||||
$this->mode = $mode;
|
||||
$this->case = $case;
|
||||
}
|
||||
|
||||
public function wrap(DriverInterface $driver): DriverInterface
|
||||
{
|
||||
if ($this->mode !== 0) {
|
||||
return new Driver($driver, $this->mode, $this->case);
|
||||
}
|
||||
|
||||
return $driver;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Portability;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Platforms\DB2Platform;
|
||||
use Doctrine\DBAL\Platforms\OraclePlatform;
|
||||
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\SqlitePlatform;
|
||||
use Doctrine\DBAL\Platforms\SQLServerPlatform;
|
||||
|
||||
final class OptimizeFlags
|
||||
{
|
||||
/**
|
||||
* Platform-specific portability flags that need to be excluded from the user-provided mode
|
||||
* since the platform already operates in this mode to avoid unnecessary conversion overhead.
|
||||
*
|
||||
* @var array<class-string, int>
|
||||
*/
|
||||
private static array $platforms = [
|
||||
DB2Platform::class => 0,
|
||||
OraclePlatform::class => Connection::PORTABILITY_EMPTY_TO_NULL,
|
||||
PostgreSQLPlatform::class => 0,
|
||||
SqlitePlatform::class => 0,
|
||||
SQLServerPlatform::class => 0,
|
||||
];
|
||||
|
||||
public function __invoke(AbstractPlatform $platform, int $flags): int
|
||||
{
|
||||
foreach (self::$platforms as $class => $mask) {
|
||||
if ($platform instanceof $class) {
|
||||
$flags &= ~$mask;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $flags;
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Portability;
|
||||
|
||||
use Doctrine\DBAL\Driver\Middleware\AbstractResultMiddleware;
|
||||
use Doctrine\DBAL\Driver\Result as ResultInterface;
|
||||
|
||||
final class Result extends AbstractResultMiddleware
|
||||
{
|
||||
private Converter $converter;
|
||||
|
||||
/** @internal The result can be only instantiated by the portability connection or statement. */
|
||||
public function __construct(ResultInterface $result, Converter $converter)
|
||||
{
|
||||
parent::__construct($result);
|
||||
|
||||
$this->converter = $converter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fetchNumeric()
|
||||
{
|
||||
return $this->converter->convertNumeric(
|
||||
parent::fetchNumeric(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fetchAssociative()
|
||||
{
|
||||
return $this->converter->convertAssociative(
|
||||
parent::fetchAssociative(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fetchOne()
|
||||
{
|
||||
return $this->converter->convertOne(
|
||||
parent::fetchOne(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fetchAllNumeric(): array
|
||||
{
|
||||
return $this->converter->convertAllNumeric(
|
||||
parent::fetchAllNumeric(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fetchAllAssociative(): array
|
||||
{
|
||||
return $this->converter->convertAllAssociative(
|
||||
parent::fetchAllAssociative(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fetchFirstColumn(): array
|
||||
{
|
||||
return $this->converter->convertFirstColumn(
|
||||
parent::fetchFirstColumn(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Portability;
|
||||
|
||||
use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware;
|
||||
use Doctrine\DBAL\Driver\Result as ResultInterface;
|
||||
use Doctrine\DBAL\Driver\Statement as DriverStatement;
|
||||
|
||||
/**
|
||||
* Portability wrapper for a Statement.
|
||||
*/
|
||||
final class Statement extends AbstractStatementMiddleware
|
||||
{
|
||||
private Converter $converter;
|
||||
|
||||
/**
|
||||
* Wraps <tt>Statement</tt> and applies portability measures.
|
||||
*/
|
||||
public function __construct(DriverStatement $stmt, Converter $converter)
|
||||
{
|
||||
parent::__construct($stmt);
|
||||
|
||||
$this->converter = $converter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function execute($params = null): ResultInterface
|
||||
{
|
||||
return new Result(
|
||||
parent::execute($params),
|
||||
$this->converter,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user