Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework the portability layer to act as a middleware #4157

Merged
merged 2 commits into from
Jul 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,11 @@ The method no longer accepts the `$username`, `$password` and `$driverOptions` a

This class was deprecated in favor of `PrimaryReadReplicaConnection`

## Removed `Portability\Connection::PORTABILITY_{PLATFORM}` constants`
## BC BREAK: Changes in the portability layer

The platform-specific portability constants were internal implementation details which are longer relevant.
1. The platform-specific portability constants (`Portability\Connection::PORTABILITY_{PLATFORM}`) were internal implementation details which are no longer relevant.
2. The `Portability\Connection` class no longer extends the DBAL `Connection`.
3. The `Portability\Class` class has been made final.

## BC BREAK changes in fetching statement results

Expand Down
5 changes: 0 additions & 5 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,5 @@ parameters:
paths:
- %currentWorkingDirectory%/src/Id/TableGenerator.php
- %currentWorkingDirectory%/src/Schema/SqliteSchemaManager.php

-
message: '~Return type \(Doctrine\\DBAL\\Portability\\Statement\) of method Doctrine\\DBAL\\Portability\\Connection::prepare\(\) should be compatible with return type \(Doctrine\\DBAL\\Statement\) of method Doctrine\\DBAL\\Connection::prepare\(\)~'
paths:
- %currentWorkingDirectory%/src/Portability/Connection.php
includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon
27 changes: 25 additions & 2 deletions src/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@
namespace Doctrine\DBAL;

use Doctrine\Common\Cache\Cache;
use Doctrine\DBAL\Driver\Middleware;
use Doctrine\DBAL\Logging\SQLLogger;

/**
* Configuration container for the Doctrine DBAL.
*
* @internal When adding a new configuration option just write a getter/setter
* pair and add the option to the _attributes array with a proper default value.
* @internal
*/
class Configuration
{
/** @var Middleware[] */
private $middlewares = [];

/**
* The attributes that are contained in the configuration.
* Values are default values.
Expand Down Expand Up @@ -108,4 +111,24 @@ public function getAutoCommit()
{
return $this->_attributes['autoCommit'] ?? true;
}

/**
* @param Middleware[] $middlewares
*
* @return $this
*/
public function setMiddlewares(array $middlewares): self
{
$this->middlewares = $middlewares;

return $this;
}

/**
* @return Middleware[]
*/
public function getMiddlewares(): array
{
return $this->middlewares;
}
}
12 changes: 12 additions & 0 deletions src/Driver/Middleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Driver;

use Doctrine\DBAL\Driver;

