Skip to content

Commit

Permalink
no issue - error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
pounard committed Feb 26, 2024
1 parent 8840081 commit 232c7c5
Show file tree
Hide file tree
Showing 31 changed files with 1,607 additions and 5 deletions.
1 change: 1 addition & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export default defineConfig({
{ text: 'Doctrine DBAL setup', link: '/introduction/getting-started#doctrine-dbal-setup' },
{ text: 'PDO setup', link: '/introduction/getting-started#pdo-setup' },
{ text: 'Symfony setup', link: '/introduction/getting-started#symfony-setup' },
{ text: 'Error handling', link: '/bridges/error' },
]
},
{ text: 'Samples usages', link: '/introduction/usage' },
Expand Down
49 changes: 47 additions & 2 deletions src/Bridge/AbstractBridge.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,25 @@ abstract class AbstractBridge extends DefaultQueryBuilder implements Bridge
private ?string $serverVersion = null;
private bool $serverVersionLookekUp = false;
private ?string $serverFlavor = null;
private ?ErrorConverter $errorConverter = null;

public function __construct(?ConverterPluginRegistry $converterPluginRegistry = null)
{
$this->converterPluginRegistry = $converterPluginRegistry;
$this->setQueryExecutor($this);
}

/**
* Disable error converter. Must be called prior to initilization.
*/
public function disableErrorConverter(): bool
{
if ($this->errorConverter) {
throw new QueryBuilderError("Bridge is already initialized, configuration must happend before it gets used.");
}
$this->errorConverter = new PassthroughErrorConverter();

Check failure on line 53 in src/Bridge/AbstractBridge.php

View workflow job for this annotation

GitHub Actions / Static Analysis (8.2)

Method MakinaCorpus\QueryBuilder\Bridge\AbstractBridge::disableErrorConverter() should return bool but return statement is missing.
}

/**
* @internal
* For dependency injection only.
Expand Down Expand Up @@ -82,6 +94,31 @@ public function getServerName(): ?string
return $this->serverName;
}

/**
* Get error converter.
*/
protected function getErrorConverter(): ErrorConverter
{
return $this->errorConverter ??= $this->createErrorConverter();
}

/**
* @internal
* For dependency injection only.
*/
public function setErrorConverter(ErrorConverter $errorConverter): void
{
$this->errorConverter = $errorConverter;
}

/**
* Please override.
*/
protected function createErrorConverter(): ErrorConverter
{
return new PassthroughErrorConverter();
}

/**
* Please override.
*/
Expand Down Expand Up @@ -199,7 +236,11 @@ public function executeQuery(string|Expression $expression = null, mixed $argume

$prepared = $this->getWriter()->prepare($expression);

return $this->doExecuteQuery($prepared->toString(), $prepared->getArguments()->getAll());
try {
return $this->doExecuteQuery($prepared->toString(), $prepared->getArguments()->getAll());
} catch (\Throwable $e) {
throw $this->getErrorConverter()->convertError($e);
}
}

/**
Expand All @@ -223,7 +264,11 @@ public function executeStatement(string|Expression $expression = null, mixed $ar

$prepared = $this->getWriter()->prepare($expression);

return $this->doExecuteStatement($prepared->toString(), $prepared->getArguments()->getAll());
try {
return $this->doExecuteStatement($prepared->toString(), $prepared->getArguments()->getAll());
} catch (\Throwable $e) {
throw $this->getErrorConverter()->convertError($e);
}
}

/**
Expand Down
5 changes: 5 additions & 0 deletions src/Bridge/Bridge.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@

interface Bridge extends QueryExecutor, QueryBuilder
{
/**
* Disable error converter. Must be called prior to initilization.
*/
public function disableErrorConverter(): bool;

/**
* Get server name.
*/
Expand Down
12 changes: 11 additions & 1 deletion src/Bridge/Doctrine/DoctrineQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use MakinaCorpus\QueryBuilder\Platform;
use MakinaCorpus\QueryBuilder\Bridge\AbstractBridge;
use MakinaCorpus\QueryBuilder\Bridge\Doctrine\ErrorConverter\DoctrineErrorConverter;
use MakinaCorpus\QueryBuilder\Bridge\Doctrine\Escaper\DoctrineEscaper;
use MakinaCorpus\QueryBuilder\Bridge\Doctrine\Escaper\DoctrineMySQLEscaper;
use MakinaCorpus\QueryBuilder\Bridge\ErrorConverter;
use MakinaCorpus\QueryBuilder\Converter\Converter;
use MakinaCorpus\QueryBuilder\Error\QueryBuilderError;
use MakinaCorpus\QueryBuilder\Escaper\Escaper;
use MakinaCorpus\QueryBuilder\Platform;
use MakinaCorpus\QueryBuilder\Result\IterableResult;
use MakinaCorpus\QueryBuilder\Result\Result;
use MakinaCorpus\QueryBuilder\Writer\Writer;
Expand All @@ -30,6 +32,14 @@ public function __construct(
$this->connection = $connection;
}

/**
* Please override.
*/
protected function createErrorConverter(): ErrorConverter
{
return new DoctrineErrorConverter();
}

