Rework the portability layer to act as a middleware

parent d3fd4f6d
...@@ -158,9 +158,11 @@ The method no longer accepts the `$username`, `$password` and `$driverOptions` a ...@@ -158,9 +158,11 @@ The method no longer accepts the `$username`, `$password` and `$driverOptions` a
This class was deprecated in favor of `PrimaryReadReplicaConnection` 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 ## BC BREAK changes in fetching statement results
......
...@@ -98,10 +98,5 @@ parameters: ...@@ -98,10 +98,5 @@ parameters:
paths: paths:
- %currentWorkingDirectory%/src/Id/TableGenerator.php - %currentWorkingDirectory%/src/Id/TableGenerator.php
- %currentWorkingDirectory%/src/Schema/SqliteSchemaManager.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: includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon - vendor/phpstan/phpstan-strict-rules/rules.neon
...@@ -2,26 +2,15 @@ ...@@ -2,26 +2,15 @@
namespace Doctrine\DBAL\Portability; namespace Doctrine\DBAL\Portability;
use Doctrine\Common\EventManager; use Doctrine\DBAL\Driver\Connection as ConnectionInterface;
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\Result as DriverResult; use Doctrine\DBAL\Driver\Result as DriverResult;
use Doctrine\DBAL\Driver\Statement as DriverStatement; use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\Result as DBALResult; use Doctrine\DBAL\ParameterType;
use PDO;
use const CASE_LOWER;
use const CASE_UPPER;
/** /**
* Portability wrapper for a Connection. * Portability wrapper for a Connection.
*/ */
class Connection extends BaseConnection final class Connection implements ConnectionInterface
{ {
public const PORTABILITY_ALL = 255; public const PORTABILITY_ALL = 255;
public const PORTABILITY_NONE = 0; public const PORTABILITY_NONE = 0;
...@@ -29,98 +18,79 @@ class Connection extends BaseConnection ...@@ -29,98 +18,79 @@ class Connection extends BaseConnection
public const PORTABILITY_EMPTY_TO_NULL = 4; public const PORTABILITY_EMPTY_TO_NULL = 4;
public const PORTABILITY_FIX_CASE = 8; public const PORTABILITY_FIX_CASE = 8;
/** @var int */ /** @var ConnectionInterface */
private $portability = self::PORTABILITY_NONE; private $connection;
/** @var int */
private $case = 0;
/** @var Converter */ /** @var Converter */
private $converter; private $converter;
/** {@inheritDoc} */ public function __construct(ConnectionInterface $connection, Converter $converter)
public function __construct( {
array $params, $this->connection = $connection;
Driver $driver, $this->converter = $converter;
?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);
} }
/** /**
* {@inheritdoc} * @return Statement
*/ */
public function connect() public function prepare(string $sql): DriverStatement
{ {
$ret = parent::connect(); return new Statement(
if ($ret) { $this->connection->prepare($sql),
$portability = (new OptimizeFlags())( $this->converter
$this->getDatabasePlatform(),
$this->portability
); );
$case = 0;
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;
}
} }
$this->converter = new Converter( public function query(string $sql): DriverResult
($portability & self::PORTABILITY_EMPTY_TO_NULL) !== 0, {
($portability & self::PORTABILITY_RTRIM) !== 0, return new Result(
$case $this->connection->query($sql),
$this->converter
); );
} }
return $ret; /**
* {@inheritDoc}
*/
public function quote($input, $type = ParameterType::STRING)
{
return $this->connection->quote($input, $type);
}
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( return $this->connection->lastInsertId($name);
parent::executeQuery($query, $params, $types, $qcp)
);
} }
/** /**
* @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( return $this->connection->commit();
parent::query($sql)
);
} }
private function wrapResult(DriverResult $result): AbstractionResult /**
* {@inheritDoc}
*/
public function rollBack()
{ {
return new DBALResult( return $this->connection->rollBack();
new Result($result, $this->converter),
$this
);
} }
} }
<?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();
}
}
<?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;
}
}
...@@ -9,7 +9,7 @@ use Doctrine\DBAL\ParameterType; ...@@ -9,7 +9,7 @@ use Doctrine\DBAL\ParameterType;
/** /**
* Portability wrapper for a Statement. * Portability wrapper for a Statement.
*/ */
class Statement implements DriverStatement final class Statement implements DriverStatement
{ {
/** @var DriverStatement */ /** @var DriverStatement */
private $stmt; private $stmt;
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
namespace Doctrine\DBAL\Tests\Functional; namespace Doctrine\DBAL\Tests\Functional;
use Doctrine\DBAL\ColumnCase; use Doctrine\DBAL\ColumnCase;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Portability\Connection as ConnectionPortability; use Doctrine\DBAL\Portability\Connection;
use Doctrine\DBAL\Portability\Middleware;
use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Tests\FunctionalTestCase; use Doctrine\DBAL\Tests\FunctionalTestCase;
use Throwable; use Throwable;
...@@ -17,30 +17,15 @@ use function strlen; ...@@ -17,30 +17,15 @@ use function strlen;
*/ */
class PortabilityTest extends FunctionalTestCase class PortabilityTest extends FunctionalTestCase
{ {
/** @var Connection */ protected function setUp(): void
private $portableConnection;
protected function tearDown(): void
{ {
if ($this->portableConnection) { parent::setUp();
$this->portableConnection->close();
}
parent::tearDown();
}
private function getPortableConnection( $this->connection = DriverManager::getConnection(
int $portabilityMode = ConnectionPortability::PORTABILITY_ALL, $this->connection->getParams(),
int $case = ColumnCase::LOWER $this->connection->getConfiguration()
): Connection { ->setMiddlewares([new Middleware(Connection::PORTABILITY_ALL, ColumnCase::LOWER)])
if (! $this->portableConnection) { );
$params = $this->connection->getParams();
$params['wrapperClass'] = ConnectionPortability::class;
$params['portability'] = $portabilityMode;
$params['fetch_case'] = $case;
$this->portableConnection = DriverManager::getConnection($params, $this->connection->getConfiguration(), $this->connection->getEventManager());
try { try {
$table = new Table('portability_table'); $table = new Table('portability_table');
...@@ -49,30 +34,32 @@ class PortabilityTest extends FunctionalTestCase ...@@ -49,30 +34,32 @@ class PortabilityTest extends FunctionalTestCase
$table->addColumn('Test_Null', 'string', ['notnull' => false]); $table->addColumn('Test_Null', 'string', ['notnull' => false]);
$table->setPrimaryKey(['Test_Int']); $table->setPrimaryKey(['Test_Int']);
$sm = $this->portableConnection->getSchemaManager(); $sm = $this->connection->getSchemaManager();
$sm->createTable($table); $sm->createTable($table);
$this->portableConnection->insert('portability_table', ['Test_Int' => 1, 'Test_String' => 'foo', 'Test_Null' => '']); $this->connection->insert('portability_table', ['Test_Int' => 1, 'Test_String' => 'foo', 'Test_Null' => '']);
$this->portableConnection->insert('portability_table', ['Test_Int' => 2, 'Test_String' => 'foo ', 'Test_Null' => null]); $this->connection->insert('portability_table', ['Test_Int' => 2, 'Test_String' => 'foo ', 'Test_Null' => null]);
} catch (Throwable $e) { } catch (Throwable $e) {
} }
} }
return $this->portableConnection; public function tearDown(): void
{
self::resetSharedConn();
} }
public function testFullFetchMode(): void public function testFullFetchMode(): void
{ {
$rows = $this->getPortableConnection()->fetchAllAssociative('SELECT * FROM portability_table'); $rows = $this->connection->fetchAllAssociative('SELECT * FROM portability_table');
$this->assertFetchResultRows($rows); $this->assertFetchResultRows($rows);
$result = $this->getPortableConnection()->query('SELECT * FROM portability_table'); $result = $this->connection->query('SELECT * FROM portability_table');
while (($row = $result->fetchAssociative())) { while (($row = $result->fetchAssociative())) {
$this->assertFetchResultRow($row); $this->assertFetchResultRow($row);
} }
$result = $this->getPortableConnection() $result = $this->connection
->prepare('SELECT * FROM portability_table') ->prepare('SELECT * FROM portability_table')
->execute(); ->execute();
...@@ -83,17 +70,15 @@ class PortabilityTest extends FunctionalTestCase ...@@ -83,17 +70,15 @@ class PortabilityTest extends FunctionalTestCase
public function testConnFetchMode(): void public function testConnFetchMode(): void
{ {
$conn = $this->getPortableConnection(); $rows = $this->connection->fetchAllAssociative('SELECT * FROM portability_table');
$rows = $conn->fetchAllAssociative('SELECT * FROM portability_table');
$this->assertFetchResultRows($rows); $this->assertFetchResultRows($rows);
$result = $conn->query('SELECT * FROM portability_table'); $result = $this->connection->query('SELECT * FROM portability_table');
while (($row = $result->fetchAssociative())) { while (($row = $result->fetchAssociative())) {
$this->assertFetchResultRow($row); $this->assertFetchResultRow($row);
} }
$result = $conn->prepare('SELECT * FROM portability_table') $result = $this->connection->prepare('SELECT * FROM portability_table')
->execute(); ->execute();
while (($row = $result->fetchAssociative())) { while (($row = $result->fetchAssociative())) {
...@@ -133,10 +118,9 @@ class PortabilityTest extends FunctionalTestCase ...@@ -133,10 +118,9 @@ class PortabilityTest extends FunctionalTestCase
* *
* @dataProvider fetchColumnProvider * @dataProvider fetchColumnProvider
*/ */
public function testfetchColumn(string $field, array $expected): void public function testFetchColumn(string $field, array $expected): void
{ {
$conn = $this->getPortableConnection(); $result = $this->connection->query('SELECT ' . $field . ' FROM portability_table');
$result = $conn->query('SELECT ' . $field . ' FROM portability_table');
$column = $result->fetchFirstColumn(); $column = $result->fetchFirstColumn();
self::assertEquals($expected, $column); self::assertEquals($expected, $column);
...@@ -161,8 +145,7 @@ class PortabilityTest extends FunctionalTestCase ...@@ -161,8 +145,7 @@ class PortabilityTest extends FunctionalTestCase
public function testFetchAllNullColumn(): void public function testFetchAllNullColumn(): void
{ {
$conn = $this->getPortableConnection(); $result = $this->connection->query('SELECT Test_Null FROM portability_table');
$result = $conn->query('SELECT Test_Null FROM portability_table');
$column = $result->fetchFirstColumn(); $column = $result->fetchFirstColumn();
self::assertSame([null, null], $column); self::assertSame([null, null], $column);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment