Add custom Doctrine Driver

[MAILPOET-3150]
This commit is contained in:
Pavel Dohnal
2020-11-11 09:51:17 +01:00
committed by Veljko V
parent ce27fd08fe
commit a751a2a459
8 changed files with 348 additions and 4 deletions

BIN
composer.phar Executable file

Binary file not shown.

View File

@ -17,6 +17,6 @@ class DatabaseInitializer {
// pass the same PDO connection to legacy Database object // pass the same PDO connection to legacy Database object
$database = new Database(); $database = new Database();
$database->init($connection->getWrappedConnection()); $database->init($connection->getWrappedConnection()->getConnection());
} }
} }

View File

@ -3,6 +3,7 @@
namespace MailPoet\Doctrine; namespace MailPoet\Doctrine;
use MailPoet\Config\Env; use MailPoet\Config\Env;
use MailPoet\Doctrine\Driver\Driver;
use MailPoet\Doctrine\Types\BigIntType; use MailPoet\Doctrine\Types\BigIntType;
use MailPoet\Doctrine\Types\JsonOrSerializedType; use MailPoet\Doctrine\Types\JsonOrSerializedType;
use MailPoet\Doctrine\Types\JsonType; use MailPoet\Doctrine\Types\JsonType;
@ -30,6 +31,7 @@ class ConnectionFactory {
$connectionParams = [ $connectionParams = [
'wrapperClass' => SerializableConnection::class, 'wrapperClass' => SerializableConnection::class,
'driver' => self::DRIVER, 'driver' => self::DRIVER,
'driverClass' => Driver::class,
'platform' => new $platformClass, 'platform' => new $platformClass,
'user' => Env::$dbUsername, 'user' => Env::$dbUsername,
'password' => Env::$dbPassword, 'password' => Env::$dbPassword,

View File

@ -0,0 +1,22 @@
<?php
namespace MailPoet\Doctrine\Driver;
use MailPoetVendor\Doctrine\DBAL\DBALException;
class Driver extends \MailPoetVendor\Doctrine\DBAL\Driver\PDOMySql\Driver {
public function connect(array $params, $username = null, $password = null, array $driverOptions = []) {
try {
$conn = new PDOConnection(
$this->constructPdoDsn($params),
$username,
$password,
$driverOptions
);
} catch (\PDOException $e) {
throw DBALException::driverException($this, $e);
}
return $conn;
}
}

View File

@ -0,0 +1,131 @@
<?php
namespace MailPoet\Doctrine\Driver;
use MailPoet\InvalidStateException;
class PDOConnection implements \MailPoetVendor\Doctrine\DBAL\Driver\Connection, \MailPoetVendor\Doctrine\DBAL\Driver\ServerInfoAwareConnection {
/** @var \PDO */
private $connection;
/**
* @param string $dsn
* @param string|null $user
* @param string|null $password
* @param array|null $options
*/
public function __construct($dsn, $user = null, $password = null, array $options = null) {
try {
$this->connection = new \PDO($dsn, (string)$user, (string)$password, (array)$options);
$this->connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
} catch (\PDOException $exception) {
throw new \MailPoetVendor\Doctrine\DBAL\Driver\PDOException($exception);
}
}
public function getConnection(): \PDO {
return $this->connection;
}
/**
* {@inheritdoc}
* @return int|false
*/
public function exec($statement) {
try {
return $this->connection->exec($statement);
} catch (\PDOException $exception) {
throw new \MailPoetVendor\Doctrine\DBAL\Driver\PDOException($exception);
}
}
public function getServerVersion() {
return $this->connection->getAttribute(\PDO::ATTR_SERVER_VERSION);
}
public function prepare($prepareString, $driverOptions = []) {
try {
return $this->createStatement(
$this->connection->prepare($prepareString, $driverOptions)
);
} catch (\PDOException $exception) {
throw new \MailPoetVendor\Doctrine\DBAL\Driver\PDOException($exception);
}
}
/**
* Creates a wrapped statement
*/
protected function createStatement(\PDOStatement $stmt): PDOStatement {
return new PDOStatement($stmt);
}
public function query() {
$args = func_get_args();
$argsCount = count($args);
try {
if ($argsCount == 4) {
$stmt = $this->connection->query($args[0], $args[1], $args[2], $args[3]);
} elseif ($argsCount == 3) {
$stmt = $this->connection->query($args[0], $args[1], $args[2]);
} elseif ($argsCount == 2) {
$stmt = $this->connection->query($args[0], $args[1]);
} else {
$stmt = $this->connection->query($args[0]);
}
if ($stmt !== false) {
return $this->createStatement($stmt);
} else {
throw new InvalidStateException('Statement is missing');
}
} catch (\PDOException $exception) {
throw new \MailPoetVendor\Doctrine\DBAL\Driver\PDOException($exception);
}
}
public function quote($input, $type = \PDO::PARAM_STR) {
return $this->connection->quote($input, $type);
}
public function lastInsertId($name = null) {
try {
if ($name === null) {
return $this->connection->lastInsertId();
}
return $this->connection->lastInsertId($name);
} catch (\PDOException $exception) {
throw new \MailPoetVendor\Doctrine\DBAL\Driver\PDOException($exception);
}
}
public function getAttribute($attribute) {
return $this->connection->getAttribute($attribute);
}
public function requiresQueryForServerVersion() {
return false;
}
public function beginTransaction() {
return $this->connection->beginTransaction();
}
public function commit() {
return $this->connection->commit();
}
public function rollBack() {
return $this->connection->rollBack();
}
public function errorCode() {
return $this->connection->errorCode();
}
public function errorInfo() {
return $this->connection->errorInfo();
}
}

View File

@ -0,0 +1,187 @@
<?php
namespace MailPoet\Doctrine\Driver;
use MailPoet\InvalidStateException;
use MailPoetVendor\Doctrine\DBAL\Driver\PDOException;
use MailPoetVendor\Doctrine\DBAL\Driver\Statement;
use MailPoetVendor\Doctrine\DBAL\Exception\InvalidArgumentException;
use MailPoetVendor\Doctrine\DBAL\ParameterType;
use PDO;
class PDOStatement implements \IteratorAggregate, Statement {
private const PARAM_TYPE_MAP = [
ParameterType::NULL => PDO::PARAM_NULL,
ParameterType::INTEGER => PDO::PARAM_INT,
ParameterType::STRING => PDO::PARAM_STR,
ParameterType::BINARY => PDO::PARAM_LOB,
ParameterType::LARGE_OBJECT => PDO::PARAM_LOB,
ParameterType::BOOLEAN => PDO::PARAM_BOOL,
];
/** @var \PDOStatement */
private $statement;
/**
* Protected constructor.
*/
public function __construct(\PDOStatement $stmt) {
$this->statement = $stmt;
}
public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) {
// This thin wrapper is necessary to shield against the weird signature
// of PDOStatement::setFetchMode(): even if the second and third
// parameters are optional, PHP will not let us remove it from this
// declaration.
try {
if ($arg2 === null && $arg3 === null) {
return $this->statement->setFetchMode($fetchMode);
}
if ($arg3 === null) {
return $this->statement->setFetchMode($fetchMode, $arg2);
}
return $this->statement->setFetchMode($fetchMode, $arg2, $arg3);
} catch (\PDOException $exception) {
throw new PDOException($exception);
}
}
public function bindValue($param, $value, $type = \PDO::PARAM_STR) {
$type = $this->convertParamType($type);
try {
return $this->statement->bindValue($param, $value, $type);
} catch (\PDOException $exception) {
throw new PDOException($exception);
}
}
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null, $driverOptions = null) {
if (is_null($type)) $type = ParameterType::STRING;
$type = $this->convertParamType($type);
try {
return $this->statement->bindParam($param, $variable, $type, ...array_slice(func_get_args(), 3));
} catch (\PDOException $exception) {
throw new PDOException($exception);
}
}
public function closeCursor() {
try {
return $this->statement->closeCursor();
} catch (\PDOException $exception) {
// Exceptions not allowed by the interface.
// In case driver implementations do not adhere to the interface, silence exceptions here.
return true;
}
}
public function execute($params = null) {
try {
return $this->statement->execute($params);
} catch (\PDOException $exception) {
throw new PDOException($exception);
}
}
public function fetch($fetchMode = null, $cursorOrientation = null, $cursorOffset = null) {
try {
if ($fetchMode === null && $cursorOrientation === null && $cursorOffset === null) {
return $this->statement->fetch();
}
if ($fetchMode !== null && $cursorOrientation === null && $cursorOffset === null) {
return $this->statement->fetch($fetchMode);
}
if ($fetchMode !== null && $cursorOrientation !== null && $cursorOffset === null) {
return $this->statement->fetch($fetchMode, $cursorOrientation);
}
if ($fetchMode !== null && $cursorOrientation !== null && $cursorOffset !== null) {
return $this->statement->fetch($fetchMode, $cursorOrientation, $cursorOffset);
}
throw new InvalidStateException('Invalid arguments for fetch');
} catch (\PDOException $exception) {
throw new PDOException($exception);
}
}
public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) {
try {
$data = null;
if ($fetchMode === null && $fetchArgument === null && $ctorArgs === null) {
$data = $this->statement->fetchAll();
}
if ($fetchMode !== null && $fetchArgument === null && $ctorArgs === null) {
$data = $this->statement->fetchAll($fetchMode);
}
if ($fetchMode !== null && $fetchArgument !== null && $ctorArgs === null) {
$data = $this->statement->fetchAll($fetchMode, $fetchArgument);
}
if ($fetchMode !== null && $fetchArgument !== null && $ctorArgs !== null) {
$data = $this->statement->fetchAll($fetchMode, $fetchArgument, $ctorArgs);
}
if (is_array($data)) {
return $data;
} else {
throw new InvalidStateException('Invalid data returned in fetchAll');
}
} catch (\PDOException $exception) {
throw new PDOException($exception);
}
}
public function fetchColumn($columnIndex = 0) {
try {
return $this->statement->fetchColumn($columnIndex);
} catch (\PDOException $exception) {
throw new PDOException($exception);
}
}
public function columnCount() {
return $this->statement->columnCount();
}
public function errorCode() {
return $this->statement->errorCode();
}
public function errorInfo() {
return $this->statement->errorInfo();
}
public function rowCount() {
return $this->statement->rowCount();
}
public function getIterator() {
while (($result = $this->statement->fetch()) !== false) {
yield $result;
}
}
/**
* Converts DBAL parameter type to PDO parameter type
*
* @param int $type Parameter type
*
* @throws PDOException
*/
private function convertParamType(int $type): int {
if (!isset(self::PARAM_TYPE_MAP[$type])) {
throw new InvalidArgumentException('Unknown parameter ' . $type);
}
return self::PARAM_TYPE_MAP[$type];
}
}

