diff --git a/.circleci/config.yml b/.circleci/config.yml index bb742bd..bafcd15 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,8 +1,8 @@ version: 2 jobs: - test-php73: + test-php74: docker: - - image: circleci/php:7.3-cli + - image: circleci/php:7.4-cli - image: postgres:alpine environment: POSTGRES_PASSWORD: root @@ -20,12 +20,12 @@ jobs: - run: name: Run tests command: | - composer update -n --prefer-dist --prefer-lowest --no-suggest - php vendor/bin/phpunit + composer update -n --prefer-dist + .circleci/wait-and-run-phpunit.sh - test-php74: + test-php80: docker: - - image: circleci/php:7.4-cli + - image: circleci/php:8.0-cli - image: postgres:alpine environment: POSTGRES_PASSWORD: root @@ -43,13 +43,13 @@ jobs: - run: name: Run tests command: | - composer update -n --prefer-dist --no-suggest - php vendor/bin/phpunit + composer update -n --prefer-dist + .circleci/wait-and-run-phpunit.sh workflows: version: 2 test: jobs: - - test-php73 - - test-php74 \ No newline at end of file + - test-php74 + - test-php80 diff --git a/.circleci/wait-and-run-phpunit.sh b/.circleci/wait-and-run-phpunit.sh new file mode 100755 index 0000000..396b5db --- /dev/null +++ b/.circleci/wait-and-run-phpunit.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +while ! nc -z localhost 3306; +do + echo "Waiting for mysql. Slepping"; + sleep 1; +done; +echo "Connected to mysql!"; + +while ! nc -z localhost 5432; +do + echo "Waiting for Postgresql. Slepping"; + sleep 1; +done; +echo "Connected to Postgresql!"; + +php vendor/bin/phpunit \ No newline at end of file diff --git a/.gitignore b/.gitignore index 35e1c90..a1e420d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /vendor /var /composer.lock -/.php_cs.cache \ No newline at end of file +/.php_cs.cache +/.phpunit.result.cache \ No newline at end of file diff --git a/.php_cs.cache b/.php_cs.cache deleted file mode 100644 index be41ed8..0000000 --- a/.php_cs.cache +++ /dev/null @@ -1 +0,0 @@ -{"php":"7.4.5","version":"2.16.1","indent":" ","lineEnding":"\n","rules":{"blank_line_after_namespace":true,"braces":{"allow_single_line_closure":true},"class_definition":{"single_line":true},"constant_case":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":true,"no_break_comment":true,"no_closing_tag":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":true,"single_import_per_statement":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"visibility_required":true,"encoding":true,"full_opening_tag":true,"array_syntax":{"syntax":"short"},"binary_operator_spaces":true,"blank_line_after_opening_tag":true,"blank_line_before_statement":{"statements":["return"]},"cast_spaces":true,"class_attributes_separation":{"elements":["method"]},"concat_space":true,"declare_equal_normalize":true,"function_typehint_space":true,"include":true,"increment_style":true,"lowercase_cast":true,"lowercase_static_reference":true,"magic_constant_casing":true,"magic_method_casing":true,"native_function_casing":true,"native_function_type_declaration_casing":true,"new_with_braces":true,"no_blank_lines_after_class_opening":true,"no_blank_lines_after_phpdoc":true,"no_empty_comment":true,"no_empty_phpdoc":true,"no_empty_statement":true,"no_extra_blank_lines":{"tokens":["curly_brace_block","extra","parenthesis_brace_block","square_brace_block","throw","use"]},"no_leading_import_slash":true,"no_leading_namespace_whitespace":true,"no_mixed_echo_print":true,"no_multiline_whitespace_around_double_arrow":true,"no_short_bool_cast":true,"no_singleline_whitespace_before_semicolons":true,"no_spaces_around_offset":true,"no_trailing_comma_in_list_call":true,"no_trailing_comma_in_singleline_array":true,"no_unneeded_control_parentheses":true,"no_unneeded_curly_braces":true,"no_unneeded_final_method":true,"no_unused_imports":true,"no_whitespace_before_comma_in_array":true,"no_whitespace_in_blank_line":true,"normalize_index_brace":true,"object_operator_without_whitespace":true,"ordered_imports":true,"php_unit_fqcn_annotation":true,"phpdoc_align":{"tags":["method","param","property","return","throws","type","var"]},"phpdoc_annotation_without_dot":true,"phpdoc_indent":true,"phpdoc_inline_tag":true,"phpdoc_no_access":true,"phpdoc_no_alias_tag":true,"phpdoc_no_package":true,"phpdoc_no_useless_inheritdoc":true,"phpdoc_return_self_reference":true,"phpdoc_scalar":true,"phpdoc_separation":true,"phpdoc_single_line_var_spacing":true,"phpdoc_summary":true,"phpdoc_to_comment":true,"phpdoc_trim":true,"phpdoc_trim_consecutive_blank_line_separation":true,"phpdoc_types":true,"phpdoc_types_order":{"null_adjustment":"always_last","sort_algorithm":"none"},"phpdoc_var_without_name":true,"return_type_declaration":true,"semicolon_after_instruction":true,"short_scalar_cast":true,"single_blank_line_before_namespace":true,"single_line_comment_style":{"comment_types":["hash"]},"single_line_throw":true,"single_quote":true,"single_trait_insert_per_statement":true,"space_after_semicolon":{"remove_in_empty_for_expressions":true},"standardize_increment":true,"standardize_not_equals":true,"ternary_operator_spaces":true,"trailing_comma_in_multiline_array":true,"trim_array_spaces":true,"unary_operator_spaces":true,"whitespace_after_comma_in_array":true,"yoda_style":true},"hashes":{"src\/Driver\/Mysql\/MysqlDriver.php":347719532,"src\/Driver\/Mysql\/EmptyDoctrineMysqlDriver.php":3752159343,"src\/Driver\/PlainDriverException.php":1220843308,"src\/Driver\/SQLite\/SQLiteDriver.php":1642906119,"src\/Driver\/SQLite\/EmptyDoctrineSQLiteDriver.php":117021226,"src\/Driver\/Driver.php":2665996535,"src\/Driver\/PostgreSQL\/EmptyDoctrinePostgreSQLDriver.php":2852569520,"src\/Driver\/PostgreSQL\/PostgreSQLDriver.php":3513572624,"src\/Credentials.php":1470751445,"src\/Result.php":85073553,"src\/Connection.php":3362170122,"src\/Mock\/MockedDBALConnection.php":2847858253,"src\/Mock\/MockedDriver.php":1506357739,"tests\/ConnectionTest.php":2012967726,"tests\/SQLiteConnectionTest.php":715561380,"tests\/PostgreSQLConnectionTest.php":1654116774,"tests\/MysqlConnectionTest.php":1643302408,"src\/Driver\/AbstractDriver.php":219146991}} \ No newline at end of file diff --git a/composer.json b/composer.json index 904f381..9b76649 100644 --- a/composer.json +++ b/composer.json @@ -10,13 +10,13 @@ } ], "require": { - "php": "^7.3", - "doctrine/dbal": "^2.5", + "php": "^7.4 || ^8.0", + "doctrine/dbal": "^3", "react/event-loop": "^1" }, "require-dev": { - "phpunit/phpunit": "7.5.17", - "clue/block-react": "*", + "phpunit/phpunit": "^9", + "clue/block-react": "^1", "react/mysql": "^0.5", "clue/reactphp-sqlite": "^1", "voryx/pgasync": "^2" diff --git a/phpunit.xml b/phpunit.xml index 268e311..be01d24 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,6 +6,7 @@ convertErrorsToExceptions="false" convertNoticesToExceptions="false" convertWarningsToExceptions="false" + convertDeprecationsToExceptions="false" processIsolation="false" stopOnFailure="true" bootstrap="vendor/autoload.php" diff --git a/src/Credentials.php b/src/Credentials.php index 91bf0d7..5771f5c 100644 --- a/src/Credentials.php +++ b/src/Credentials.php @@ -138,7 +138,7 @@ public function toString(): string $this->dbName ); - if (strpos($asString, ':@') === 0) { + if (0 === strpos($asString, ':@')) { return rawurldecode( substr($asString, 2) ); diff --git a/src/Driver/Exception.php b/src/Driver/Exception.php new file mode 100644 index 0000000..be7c4fa --- /dev/null +++ b/src/Driver/Exception.php @@ -0,0 +1,25 @@ + + */ + +declare(strict_types=1); + +namespace Drift\DBAL\Driver; + +use Doctrine\DBAL\Driver\AbstractException; + +/** + * Class Exception. + */ +class Exception extends AbstractException +{ +} diff --git a/src/Driver/Mysql/MysqlDriver.php b/src/Driver/Mysql/MysqlDriver.php index 8e64979..1ef61ae 100644 --- a/src/Driver/Mysql/MysqlDriver.php +++ b/src/Driver/Mysql/MysqlDriver.php @@ -15,9 +15,12 @@ namespace Drift\DBAL\Driver\Mysql; +use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface; +use Doctrine\DBAL\Driver\API\MySQL\ExceptionConverter; +use Doctrine\DBAL\Query; use Drift\DBAL\Credentials; use Drift\DBAL\Driver\AbstractDriver; -use Drift\DBAL\Driver\PlainDriverException; +use Drift\DBAL\Driver\Exception as DoctrineException; use Drift\DBAL\Result; use React\EventLoop\LoopInterface; use React\MySQL\ConnectionInterface; @@ -32,20 +35,10 @@ */ class MysqlDriver extends AbstractDriver { - /** - * @var Factory - */ - private $factory; - - /** - * @var ConnectionInterface - */ - private $connection; - - /** - * @var EmptyDoctrineMysqlDriver - */ - private $doctrineDriver; + private Factory $factory; + private ConnectionInterface $connection; + private EmptyDoctrineMysqlDriver $doctrineDriver; + private ExceptionConverterInterface $exceptionConverter; /** * MysqlDriver constructor. @@ -59,6 +52,7 @@ public function __construct(LoopInterface $loop, ConnectorInterface $connector = $this->factory = is_null($connector) ? new Factory($loop) : new Factory($loop, $connector); + $this->exceptionConverter = new ExceptionConverter(); } /** @@ -88,10 +82,8 @@ public function query( $queryResult->affectedRows ); }) - ->otherwise(function (Exception $exception) { - $message = $exception->getMessage(); - - throw $this->doctrineDriver->convertException($message, PlainDriverException::createFromMessageAndErrorCode($message, (string) $exception->getCode())); + ->otherwise(function (Exception $exception) use (&$sql, &$parameters) { + throw $this->exceptionConverter->convert(new DoctrineException($exception->getMessage(), null, $exception->getCode()), new Query($sql, $parameters, [])); }); } } diff --git a/src/Driver/PostgreSQL/PostgreSQLDriver.php b/src/Driver/PostgreSQL/PostgreSQLDriver.php index 9765471..56399fb 100644 --- a/src/Driver/PostgreSQL/PostgreSQLDriver.php +++ b/src/Driver/PostgreSQL/PostgreSQLDriver.php @@ -15,10 +15,13 @@ namespace Drift\DBAL\Driver\PostgreSQL; +use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface; +use Doctrine\DBAL\Driver\API\PostgreSQL\ExceptionConverter; +use Doctrine\DBAL\Query; use Doctrine\DBAL\Query\QueryBuilder; use Drift\DBAL\Credentials; use Drift\DBAL\Driver\AbstractDriver; -use Drift\DBAL\Driver\PlainDriverException; +use Drift\DBAL\Driver\Exception as DoctrineException; use Drift\DBAL\Result; use PgAsync\Client; use PgAsync\ErrorException; @@ -31,20 +34,10 @@ */ class PostgreSQLDriver extends AbstractDriver { - /** - * @var Client - */ - private $client; - - /** - * @var LoopInterface - */ - private $loop; - - /** - * @var EmptyDoctrinePostgreSQLDriver - */ - private $doctrineDriver; + private Client $client; + private LoopInterface $loop; + private EmptyDoctrinePostgreSQLDriver $doctrineDriver; + private ExceptionConverterInterface $exceptionConverter; /** * @param LoopInterface $loop @@ -53,6 +46,7 @@ public function __construct(LoopInterface $loop) { $this->doctrineDriver = new EmptyDoctrinePostgreSQLDriver(); $this->loop = $loop; + $this->exceptionConverter = new ExceptionConverter(); } /** @@ -92,9 +86,8 @@ public function query( ->executeStatement($sql, $parameters) ->subscribe(function ($row) use (&$results) { $results[] = $row; - }, function (ErrorException $exception) use ($deferred) { + }, function (ErrorException $exception) use ($deferred, &$sql, &$parameters) { $errorResponse = $exception->getErrorResponse(); - $message = $exception->getMessage(); $code = 0; foreach ($errorResponse->getErrorMessages() as $messageLine) { if ('C' === $messageLine['type']) { @@ -102,14 +95,12 @@ public function query( } } - $exception = $this - ->doctrineDriver - ->convertException( - $message, - PlainDriverException::createFromMessageAndErrorCode( - $message, - (string) $code - )); + $exception = $this->exceptionConverter->convert( + new DoctrineException($exception->getMessage(), \strval($code)), + new Query( + $sql, $parameters, [] + ) + ); $deferred->reject($exception); }, function () use (&$results, $deferred) { diff --git a/src/Driver/SQLite/SQLiteDriver.php b/src/Driver/SQLite/SQLiteDriver.php index f219928..96a7ae0 100644 --- a/src/Driver/SQLite/SQLiteDriver.php +++ b/src/Driver/SQLite/SQLiteDriver.php @@ -18,9 +18,12 @@ use Clue\React\SQLite\DatabaseInterface; use Clue\React\SQLite\Factory; use Clue\React\SQLite\Result as SQLiteResult; +use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface; +use Doctrine\DBAL\Driver\API\SQLite\ExceptionConverter; +use Doctrine\DBAL\Query; use Drift\DBAL\Credentials; use Drift\DBAL\Driver\AbstractDriver; -use Drift\DBAL\Driver\PlainDriverException; +use Drift\DBAL\Driver\Exception as DoctrineException; use Drift\DBAL\Result; use React\EventLoop\LoopInterface; use React\Promise\PromiseInterface; @@ -31,20 +34,10 @@ */ class SQLiteDriver extends AbstractDriver { - /** - * @var Factory - */ - private $factory; - - /** - * @var DatabaseInterface - */ - private $database; - - /** - * @var EmptyDoctrineSQLiteDriver - */ - private $doctrineDriver; + private Factory $factory; + private DatabaseInterface $database; + private EmptyDoctrineSQLiteDriver $doctrineDriver; + private ExceptionConverterInterface $exceptionConverter; /** * SQLiteDriver constructor. @@ -55,6 +48,7 @@ public function __construct(LoopInterface $loop) { $this->doctrineDriver = new EmptyDoctrineSQLiteDriver(); $this->factory = new Factory($loop); + $this->exceptionConverter = new ExceptionConverter(); } /** @@ -84,10 +78,8 @@ public function query( $sqliteResult->changed ); }) - ->otherwise(function (RuntimeException $exception) { - $message = $exception->getMessage(); - - throw $this->doctrineDriver->convertException($message, PlainDriverException::createFromMessageAndErrorCode($message, (string) $exception->getCode())); + ->otherwise(function (RuntimeException $exception) use (&$sql, &$parameters) { + throw $this->exceptionConverter->convert(new DoctrineException($exception->getMessage()), new Query($sql, $parameters, [])); }); } } diff --git a/src/Mock/MockedDBALConnection.php b/src/Mock/MockedDBALConnection.php index 626245c..9a01aee 100644 --- a/src/Mock/MockedDBALConnection.php +++ b/src/Mock/MockedDBALConnection.php @@ -17,9 +17,10 @@ use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\DBALException; -use Doctrine\DBAL\Driver; use Doctrine\DBAL\ParameterType; +use Doctrine\DBAL\Result; +use Doctrine\DBAL\Statement; +use Doctrine\DBAL\Types\Type; use Exception; /** @@ -28,17 +29,23 @@ class MockedDBALConnection extends Connection { /** - * {@inheritdoc} + * Prepares an SQL statement. + * + * @param string $sql the SQL statement to prepare + * + * @throws \Doctrine\DBAL\Exception */ - public function prepare($prepareString) + public function prepare(string $sql): Statement { throw new Exception('Mocked method. Unable to be used'); } /** - * {@inheritdoc} + * BC layer for a wide-spread use-case of old DBAL APIs. + * + * @deprecated This API is deprecated and will be removed after 2022 */ - public function query() + public function query(string $sql): Result { throw new Exception('Mocked method. Unable to be used'); } @@ -52,9 +59,11 @@ public function quote($input, $type = ParameterType::STRING) } /** - * {@inheritdoc} + * BC layer for a wide-spread use-case of old DBAL APIs. + * + * @deprecated This API is deprecated and will be removed after 2022 */ - public function exec($statement) + public function exec(string $sql): int { throw new Exception('Mocked method. Unable to be used'); } @@ -113,35 +122,30 @@ public function errorInfo() * If the query is parametrized, a prepared statement is used. * If an SQLLogger is configured, the execution is logged. * - * @param string $query the SQL query to execute - * @param mixed[] $params the parameters to bind to the query, if any - * @param int[]|string[] $types the types the previous parameters are in - * @param QueryCacheProfile|null $qcp the query cache profile, optional - * - * @return Driver\ResultStatement the executed statement + * @param string $sql SQL query + * @param list|array $params Query parameters + * @param array|array $types Parameter types * - * @throws DBALException + * @throws \Doctrine\DBAL\Exception */ - public function executeQuery($query, array $params = [], $types = [], ?QueryCacheProfile $qcp = null) - { + public function executeQuery( + string $sql, + array $params = [], + $types = [], + ?QueryCacheProfile $qcp = null + ): Result { throw new Exception('Mocked method. Unable to be used'); } /** - * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters - * and returns the number of affected rows. - * - * This method supports PDO binding types as well as DBAL mapping types. - * - * @param string $query the SQL query - * @param mixed[] $params the query parameters - * @param int[]|string[] $types the parameter types + * BC layer for a wide-spread use-case of old DBAL APIs. * - * @return int the number of affected rows + * @deprecated This API is deprecated and will be removed after 2022 * - * @throws DBALException + * @param array $params The query parameters + * @param array $types The parameter types */ - public function executeUpdate($query, array $params = [], array $types = []) + public function executeUpdate(string $sql, array $params = [], array $types = []): int { throw new Exception('Mocked method. Unable to be used'); } diff --git a/src/Mock/MockedDriver.php b/src/Mock/MockedDriver.php index dee8e49..7d41514 100644 --- a/src/Mock/MockedDriver.php +++ b/src/Mock/MockedDriver.php @@ -17,6 +17,9 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Driver\API\ExceptionConverter; +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\AbstractSchemaManager; use Exception; /** @@ -41,9 +44,12 @@ public function getDatabasePlatform() } /** - * {@inheritdoc} + * Gets the SchemaManager that can be used to inspect and change the underlying + * database schema of the platform this driver connects to. + * + * @return AbstractSchemaManager */ - public function getSchemaManager(Connection $conn) + public function getSchemaManager(Connection $conn, AbstractPlatform $platform) { throw new Exception('Mocked method. Unable to be used'); } @@ -63,4 +69,12 @@ public function getDatabase(Connection $conn) { throw new Exception('Mocked method. Unable to be used'); } + + /** + * {@inheritdoc} + */ + public function getExceptionConverter(): ExceptionConverter + { + throw new Exception('Mocked method. Unable to be used'); + } } diff --git a/tests/ConnectionTest.php b/tests/ConnectionTest.php index 4967800..8bd9162 100644 --- a/tests/ConnectionTest.php +++ b/tests/ConnectionTest.php @@ -15,17 +15,17 @@ namespace Drift\DBAL\Tests; +use function Clue\React\Block\await; use Doctrine\DBAL\Exception\InvalidArgumentException; use Doctrine\DBAL\Exception\TableExistsException; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Drift\DBAL\Connection; use Drift\DBAL\Result; -use function Clue\React\Block\await; -use function React\Promise\all; use PHPUnit\Framework\TestCase; use React\EventLoop\Factory; use React\EventLoop\LoopInterface; +use function React\Promise\all; use React\Promise\PromiseInterface; /** diff --git a/tests/CredentialsTest.php b/tests/CredentialsTest.php index b8e89dc..d9ba08f 100644 --- a/tests/CredentialsTest.php +++ b/tests/CredentialsTest.php @@ -8,7 +8,7 @@ * * Feel free to edit as you please, and have fun. * - * @author David Llop + * @author Marc Morera */ declare(strict_types=1); diff --git a/tests/Mysql5ConnectionTest.php b/tests/Mysql5ConnectionTest.php index 00ef67f..5258bc4 100644 --- a/tests/Mysql5ConnectionTest.php +++ b/tests/Mysql5ConnectionTest.php @@ -15,7 +15,7 @@ namespace Drift\DBAL\Tests; -use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Platforms\MySQLPlatform; use Drift\DBAL\Connection; use Drift\DBAL\Credentials; use Drift\DBAL\Driver\Mysql\MysqlDriver; @@ -31,7 +31,7 @@ class Mysql5ConnectionTest extends ConnectionTest */ protected function getConnection(LoopInterface $loop): Connection { - $mysqlPlatform = new MySqlPlatform(); + $mysqlPlatform = new MySQLPlatform(); return Connection::createConnected(new MysqlDriver( $loop diff --git a/tests/PostgreSQLConnectionTest.php b/tests/PostgreSQLConnectionTest.php index e8eb2aa..a864ada 100644 --- a/tests/PostgreSQLConnectionTest.php +++ b/tests/PostgreSQLConnectionTest.php @@ -15,7 +15,7 @@ namespace Drift\DBAL\Tests; -use Doctrine\DBAL\Platforms\PostgreSqlPlatform; +use Doctrine\DBAL\Platforms\PostgreSQL94Platform; use Drift\DBAL\Connection; use Drift\DBAL\Credentials; use Drift\DBAL\Driver\PostgreSQL\PostgreSQLDriver; @@ -31,7 +31,7 @@ class PostgreSQLConnectionTest extends ConnectionTest */ public function getConnection(LoopInterface $loop): Connection { - $mysqlPlatform = new PostgreSqlPlatform(); + $postgreSQLPlatform = new PostgreSQL94Platform(); return Connection::createConnected(new PostgreSQLDriver($loop), new Credentials( '127.0.0.1', @@ -39,6 +39,6 @@ public function getConnection(LoopInterface $loop): Connection 'root', 'root', 'test' - ), $mysqlPlatform); + ), $postgreSQLPlatform); } }