diff --git a/src/Driver/Mysqli/MysqlConnectionFailedException.php b/src/Driver/Mysqli/MysqlConnectionFailedException.php deleted file mode 100644 index d9efdd9..0000000 --- a/src/Driver/Mysqli/MysqlConnectionFailedException.php +++ /dev/null @@ -1,13 +0,0 @@ -getCode(), $previous); - } -} diff --git a/src/Driver/Mysqli/MysqlException.php b/src/Driver/Mysqli/MysqlException.php deleted file mode 100644 index 3a9d9c1..0000000 --- a/src/Driver/Mysqli/MysqlException.php +++ /dev/null @@ -1,38 +0,0 @@ -connection || !@mysqli_ping($this->connection)) { - $this->connection = mysqli_connect($this->host, $this->username, $this->password, $this->database, $this->port, $this->socket); - if (!$this->connection) { - throw new MysqlConnectionFailedException(new MysqlException($this->connection)); - } + if ($this->connection) { + return; + } + + mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); + try { + $this->connection = new \mysqli($this->host, $this->username, $this->password, $this->database, $this->port, $this->socket); + } catch (mysqli_sql_exception $e) { + throw MysqliConnectionException::fromException($e); } } + protected function reconnect(): void + { + $this->connection = null; + $this->connect(); + } + /** * Execute a mysql query * * @param string $query * @return bool|mysqli_result - * @throws MysqlConnectionFailedException if connecting to the mysql database fails - * @throws MysqlException if a mysql error occurs while executing the query + * @throws MysqliConnectionException if connecting to the mysql database fails + * @throws MysqliException if a mysql error occurs while executing the query */ protected function rawQuery(string $query): mysqli_result|true { $this->connect(); - $result = mysqli_query($this->connection, $query); - MysqlException::checkConnection($this->connection); - return $result; + $retries = $this->connectionRetries; + 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--; + continue; + } + + // other error, throw exception + throw MysqliException::fromException($e); + } + } + } + + /** + * Escape a string for use in a mysql query + * + * @param string $data + * @return string + */ + protected function escape(string $data): string + { + $this->connect(); + $retries = $this->connectionRetries; + 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--; + continue; + } + + // other error, throw exception + throw MysqliException::fromException($e); + } + } } /** @@ -130,8 +194,8 @@ protected function rawQuery(string $query): mysqli_result|true * * @param ModelInterface $model * @return bool - * @throws MysqlConnectionFailedException if connecting to the mysql database fails - * @throws MysqlException if a mysql error occurs while executing the query + * @throws MysqliConnectionException if connecting to the mysql database fails + * @throws MysqliException if a mysql error occurs while executing the query */ public function save(ModelInterface $model): bool { @@ -149,7 +213,7 @@ public function save(ModelInterface $model): bool } else if (is_null($value)) { $values[] = "NULL"; } else { - $values[] = "'" . mysqli_real_escape_string($this->connection, $value) . "'"; + $values[] = "'" . $this->escape($value) . "'"; } } @@ -160,7 +224,7 @@ public function save(ModelInterface $model): bool } else if (is_null($modelValue)) { $updates[] = "`" . $column . "`=NULL"; } else { - $updates[] = "`" . $column . "`='" . mysqli_real_escape_string($this->connection, $modelValue) . "'"; + $updates[] = "`" . $column . "`='" . $this->escape($modelValue) . "'"; } } @@ -177,22 +241,22 @@ public function save(ModelInterface $model): bool * @param mixed $id * @param ModelInterface|null $model * @return ModelInterface|null - * @throws MysqlConnectionFailedException if connecting to the mysql database fails - * @throws MysqlException if a mysql error occurs while executing the query + * @throws MysqliConnectionException if connecting to the mysql database fails + * @throws MysqliException if a mysql error occurs while executing the query */ public function get(string $modelClass, mixed $id, ?ModelInterface $model = null): ?ModelInterface { $this->connect(); $table = $modelClass::getName(); - $escapedId = mysqli_real_escape_string($this->connection, $id); + $escapedId = $this->escape($id); $query = "SELECT * FROM `" . $table . "` WHERE `" . $modelClass::getIdField() . "` = '" . $escapedId . "'"; $result = $this->rawQuery($query); - if (!$result || mysqli_num_rows($result) === 0) { + if (!$result || $result->num_rows === 0) { return null; } - $row = mysqli_fetch_assoc($result); + $row = $result->fetch_assoc(); if ($model) { return $model->applyData($row); } @@ -204,15 +268,15 @@ public function get(string $modelClass, mixed $id, ?ModelInterface $model = null * * @param ModelInterface $model * @return bool - * @throws MysqlConnectionFailedException if connecting to the mysql database fails - * @throws MysqlException if a mysql error occurs while executing the query + * @throws MysqliConnectionException if connecting to the mysql database fails + * @throws MysqliException if a mysql error occurs while executing the query */ public function delete(ModelInterface $model): bool { $this->connect(); $table = $model::getName(); - $id = mysqli_real_escape_string($this->connection, $model->getId()); + $id = $this->escape($model->getId()); $query = "DELETE FROM `" . $table . "` WHERE `" . $model->getIdField() . "` = '" . $id . "'"; $this->rawQuery($query); @@ -224,16 +288,14 @@ public function delete(ModelInterface $model): bool * * @param Query $query * @return QueryResult - * @throws MysqlConnectionFailedException if connecting to the mysql database fails - * @throws MysqlException if a mysql error occurs while executing the query + * @throws MysqliConnectionException if connecting to the mysql database fails + * @throws MysqliException if a mysql error occurs while executing the query */ public function query(Query $query): QueryResult { $this->connect(); - $generator = new SQL(function ($value) { - return mysqli_real_escape_string($this->connection, $value); - }); + $generator = new SQL($this->escape(...)); $queryString = $generator->generate($query); @@ -242,7 +304,7 @@ public function query(Query $query): QueryResult $result = new QueryResult(); $result->setQueryString($queryString); if ($query instanceof UpdateQuery || $query instanceof DeleteQuery) { - $result->setAffectedRows(mysqli_affected_rows($this->connection)); + $result->setAffectedRows($this->connection->affected_rows); return $result; } @@ -250,7 +312,7 @@ public function query(Query $query): QueryResult return $result; } - while ($row = mysqli_fetch_assoc($rawQueryResult)) { + while ($row = $rawQueryResult->fetch_assoc()) { /** @var class-string $modelClass */ $modelClass = $query->modelClassName; $model = $modelClass::getModelFromData($row); @@ -264,7 +326,7 @@ public function query(Query $query): QueryResult /** * @param string|null $host - * @return Mysqli + * @return $this */ public function setHost(?string $host): Mysqli { @@ -274,7 +336,7 @@ public function setHost(?string $host): Mysqli /** * @param int|null $port - * @return Mysqli + * @return $this */ public function setPort(?int $port): Mysqli { @@ -284,7 +346,7 @@ public function setPort(?int $port): Mysqli /** * @param string|null $username - * @return Mysqli + * @return $this */ public function setUsername(?string $username): Mysqli { @@ -294,7 +356,7 @@ public function setUsername(?string $username): Mysqli /** * @param string|null $password - * @return Mysqli + * @return $this */ public function setPassword(?string $password): Mysqli { @@ -304,7 +366,7 @@ public function setPassword(?string $password): Mysqli /** * @param string|null $socket - * @return Mysqli + * @return $this */ public function setSocket(?string $socket): Mysqli { @@ -314,11 +376,21 @@ public function setSocket(?string $socket): Mysqli /** * @param string $database - * @return Mysqli + * @return $this */ 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/MysqliConnectionException.php b/src/Driver/Mysqli/MysqliConnectionException.php new file mode 100644 index 0000000..92507af --- /dev/null +++ b/src/Driver/Mysqli/MysqliConnectionException.php @@ -0,0 +1,7 @@ +getCode() . ": " . $exception->getMessage(), $exception->getCode(), $exception); + } + + public function __construct(string $message, int $code = 0, ?Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/src/Driver/Redis/Exception/RedisConnectionException.php b/src/Driver/Redis/Exception/RedisConnectionException.php index 69b6d89..8b693d8 100644 --- a/src/Driver/Redis/Exception/RedisConnectionException.php +++ b/src/Driver/Redis/Exception/RedisConnectionException.php @@ -15,7 +15,7 @@ class RedisConnectionException extends RedisModelException * @param Throwable $exception * @return static */ - static function wrapping(Throwable $exception): static + static function fromException(Throwable $exception): static { return new static($exception->getMessage(), $exception->getCode(), $exception); } diff --git a/src/Driver/Redis/Redis.php b/src/Driver/Redis/Redis.php index bf56a63..ddea7a9 100644 --- a/src/Driver/Redis/Redis.php +++ b/src/Driver/Redis/Redis.php @@ -83,7 +83,7 @@ protected function connect(): void $this->connection->connect($this->socket); } } catch (RedisException $e) { - throw RedisConnectionException::wrapping($e); + throw RedisConnectionException::fromException($e); } } } @@ -119,7 +119,7 @@ public function save(ModelInterface $model): bool $this->connection->set($key, json_encode($model), $model->getCacheTime()); RedisQueryException::checkConnection($this->connection); } catch (RedisException $e) { - throw RedisConnectionException::wrapping($e); + throw RedisConnectionException::fromException($e); } return true; } @@ -144,7 +144,7 @@ public function get(string $modelClass, mixed $id, ?ModelInterface $model = null $rawData = $this->connection->get($this->generateCacheKey($modelClass, $id)); RedisQueryException::checkConnection($this->connection); } catch (RedisException $e) { - throw RedisConnectionException::wrapping($e); + throw RedisConnectionException::fromException($e); } if (!$rawData) { @@ -181,7 +181,7 @@ public function delete(ModelInterface $model): bool $this->connection->del($key); RedisQueryException::checkConnection($this->connection); } catch (RedisException $e) { - throw RedisConnectionException::wrapping($e); + throw RedisConnectionException::fromException($e); } return true; }