View File

@ -4,6 +4,7 @@ namespace MailPoet\Test\Config;
use MailPoet\Config\Database; use MailPoet\Config\Database;
use MailPoet\Config\Env; use MailPoet\Config\Env;
use MailPoetVendor\Doctrine\DBAL\Connection;
use MailPoetVendor\Idiorm\ORM; use MailPoetVendor\Idiorm\ORM;
class DatabaseTest extends \MailPoetTest { class DatabaseTest extends \MailPoetTest {
@ -28,7 +29,8 @@ class DatabaseTest extends \MailPoetTest {
} }
public function testItSetsDBDriverOptions() { public function testItSetsDBDriverOptions() {
$this->database->init($this->connection->getWrappedConnection()); $connection = $this->diContainer->get(Connection::class);
$this->database->init($connection->getWrappedConnection()->getConnection());
$result = ORM::for_table("") $result = ORM::for_table("")
->raw_query( ->raw_query(
'SELECT ' . 'SELECT ' .

View File

@ -29,7 +29,7 @@ class ConnectionFactoryTest extends \MailPoetTest {
$connection = $connectionFactory->createConnection(); $connection = $connectionFactory->createConnection();
expect($connection)->isInstanceOf(SerializableConnection::class); expect($connection)->isInstanceOf(SerializableConnection::class);
expect($connection->getWrappedConnection())->isInstanceOf(PDO::class); expect($connection->getWrappedConnection()->getConnection())->isInstanceOf(PDO::class);
expect($connection->getDriver())->isInstanceOf(PDOMySql\Driver::class); expect($connection->getDriver())->isInstanceOf(PDOMySql\Driver::class);
expect($connection->getDatabasePlatform())->isInstanceOf(MySqlPlatform::class); expect($connection->getDatabasePlatform())->isInstanceOf(MySqlPlatform::class);
expect($connection->getHost())->equals(Env::$dbHost); expect($connection->getHost())->equals(Env::$dbHost);
@ -80,7 +80,7 @@ class ConnectionFactoryTest extends \MailPoetTest {
Env::$dbHost = '::ffff:' . gethostbyname($this->envBackup['db_host']); Env::$dbHost = '::ffff:' . gethostbyname($this->envBackup['db_host']);
$connectionFactory = new ConnectionFactory(); $connectionFactory = new ConnectionFactory();
$connection = $connectionFactory->createConnection(); $connection = $connectionFactory->createConnection();
expect($connection->getWrappedConnection())->isInstanceOf(PDO::class); expect($connection->getWrappedConnection()->getConnection())->isInstanceOf(PDO::class);
expect($connection->executeQuery('SELECT 1')->fetchColumn())->same('1'); expect($connection->executeQuery('SELECT 1')->fetchColumn())->same('1');
} }