/**
* {@inheritdoc}
*/
Expand Down
95 changes: 95 additions & 0 deletions src/Bridge/Doctrine/ErrorConverter/DoctrineErrorConverter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

declare(strict_types=1);

namespace MakinaCorpus\QueryBuilder\Bridge\Doctrine\ErrorConverter;

use Doctrine\DBAL\ConnectionException;
use Doctrine\DBAL\Exception\ConnectionLost;
use Doctrine\DBAL\Exception\ConstraintViolationException;
use Doctrine\DBAL\Exception\DatabaseDoesNotExist;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\InvalidFieldNameException;
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\DBAL\Schema\Exception\ColumnDoesNotExist;
use Doctrine\DBAL\Schema\Exception\TableDoesNotExist;
use MakinaCorpus\QueryBuilder\Bridge\ErrorConverter;
use MakinaCorpus\QueryBuilder\Error\Bridge\AmbiguousIdentifierError;
use MakinaCorpus\QueryBuilder\Error\Bridge\ColumnDoesNotExistError;
use MakinaCorpus\QueryBuilder\Error\Bridge\ConstraintViolationError;
use MakinaCorpus\QueryBuilder\Error\Bridge\DatabaseObjectDoesNotExistError;
use MakinaCorpus\QueryBuilder\Error\Bridge\ForeignKeyConstraintViolationError;
use MakinaCorpus\QueryBuilder\Error\Bridge\NotNullConstraintViolationError;
use MakinaCorpus\QueryBuilder\Error\Bridge\TableDoesNotExistError;
use MakinaCorpus\QueryBuilder\Error\Bridge\UnableToConnectError;
use MakinaCorpus\QueryBuilder\Error\Bridge\UniqueConstraintViolationError;
use MakinaCorpus\QueryBuilder\Bridge\Pdo\ErrorConverter\PdoSQLiteErrorConverter;

class DoctrineErrorConverter implements ErrorConverter
{
/**
* {@inheritdoc}
*/
public function convertError(\Throwable $error, ?string $sql = null, ?string $message = null): \Throwable
{
$message ??= $error->getMessage();

if ($error instanceof InvalidFieldNameException || $error instanceof ColumnDoesNotExist) {
return new ColumnDoesNotExistError($message, $error->getCode(), $error);
}

if ($error instanceof DatabaseDoesNotExist) {
return new DatabaseObjectDoesNotExistError($message, $error->getCode(), $error);
}

if ($error instanceof ForeignKeyConstraintViolationException) {
return new ForeignKeyConstraintViolationError($message, $error->getCode(), $error);
}

if ($error instanceof NotNullConstraintViolationException) {
return new NotNullConstraintViolationError($message, $error->getCode(), $error);
}

if ($error instanceof TableDoesNotExist || $error instanceof TableNotFoundException) {
return new TableDoesNotExistError($message, $error->getCode(), $error);
}

/* if ($error instanceof Foo) {
return new TransactionDeadlockError();
} */

/* if ($error instanceof Foo) {
return new TransactionLockWaitTimeoutError();
} */

if ($error instanceof ConnectionException || $error instanceof ConnectionLost) {
return new UnableToConnectError($message, $error->getCode(), $error);
}

if ($error instanceof UniqueConstraintViolationException) {
return new UniqueConstraintViolationError($message, $error->getCode(), $error);
}

/*
* More generic errors after.
*/

if ($error instanceof NonUniqueFieldNameException) {
return new AmbiguousIdentifierError();
}

/* if ($error instanceof Foo) {
return new TransactionFailedError();
} */

if ($error instanceof ConstraintViolationException) {
return new ConstraintViolationError($message, $error->getCode(), $error);
}

// Provide fallbacks for SQLite, because DBAL don't catch them all.
return PdoSQLiteErrorConverter::createErrorFromMessage($error, $sql, $message);
}
}
20 changes: 20 additions & 0 deletions src/Bridge/ErrorConverter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace MakinaCorpus\QueryBuilder\Bridge;

interface ErrorConverter
{
/**
* Convert underlaying connector errors to a common error.
*
* @param \Throwable $error
* Original error from driver.
* @param null|string $sqlQuery
* The raw SQL query that is the subject of this error.
* @param null|string $message
* Overriden error message, if any.
*/
public function convertError(\Throwable $error, ?string $sql = null, ?string $message = null): \Throwable;

Check failure on line 19 in src/Bridge/ErrorConverter.php

View workflow job for this annotation

GitHub Actions / Static Analysis (8.2)

PHPDoc tag @param references unknown parameter: $sqlQuery
}
23 changes: 23 additions & 0 deletions src/Bridge/PassthroughErrorConverter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace MakinaCorpus\QueryBuilder\Bridge;

/**
* Implementation for when the error handling is disabled.
*
* Useful when configured over a doctrine/dbal bridge in Symfony context and
* you want the Query Builder to be absolutely transparently integrated with
* it. It's also true for other bridges contextes.
*/
class PassthroughErrorConverter implements ErrorConverter
{
/**
* {@inheritdoc}
*/
public function convertError(\Throwable $error, ?string $sql = null, ?string $message = null): \Throwable
{
return $error;
}
}
Loading

0 comments on commit 232c7c5

Please sign in to comment.