Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions src/Driver/Mysqli/MysqlConnectionFailedException.php

This file was deleted.

38 changes: 0 additions & 38 deletions src/Driver/Mysqli/MysqlException.php

This file was deleted.

146 changes: 109 additions & 37 deletions src/Driver/Mysqli/Mysqli.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
Query\Generator\SQL,
Query\Query,
Query\QueryResult,
Query\UpdateQuery};
Query\UpdateQuery
};
use mysqli_result;
use mysqli_sql_exception;

/**
* Class Mysqli
Expand Down Expand Up @@ -75,6 +77,8 @@ class Mysqli extends Driver implements CRUDAbleInterface, CRUDQueryableInterface
*/
protected ?\mysqli $connection = null;

protected int $connectionRetries = 1;

/**
* Mysqli constructor.
*
Expand All @@ -97,41 +101,101 @@ public function __construct(?string $host = null, ?int $port = null, ?string $us

/**
* Connect to database
* @throws MysqlConnectionFailedException if connecting to the mysql database fails
* @throws MysqliConnectionException if connecting to the mysql database fails
*/
protected function connect(): void
{
if (!$this->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);
}
}
}

/**
* Save the model
*
* @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
{
Expand All @@ -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) . "'";
}
}

Expand All @@ -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) . "'";
}
}

Expand All @@ -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);
}
Expand All @@ -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);

Expand All @@ -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);

Expand All @@ -242,15 +304,15 @@ 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;
}

if (is_bool($rawQueryResult)) {
return $result;
}

while ($row = mysqli_fetch_assoc($rawQueryResult)) {
while ($row = $rawQueryResult->fetch_assoc()) {
/** @var class-string<ModelInterface> $modelClass */
$modelClass = $query->modelClassName;
$model = $modelClass::getModelFromData($row);
Expand All @@ -264,7 +326,7 @@ public function query(Query $query): QueryResult

/**
* @param string|null $host
* @return Mysqli
* @return $this
*/
public function setHost(?string $host): Mysqli
{
Expand All @@ -274,7 +336,7 @@ public function setHost(?string $host): Mysqli

/**
* @param int|null $port
* @return Mysqli
* @return $this
*/
public function setPort(?int $port): Mysqli
{
Expand All @@ -284,7 +346,7 @@ public function setPort(?int $port): Mysqli

/**
* @param string|null $username
* @return Mysqli
* @return $this
*/
public function setUsername(?string $username): Mysqli
{
Expand All @@ -294,7 +356,7 @@ public function setUsername(?string $username): Mysqli

/**
* @param string|null $password
* @return Mysqli
* @return $this
*/
public function setPassword(?string $password): Mysqli
{
Expand All @@ -304,7 +366,7 @@ public function setPassword(?string $password): Mysqli

/**
* @param string|null $socket
* @return Mysqli
* @return $this
*/
public function setSocket(?string $socket): Mysqli
{
Expand All @@ -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;
}
}
7 changes: 7 additions & 0 deletions src/Driver/Mysqli/MysqliConnectionException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Aternos\Model\Driver\Mysqli;

class MysqliConnectionException extends MysqliException
{
}
25 changes: 25 additions & 0 deletions src/Driver/Mysqli/MysqliException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php /** @noinspection PhpComposerExtensionStubsInspection */

namespace Aternos\Model\Driver\Mysqli;

use Aternos\Model\ModelException;
use mysqli;
use mysqli_sql_exception;
use Throwable;

class MysqliException extends ModelException
{
/**
* @param mysqli_sql_exception $exception
* @return static
*/
public static function fromException(mysqli_sql_exception $exception): static
{
return new static("MySQLi Exception #" . $exception->getCode() . ": " . $exception->getMessage(), $exception->getCode(), $exception);
}

public function __construct(string $message, int $code = 0, ?Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}
Loading
Loading