diff --git a/src/Driver/Mysqli/Mysqli.php b/src/Driver/Mysqli/Mysqli.php index 0d66a41..fc1a1c8 100644 --- a/src/Driver/Mysqli/Mysqli.php +++ b/src/Driver/Mysqli/Mysqli.php @@ -28,6 +28,10 @@ class Mysqli extends Driver implements CRUDAbleInterface, CRUDQueryableInterface { public const string ID = "mysqli"; + + /** @var int[] */ + protected const array CONNECTION_ERROR_CODES = [2006, 2013]; + protected string $id = self::ID; /** @@ -77,8 +81,6 @@ class Mysqli extends Driver implements CRUDAbleInterface, CRUDQueryableInterface */ protected ?\mysqli $connection = null; - protected int $connectionRetries = 1; - /** * Mysqli constructor. * @@ -123,6 +125,17 @@ protected function reconnect(): void $this->connect(); } + /** + * @return RetryCollection + */ + protected function getRetryCollection(): RetryCollection + { + return new RetryCollection([ + new RetryGroup(static::CONNECTION_ERROR_CODES, 1), // connection + new RetryGroup([1213], 3) // deadlock + ]); + } + /** * Execute a mysql query * @@ -134,24 +147,19 @@ protected function reconnect(): void protected function rawQuery(string $query): mysqli_result|true { $this->connect(); - $retries = $this->connectionRetries; + $retries = $this->getRetryCollection(); while (true) { try { return $this->connection->query($query); } catch (mysqli_sql_exception $e) { - // no more retries left - if ($retries <= 0) { - throw MysqliException::fromException($e); - } - - // connection error, try to reconnect and retry - if ($e->getCode() === 2006 || $e->getCode() === 2013) { - $this->reconnect(); - $retries--; + if ($retries->canRetry($e->getCode())) { + if (in_array($e->getCode(), static::CONNECTION_ERROR_CODES, true)) { + $this->reconnect(); + } + $this->handleRetriedException($e); continue; } - // other error, throw exception throw MysqliException::fromException($e); } } @@ -166,29 +174,33 @@ protected function rawQuery(string $query): mysqli_result|true protected function escape(string $data): string { $this->connect(); - $retries = $this->connectionRetries; + $retries = $this->getRetryCollection(); while (true) { try { return $this->connection->real_escape_string($data); } catch (mysqli_sql_exception $e) { - // no more retries left - if ($retries <= 0) { - throw MysqliException::fromException($e); - } - - // connection error, try to reconnect and retry - if ($e->getCode() === 2006 || $e->getCode() === 2013) { - $this->reconnect(); - $retries--; + if ($retries->canRetry($e->getCode())) { + if (in_array($e->getCode(), static::CONNECTION_ERROR_CODES, true)) { + $this->reconnect(); + } + $this->handleRetriedException($e); continue; } - // other error, throw exception throw MysqliException::fromException($e); } } } + /** + * @param mysqli_sql_exception $exception + * @return void + */ + protected function handleRetriedException(mysqli_sql_exception $exception): void + { + // can be used to log retried exceptions + } + /** * Save the model * @@ -383,14 +395,4 @@ public function setDatabase(string $database): Mysqli $this->database = $database; return $this; } - - /** - * @param int $connectionRetries - * @return $this - */ - public function setConnectionRetries(int $connectionRetries): static - { - $this->connectionRetries = $connectionRetries; - return $this; - } } diff --git a/src/Driver/Mysqli/RetryCollection.php b/src/Driver/Mysqli/RetryCollection.php new file mode 100644 index 0000000..4e9a60c --- /dev/null +++ b/src/Driver/Mysqli/RetryCollection.php @@ -0,0 +1,50 @@ +groups[] = $group; + return $this; + } + + /** + * @param int[] $statusCodes + * @return $this + */ + public function removeGroup(array $statusCodes): static + { + $this->groups = array_values(array_filter($this->groups, function (RetryGroup $group) use ($statusCodes) { + return !$group->matchesStatusCodes($statusCodes); + })); + return $this; + } + + /** + * @param int $statusCode + * @return bool + */ + public function canRetry(int $statusCode): bool + { + foreach ($this->groups as $group) { + if ($group->matchesStatusCode($statusCode) && $group->hasRetriesLeft()) { + $group->addRetry(); + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/src/Driver/Mysqli/RetryGroup.php b/src/Driver/Mysqli/RetryGroup.php new file mode 100644 index 0000000..0f4310e --- /dev/null +++ b/src/Driver/Mysqli/RetryGroup.php @@ -0,0 +1,74 @@ +maxRetries; + } + + /** + * @return void + */ + public function addRetry(): void + { + $this->retries++; + } + + /** + * @return bool + */ + public function hasRetriesLeft(): bool + { + return $this->retries < $this->maxRetries; + } + + /** + * @return int[] + */ + public function getStatusCodes(): array + { + return $this->statusCodes; + } + + /** + * @param int $statusCode + * @return bool + */ + public function matchesStatusCode(int $statusCode): bool + { + return in_array($statusCode, $this->statusCodes); + } + + /** + * @param int[] $statusCodes + * @return bool + */ + public function matchesStatusCodes(array $statusCodes): bool + { + foreach ($statusCodes as $statusCode) { + if ($this->matchesStatusCode($statusCode)) { + return true; + } + } + return false; + } +} \ No newline at end of file