interface Middleware
{
public function wrap(Driver $driver): Driver;
}
4 changes: 4 additions & 0 deletions src/DriverManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ public static function getConnection(

$driver = new $className();

foreach ($config->getMiddlewares() as $middleware) {
$driver = $middleware->wrap($driver);
}

$wrapperClass = Connection::class;
if (isset($params['wrapperClass'])) {
if (! is_subclass_of($params['wrapperClass'], $wrapperClass)) {
Expand Down
128 changes: 49 additions & 79 deletions src/Portability/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,125 +2,95 @@

namespace Doctrine\DBAL\Portability;

use Doctrine\Common\EventManager;
use Doctrine\DBAL\Abstraction\Result as AbstractionResult;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\DBAL\ColumnCase;
use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\Connection as BaseConnection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\PDO\Connection as PDOConnection;
use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
use Doctrine\DBAL\Driver\Result as DriverResult;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\Result as DBALResult;
use PDO;

use const CASE_LOWER;
use const CASE_UPPER;
use Doctrine\DBAL\ParameterType;

/**
* Portability wrapper for a Connection.
*/
class Connection extends BaseConnection
final class Connection implements ConnectionInterface
{
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;

/** @var int */
private $portability = self::PORTABILITY_NONE;

/** @var int */
private $case = 0;
/** @var ConnectionInterface */
private $connection;

/** @var Converter */
private $converter;

/** {@inheritDoc} */
public function __construct(
array $params,
Driver $driver,
?Configuration $config = null,
?EventManager $eventManager = null
) {
if (isset($params['portability'])) {
$this->portability = $params['portability'];
}

if (isset($params['fetch_case'])) {
$this->case = $params['fetch_case'];
}

unset($params['portability'], $params['fetch_case']);

parent::__construct($params, $driver, $config, $eventManager);
public function __construct(ConnectionInterface $connection, Converter $converter)
{
$this->connection = $connection;
$this->converter = $converter;
}

/**
* {@inheritdoc}
* @return Statement
*/
public function connect()
public function prepare(string $sql): DriverStatement
{
$ret = parent::connect();
if ($ret) {
$portability = (new OptimizeFlags())(
$this->getDatabasePlatform(),
$this->portability
);

$case = 0;
return new Statement(
$this->connection->prepare($sql),
$this->converter
);
}

if ($this->case !== 0 && ($portability & self::PORTABILITY_FIX_CASE) !== 0) {
if ($this->_conn instanceof PDOConnection) {
// make use of c-level support for case handling
$this->_conn->getWrappedConnection()->setAttribute(PDO::ATTR_CASE, $this->case);
} else {
$case = $this->case === ColumnCase::LOWER ? CASE_LOWER : CASE_UPPER;
}
}
public function query(string $sql): DriverResult
{
return new Result(
$this->connection->query($sql),
$this->converter
);
}

$this->converter = new Converter(
($portability & self::PORTABILITY_EMPTY_TO_NULL) !== 0,
($portability & self::PORTABILITY_RTRIM) !== 0,
$case
);
}
/**
* {@inheritDoc}
*/
public function quote($input, $type = ParameterType::STRING)
{
return $this->connection->quote($input, $type);
}

return $ret;
public function exec(string $statement): int
{
return $this->connection->exec($statement);
}

/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function executeQuery(string $query, array $params = [], $types = [], ?QueryCacheProfile $qcp = null): AbstractionResult
public function lastInsertId($name = null)
{
return $this->wrapResult(
parent::executeQuery($query, $params, $types, $qcp)
);
return $this->connection->lastInsertId($name);
}

/**
* @return Statement
* {@inheritDoc}
*/
public function prepare(string $sql): DriverStatement
public function beginTransaction()
{
return new Statement(parent::prepare($sql), $this->converter);
return $this->connection->beginTransaction();
}

public function query(string $sql): DriverResult
/**
* {@inheritDoc}
*/
public function commit()
{
return $this->wrapResult(
parent::query($sql)
);
return $this->connection->commit();
}

private function wrapResult(DriverResult $result): AbstractionResult
/**
* {@inheritDoc}
*/
public function rollBack()
{
return new DBALResult(
new Result($result, $this->converter),
$this
);
return $this->connection->rollBack();
}
}
90 changes: 90 additions & 0 deletions src/Portability/Driver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

namespace Doctrine\DBAL\Portability;

use Doctrine\DBAL\ColumnCase;
use Doctrine\DBAL\Connection as DBALConnection;
use Doctrine\DBAL\Driver as DriverInterface;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Driver\PDO;
use Doctrine\DBAL\Platforms\AbstractPlatform;

use const CASE_LOWER;
use const CASE_UPPER;

final class Driver implements DriverInterface
{
/** @var DriverInterface */
private $driver;

/** @var int */
private $mode;

/** @var int */
private $case;

public function __construct(DriverInterface $driver, int $mode, int $case)
{
$this->driver = $driver;
$this->mode = $mode;
$this->case = $case;
}

/**
* {@inheritDoc}
*/
public function connect(array $params)
{
$connection = $this->driver->connect($params);

$portability = (new OptimizeFlags())(
$this->getDatabasePlatform(),
$this->mode
);

$case = 0;

if ($this->case !== 0 && ($portability & Connection::PORTABILITY_FIX_CASE) !== 0) {
if ($connection instanceof PDO\Connection) {
// make use of c-level support for case handling
$portability &= ~Connection::PORTABILITY_FIX_CASE;
$connection->getWrappedConnection()->setAttribute(\PDO::ATTR_CASE, $this->case);
} else {
$case = $this->case === ColumnCase::LOWER ? CASE_LOWER : CASE_UPPER;
}
}

$convertEmptyStringToNull = ($portability & Connection::PORTABILITY_EMPTY_TO_NULL) !== 0;
$rightTrimString = ($portability & Connection::PORTABILITY_RTRIM) !== 0;

if (! $convertEmptyStringToNull && ! $rightTrimString && $case === 0) {
return $connection;
}

return new Connection(
$connection,
new Converter($convertEmptyStringToNull, $rightTrimString, $case)
);
}

/**
* {@inheritDoc}
*/
public function getDatabasePlatform()
{
return $this->driver->getDatabasePlatform();
}

/**
* {@inheritDoc}
*/
public function getSchemaManager(DBALConnection $conn, AbstractPlatform $platform)
{
return $this->driver->getSchemaManager($conn, $platform);
}

public function getExceptionConverter(): ExceptionConverter
{
return $this->driver->getExceptionConverter();
}
}
32 changes: 32 additions & 0 deletions src/Portability/Middleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Portability;

use Doctrine\DBAL\Driver as DriverInterface;
use Doctrine\DBAL\Driver\Middleware as MiddlewareInterface;

final class Middleware implements MiddlewareInterface
{
/** @var int */
private $mode;

/** @var int */
private $case;

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;
}
}
Loading