diff --git a/.gitignore b/.gitignore
index c7b2739..e1118fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2220,3 +2220,4 @@ FodyWeavers.xsd
/composer.lock
/vendor/goodboyalex/php_components_pack/
/.idea/php-test-framework.xml
+/.idea/inspectionProfiles/Project_Default.xml
diff --git a/sources/classes/ConditionBuilder.php b/sources/classes/ConditionBuilder.php
index ccd7e39..7de805a 100644
--- a/sources/classes/ConditionBuilder.php
+++ b/sources/classes/ConditionBuilder.php
@@ -194,9 +194,10 @@
}
/**
- * Собирает условие в виде строки.
+ * Собирает условие в виде пригодном для SQL. Также возвращает массив параметров защиты от SQL-инъекций.
*
- * @return string Возвращает условие в виде, пригодном для SQL.
+ * @return Tuple (string, array) Возвращает условие в виде, пригодном для SQL и массив параметров для защиты от
+ * SQL-инъекций.
*/
public function Build (): Tuple
{
diff --git a/sources/classes/Database.php b/sources/classes/Database.php
index 1264817..e50ddc7 100644
--- a/sources/classes/Database.php
+++ b/sources/classes/Database.php
@@ -9,11 +9,13 @@
use Closure;
use goodboyalex\php_db_components_pack\enums\DBDriver;
use goodboyalex\php_db_components_pack\models\DBConfig;
+ use goodboyalex\php_db_components_pack\traits\Database\DatabaseCountExist;
use goodboyalex\php_db_components_pack\traits\Database\DatabaseGet;
use goodboyalex\php_db_components_pack\traits\Database\DatabaseInsert;
use goodboyalex\php_db_components_pack\traits\Database\DatabaseQueryExecute;
use goodboyalex\php_db_components_pack\traits\Database\DatabaseSpecial;
use goodboyalex\php_db_components_pack\traits\Database\DatabaseTransactions;
+ use goodboyalex\php_db_components_pack\traits\Database\DatabaseUpdate;
use PDO;
use PDOException;
@@ -23,7 +25,7 @@
* Используется класс PDO для подключения к базе данных.
*
* @author Александр Бабаев
- * @package php_components_pack
+ * @package php_db_components_pack
* @version 1.0
* @since 1.0
* @see PDO
@@ -47,7 +49,9 @@
private string $DBSignClose;
/**
- * @var Closure $OnException Обработчик исключений.
+ * @var Closure $OnException Обработчик исключений. Анонимная функция формата:
+ *
+ * function (Exception $e, bool $isTerminate): void
*/
private Closure $OnException;
@@ -57,10 +61,12 @@
private DBConfig $Config;
/**
- * Конструктор. Подключает базу данных
+ * Конструктор.
*
* @param DBConfig $config Конфигурация подключения к базе данных.
- * @param callable $onException Обработчик исключений.
+ * @param callable $onException Обработчик исключений. Анонимная функция формата:
+ *
+ * function (Exception $e, bool $isTerminate): void
*/
public function __construct (DBConfig $config, callable $onException)
{
@@ -126,40 +132,6 @@
$this->DataBaseHandle = null;
}
- /**
- * Получает набор строк в массиве данных, удовлетворяющий выборке
- *
- * @param string $table Имя таблицы
- * @param array $columns Колонки, которые нужно включить в запрос
- * @param array $where Параметры выборки
- *
- * @return false|array Строка в формате массива или false в случае ошибки
- *
- * @see Query
- * @see QueryFirst
- * @see QueryLast
- * @see GetRow
- */
- public function GetRows (string $table, array $columns = [], array $where = []): false|array
- {
- // Задаю массив параметров
- $params = [];
-
- // Получаю SQL запрос
- $sql = $this->PrepareSQLForRowsQuery($table, $columns, $where, $params);
-
- // Получаю строки на основании запроса
- $queryResult = $this->Query($sql, $params);
-
- // Если строки не получены
- if ($queryResult === false)
- // - то выдаю ошибку
- return false;
-
- // Получаю значение строк
- return $queryResult;
- }
-
// Транзакции
use DatabaseTransactions;
@@ -172,220 +144,15 @@
// Получение данных
use DatabaseGet;
+ // Проверка существования и получение количества строк
+ use DatabaseCountExist;
+
+ // Обновление записей
+ use DatabaseUpdate;
+
// Приватные методы
use DatabaseSpecial;
-
-
- /**
- * Получает колонку в массиве данных
- *
- * @param string $table Имя таблицы
- * @param string $column Имя колонки
- * @param array $where Параметры запроса
- *
- * @return false|array Ассоциированный массив с результатом запроса или false в случае ошибки
- *
- * @see Query
- */
- public function GetCol (string $table, string $column, array $where = []): false|array
- {
- // Задаю параметры
- $params = [];
-
- // Строковая интерпретация массива условий
- $sql_where = $this->PrepareQueryWhere($where, $params);
-
- // Создаю запрос
- $sql = "SELECT $this->DBSignOpen$column$this->DBSignClose FROM $this->DBSignOpen$table$this->DBSignClose";
-
- // Если заданы where-параметры
- if (count($where) > 0) {
- // - то добавляю их
- $sql .= " WHERE $sql_where";
- }
-
- // Получаю столбец на основании запроса
- $queryResult = $this->Query($sql, $params);
-
- // Если строка не получена или пуста
- if ($queryResult === false)
- // - то выдаю ошибку
- return false;
-
- // Создаю результат
- $result = [];
-
- // Для каждого результата запроса
- foreach ($queryResult as $row)
- // - передаю его в результат
- $result[] = $row[$column];
-
- // Вывожу результат
- return $result;
- }
-
- /**
- * Получение значение единичного поля
- *
- * @param string $table Имя таблицы
- * @param string $column Требуемый столбец
- * @param array $where Параметры запроса
- *
- * @return mixed|null Результат запроса или null в случае ошибки
- */
- public function GetValue (string $table, string $column, array $where = []): mixed
- {
- // Задаю параметры
- $params = [];
-
- // Строковая интерпретация массива условий
- $sql_where = $this->PrepareQueryWhere($where, $params);
-
- // Создаю запрос
- $sql = "SELECT $this->DBSignOpen$column$this->DBSignClose FROM $this->DBSignOpen$table$this->DBSignClose";
-
- // Если заданы where-параметры
- if (count($where) > 0) {
- // - то добавляю их
- $sql .= " WHERE $sql_where";
- }
-
- // Получаю строку на основании запроса
- $queryResult = $this->QueryScalar($sql, $params);
-
- // Если строка не получена или пуста
- if ($queryResult === false || count($queryResult) == 0)
- // - то выдаю результат null
- return null;
-
- // Получаю значение колонки
- return $queryResult[$column];
- }
-
- /**
- * Заменяет данные в строке базы данных
- *
- * @param string $table Имя таблицы
- * @param array $set Массив данных для замены
- * @param array $where Массив условий
- *
- * @return bool Результат выполнения
- */
- public function Update (string $table, array $set, array $where = []): bool
- {
- // Создаю массив параметров
- $params_set = [];
-
- // Строковая интерпретация массива для изменения
- $sql_set = "";
-
- // Для каждых данных для изменения
- foreach ($set as $key => $value) {
- // - получаю ключ 100%-но без ":" в начале
- $set_key = $key[0] == ":" ? substr($key, 1) : $key;
-
- // - добавляю префикс для 2 или более итерации
- $prefix = $sql_set == "" ? "" : ", ";
-
- // - добавляю данные в sql_set
- $sql_set .= "$prefix$this->DBSignOpen$set_key$this->DBSignClose=:$set_key";
-
- // - добавляю данные в параметры
- $params_set[":" . $set_key] = $value;
- }
-
- // Обработанные параметры
- $params_where = [];
-
- // Строковая интерпретация массива условий
- $sql_where = $this->PrepareQueryWhere(where: $where, params: $params_where);
-
- // Создаю параметры
- $params = array_merge($params_set, $params_where);
-
- // Создаю запрос
- $sql = "UPDATE $this->DBSignOpen$table$this->DBSignClose SET $sql_set";
-
- // Если заданы where-параметры
- if (count($where) > 0)
- // - то добавляю их
- $sql .= " WHERE $sql_where";
-
- // Выполняю запрос
- $count = $this->Execute($sql, $params);
-
- // Если результат - false
- if ($count === false)
- // - то и общий результат - false
- return false;
-
- // Если изменено 0 строк
- if ($count === 0)
- // - то и общий результат - false
- return false;
-
- // Вывожу результат -- успех
- return true;
- }
-
- /**
- * Проверяет, существует ли запись в таблице.
- *
- * @param string $table Имя таблицы
- * @param array $where Массив условий
- *
- * @return bool Результат проверки
- */
- public function IsExist (string $table, array $where = []): bool
- {
- // Вывожу результат
- return $this->Count($table, $where) > 0;
- }
-
- /**
- * Подсчитывает количество строк, удовлетворяющих условию.
- *
- * @param string $table Имя таблицы
- * @param array $where Массив условий выборки
- *
- * @return int Количество строк или -1, в случае ошибки
- */
- public function Count (string $table, array $where = []): int
- {
- // Параметры
- $params = [];
-
- // Строковая интерпретация массива условий
- $sql_where = $this->PrepareQueryWhere($where, $params);
-
- // Создаю запрос
- $sql = "SELECT COUNT(*) FROM $this->DBSignOpen$table$this->DBSignClose";
-
- // Если заданы where-параметры
- if (count($where) > 0) {
- // - то добавляю их
- $sql .= ' WHERE ' . $sql_where;
- }
-
- // Выполняю запрос
- $countResult = $this->Query($sql, $params);
-
- // Если запрос выполнен с ошибкой
- if ($countResult === false)
- // - то в результат идёт -1
- return -1;
-
- // Получаю секцию
- $section = match ($this->Config->Driver) {
- DBDriver::MySQL, DBDriver::SQLite => "COUNT(*)",
- DBDriver::MSSQL, DBDriver::OracleDB, DBDriver::PostgreSQL => ""
- };
-
- // Вывожу количество
- return isset($countResult[0][$section]) ? (int)$countResult[0][$section] : -1;
- }
-
/**
* Удаляет строки по условию.
*
diff --git a/sources/traits/Database/DatabaseCountExist.php b/sources/traits/Database/DatabaseCountExist.php
new file mode 100644
index 0000000..a994f2b
--- /dev/null
+++ b/sources/traits/Database/DatabaseCountExist.php
@@ -0,0 +1,80 @@
+Build();
+
+ // Создаю запрос
+ $sql = "SELECT COUNT(*) FROM $this->DBSignOpen$table$this->DBSignClose";
+
+ // Если заданы where-параметры
+ if ($where->Count() > 0)
+ // - то добавляю их
+ $sql .= ' WHERE ' . $sql_where;
+
+ // Выполняю запрос
+ $countResult = $this->Query($sql, $params);
+
+ // Если запрос выполнен с ошибкой
+ if ($countResult === false)
+ // - то в результат идёт -1
+ return -1;
+
+ // Получаю секцию
+ $section = match ($this->Config->Driver) {
+ DBDriver::MySQL, DBDriver::SQLite => "COUNT(*)",
+ DBDriver::MSSQL, DBDriver::OracleDB, DBDriver::PostgreSQL => ""
+ };
+
+ // Вывожу количество
+ return isset($countResult[0][$section]) ? (int)$countResult[0][$section] : -1;
+ }
+
+ /**
+ * Проверяет, существует ли запись в таблице.
+ *
+ * @param string $table Имя таблицы.
+ * @param ConditionBuilder $where Условия выборки.
+ *
+ * @return bool Результат проверки.
+ */
+ public function IsExist (string $table, ConditionBuilder $where): bool
+ {
+ // Вывожу результат
+ return $this->Count($table, $where) > 0;
+ }
+ }
\ No newline at end of file
diff --git a/sources/traits/Database/DatabaseGet.php b/sources/traits/Database/DatabaseGet.php
index 339e9de..ab6c871 100644
--- a/sources/traits/Database/DatabaseGet.php
+++ b/sources/traits/Database/DatabaseGet.php
@@ -1,20 +1,20 @@
false в случае ошибки.
*/
- public function GetRowColumns (string $table, array $columns, ConditionBuilder $where): false|array
+ public function GetRow (string $table, ConditionBuilder $where, array $columns = [],
+ string $className = "\\StdClass"): object|false
+ {
+ // Задаю массив параметров
+ $params = [];
+
+ // Формируем SQL-запрос
+ $sql = $this->PrepareSQLForRowsQuery($table, $columns, $where, $params);
+
+ // Добавляю лимит
+ $sql .= " LIMIT 1";
+
+ // Получаю строку
+ $row = $this->Query($sql, $params);
+
+ // Если не получено
+ if ($row === false)
+ // - то вывожу false
+ return false;
+
+ // Получаю объект
+ $item = $this->RestoreItem($row, $className, DBOperation::Get);
+
+ // Если при получении возникла ошибка
+ if ($item === null)
+ // - то возвращаю false
+ return false;
+
+ // Возвращаю элемент
+ return $item;
+ }
+
+ /**
+ * Получает набор строк в массиве данных, удовлетворяющий выборке.
+ *
+ * @param string $table Имя таблицы.
+ * @param ConditionBuilder $where Where-условия выборки.
+ * @param array $columns Колонки, которые нужно включить в запрос.
+ * @param string $className Полное имя класса, реализуемого интерфейсом IDBItem.
+ *
+ * @return false|ObjectArray Массив найденных классов или false
в случае ошибки.
+ */
+ public function GetRows (string $table, ConditionBuilder $where, array $columns = [],
+ string $className = "\\StdClass"): false|ObjectArray
{
// Задаю массив параметров
$params = [];
@@ -44,78 +82,99 @@
// Получаю SQL запрос
$sql = $this->PrepareSQLForRowsQuery($table, $columns, $where, $params);
- // Получаю строку на основании запроса
- return $this->QueryScalar($sql, $params);
+ // Получаю строки на основании запроса
+ $queryResult = $this->Query($sql, $params);
+
+ // Если строки не получены
+ if ($queryResult === false)
+ // - то выдаю ошибку
+ return false;
+
+ // Создаю массив объектов
+ $result = new ObjectArray();
+
+ // Для каждого элемента
+ foreach ($queryResult as $row) {
+ // - пытаюсь восстановить объект
+ $item = $this->RestoreItem($row, $className, DBOperation::Get);
+
+ // - если не получилось
+ if ($item === null)
+ // -- то пропускаю элемент
+ continue;
+
+ // - добавляю элемент в массив
+ $result[] = $item;
+ }
+
+ // Выдаю массив объектов
+ return $result;
}
/**
- * Извлекает одну запись из базы данных и создает соответствующий объект класса
+ * Получает колонку в массиве данных.
*
- * @param string $table Название таблицы
- * @param ConditionBuilder $condition Условия выборки
- * @param string $className Полное имя класса, реализуемого интерфейсом IDBItem
+ * @param string $table Имя таблицы.
+ * @param string $column Имя колонки.
+ * @param ConditionBuilder $where Параметры запроса.
*
- * @return object Заполненный объект класса
+ * @return false|array Ассоциированный массив с результатом запроса или false в случае ошибки.
+ *
+ * @see Query
*/
- public function GetRow (string $table, ConditionBuilder $condition, string $className): object
+ public function GetCol (string $table, string $column, ConditionBuilder $where): false|array
{
- try {
- // Строим условие WHERE для SQL-запроса
- $whereClause = $condition->Build();
-
- // Формируем SQL-запрос
- $sql = "SELECT * FROM $table WHERE $whereClause LIMIT 1;";
-
- // Подготовленное выражение
- $stmt = $this->pdo->prepare($sql);
-
- // Присваивание параметров
- foreach ($params as $col => $val) {
- $stmt->bindParam(":$col", $val);
- }
-
- // Выполнение запроса
- $stmt->execute();
-
- // Получаем первую строку результатов
- $row = $stmt->fetch(PDO::FETCH_ASSOC);
-
- if (!$row) {
- throw new Exception("No data found with the given conditions.");
- }
-
- // Создание объекта желаемого класса
- $obj = new $className();
-
- // Заполняем поля объекта соответствующими значениями из БД
- foreach ($row as $column => $value) {
- // Анализируем свойства класса и находим соответствующее свойство
- $properties = (new ReflectionClass($obj))->getProperties(ReflectionProperty::IS_PUBLIC);
- foreach ($properties as $prop) {
- // Проверяем, соответствует ли данное свойство указанному полю
- $attributes = $prop->getAttributes(FieldName::class);
- if ($attributes && $attributes[0]->getArguments()['name'] === $column) {
- // Применяем процедуру конвертации (если указана)
- $convertAttrs = $prop->getAttributes(ConvertToDB::class);
- if ($convertAttrs) {
- $converter = $convertAttrs[0]->getArguments()['fromDb'];
- if ($converter !== null) {
- $value = call_user_func($converter, $value);
- }
- }
-
- // Устанавливаем значение свойства
- $obj->{$prop->getName()} = $value;
- break;
- }
- }
- }
-
- return $obj;
- }
- catch (\PDOException|\Exception $e) {
- error_log("Error fetching data: " . $e->getMessage());
- return null;
- }
+ /**
+ * Интерпретирую условия.
+ *
+ * @var string $sql_where Строка запроса.
+ * @var array $params Массив параметров строки запроса.
+ */
+ [$sql_where, $params] = $where->Build();
+
+ // Создаю запрос
+ $sql = "SELECT $this->DBSignOpen$column$this->DBSignClose FROM $this->DBSignOpen$table$this->DBSignClose";
+
+ // Если заданы where-параметры
+ if ($where->Count() > 0)
+ // - то добавляю их
+ $sql .= " WHERE $sql_where";
+
+ // Получаю столбец на основании запроса
+ $queryResult = $this->Query($sql, $params);
+
+ // Если строка не получена или пуста
+ if ($queryResult === false)
+ // - то выдаю ошибку
+ return false;
+
+ // Создаю результат
+ $result = [];
+
+ // Для каждого результата запроса
+ foreach ($queryResult as $row)
+ // - передаю его в результат
+ $result[] = $row[$column];
+
+ // Вывожу результат
+ return $result;
+ }
+
+ /**
+ * Получение значение единичного поля.
+ *
+ * @param string $table Имя таблицы.
+ * @param string $column Требуемый столбец.
+ * @param ConditionBuilder $where Параметры запроса.
+ *
+ * @return mixed|null Результат запроса или null
в случае ошибки.
+ */
+ public function GetValue (string $table, string $column, ConditionBuilder $where): mixed
+ {
+ // Получаю колонку по условию из таблицы
+ $result = $this->GetCol($table, $column, $where);
+
+ // Если результат получен, то выдаю первый элемент, а если нет, то null
+ return $result[0] ?? null;
}
}
\ No newline at end of file
diff --git a/sources/traits/Database/DatabaseQueryExecute.php b/sources/traits/Database/DatabaseQueryExecute.php
index 22570f2..03541a3 100644
--- a/sources/traits/Database/DatabaseQueryExecute.php
+++ b/sources/traits/Database/DatabaseQueryExecute.php
@@ -6,6 +6,7 @@
namespace goodboyalex\php_db_components_pack\traits\Database;
+ use Exception;
use PDO;
use PDOException;
@@ -13,7 +14,7 @@
* Трейт для работы с запросами к базе данных типа Query и Execute.
*
* @author Александр Бабаев
- * @package php_components_pack
+ * @package php_db_components_pack
* @version 1.0
* @since 1.0
* @see PDO
@@ -21,162 +22,138 @@
trait DatabaseQueryExecute
{
/**
- * Запрос строк из базы данных.
+ * Выполнить запрос к базе данных и вернуть ассоциированный массив.
*
- * @param string $query Запрос
- * @param array $params Параметры запроса
+ * @param string $query Запрос SQL.
+ * @param array $params Параметры запроса.
*
- * @return false|array Ассоциированный массив с результатом запроса или false в случае ошибки
+ * @return array|false Ассоциативный массив с результатом запроса или false
в случае ошибки.
*/
- public function Query (string $query, array $params = []): false|array
+ public function Query (string $query, array $params = []): array|false
{
- // По умолчанию, результат пуст
- $result = false;
+ // Проверяем, что это именно запрос SELECT
+ if (!preg_match('/^\s*SELECT\s+/i', trim($query))) {
+ $this->HandleException(new Exception("Некорректный запрос для метода Query() / Incorrect request for the Query() method: '$query'. Используйте метод Execute() для небезопасных операций / Use the Execute() method for unsafe operations."),
+ false);
+ return false;
+ }
+
+ // Подготавливаем запрос
+ $stmt = $this->DataBaseHandle->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY]);
+
+ // Если запрос не подготовлен
+ if ($stmt === false) {
+ // - обрабатываю ошибку
+ $this->HandleException(new Exception("Ошибка подготовки SQL-запроса / SQL query preparation error"),
+ false);
+
+ // - прерываю выполнение
+ return false;
+ }
try {
- // Подготавливаю запрос
- $STH = $this->DataBaseHandle->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY]);
-
- // Выполняю запрос
- $STH->execute($params);
-
- // Указываю, что данные, которые я хочу получить, должны быть в ассоциативном массиве
- $STH->setFetchMode(PDO::FETCH_ASSOC);
-
- // Получаю все данные
- $result = $STH->fetchAll();
+ // Выполняем запрос
+ $stmt->execute($params);
}
catch (PDOException $e) {
- $this->HandleException($e);
+ // - в случае ошибки, обрабатываю её
+ $this->HandleException(new Exception("Ошибка выполнения SQL-запроса / SQL query execution error: " .
+ $e->getMessage()), false);
+
+ // - прерываю выполнение
+ return false;
}
- // Вывожу результат
- return $result;
+ // Если получены данные, то возвращаю их, в противном случае возвращаю пустой массив
+ return ($data = $stmt->fetchAll(PDO::FETCH_ASSOC)) !== [] ? $data : [];
}
/**
- * Выполняем запрос на получение одной строки (аналог QueryFirst)
+ * Получить первую строку результата запроса.
*
- * @param string $query Запрос
- * @param array $params Параметры запроса
+ * @param string $query Запрос SQL.
+ * @param array $params Параметры запроса.
*
- * @return false|array Строка в формате массива или false в случае ошибки
- *
- * @see Query
- * @see QueryFirst
- * @see QueryLast
- * @see GetRow
+ * @return array|false Первая строка результата запроса или false
в случае ошибки.
*/
- public function QueryScalar (string $query, array $params = []): false|array
+ public function QueryFirst (string $query, array $params = []): array|false
{
- return $this->QueryFirst($query, $params);
+ // Выполняем запрос
+ $rows = $this->Query($query, $params);
+
+ // Если получены данные, то возвращаю первую строку, в противном случае возвращаю false
+ return is_array($rows) ? reset($rows) : false;
}
/**
- * Выполняем запрос на получение первой строки
+ * Получить последнюю строку результата запроса.
*
- * @param string $query Запрос
- * @param array $params Параметры запроса
+ * @param string $query Запрос SQL.
+ * @param array $params Параметры запроса.
*
- * @return false|array Строка в формате массива или false в случае ошибки
- *
- * @see Query
- * @see QueryLast
- * @see QueryScalar
+ * @return array|false Последняя строка результата запроса или false
в случае ошибки.
*/
- public function QueryFirst (string $query, array $params = []): false|array
+ public function QueryLast (string $query, array $params = []): array|false
{
- // Выполняю запрос
- $result = $this->Query($query, $params);
+ // Выполняем запрос
+ $rows = $this->Query($query, $params);
- // Если в результате запроса получили ошибку или количество строк = 0
- if ($result === false || count($result) == 0)
- // - то возвращаем ошибку
- return false;
-
- // Получаю первый ключ массива
- $firstKey = array_key_first($result);
-
- // Возвращаем первую строку
- return $result[$firstKey];
+ // Если получены данные, то возвращаю последнюю строку, в противном случае возвращаю false
+ return is_array($rows) ? end($rows) : false;
}
/**
- * Выполняем запрос на получение последней строки
+ * Выполнить запрос, который изменяет данные в базе данных (INSERT, UPDATE, DELETE).
*
- * @param string $query Запрос
- * @param array $params Параметры запроса
+ * @param string $query Запрос SQL.
+ * @param array $params Параметры запроса.
*
- * @return false|array Строка в формате массива или false в случае ошибки
- *
- * @see Query
- * @see QueryFirst
- * @see QueryScalar
- */
- public function QueryLast (string $query, array $params = []): false|array
- {
- // Выполняю запрос
- $result = $this->Query($query, $params);
-
- // Если в результате запроса получили ошибку или количество строк = 0
- if ($result === false || count($result) == 0)
- // - то возвращаем ошибку
- return false;
-
- // Получаю последний ключ массива
- $lastKey = array_key_last($result);
-
- // Возвращаем первую строку
- return $result[$lastKey];
- }
-
-
- /**
- * Выполнение запроса. Обычно используется для операций,
- * которые не возвращают никаких данных, кроме количества
- * затронутых ими записей. Например,
- *
- * $db->Execute('DELETE FROM table WHERE id=1');
- *
- * @param string $query Запрос
- * @param array $params Параметры запроса
- *
- * @return int|false Количество затронутых строк или false в случае ошибки
+ * @return int|false Количество затронутых строк или false
в случае ошибки.
*/
public function Execute (string $query, array $params = []): int|false
{
- // По умолчанию результат false
- $result = false;
+ // Проверяем, что это не запрос SELECT
+ if (preg_match('/^\s*SELECT\s+/i', trim($query))) {
+ // - выдаю ошибку
+ $this->HandleException(new Exception("Некорректный запрос для метода Execute() / Incorrect request for the Execute() method: '$query'. Используйте метод Query() для безопасного выбора данных / Use the Query() method to select data safely."),
+ false);
+
+ // - аннулирую результат
+ return false;
+ }
+
try {
- // Если параметры не заданы
- if (count($params) == 0) {
- // - то выполняю запрос
- $result = $this->DataBaseHandle->exec($query);
- }
+ // Если это простой запрос без параметров
+ if (empty($params))
+ // - то просто выполняю его
+ return $this->DataBaseHandle->exec($query);
else {
- // - в противном случае
- // -- подготавливаю запрос
- $STH = $this->DataBaseHandle->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY]);
+ // - в противном случае, подготавливаем запрос с параметрами
+ $stmt = $this->DataBaseHandle->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY]);
- // -- выполняю запрос
- $opResult = $STH->execute($params);
+ // - если подготовили неуспешно
+ if (!$stmt) {
+ // -- то формируем ошибку
+ $this->HandleException(new PDOException("Ошибка подготовки SQL-запроса / SQL query preparation error"),
+ false);
+
+ // -- возвращаем неудачу
+ return false;
+ }
- // -- и если выполнение успешное,
- if ($opResult)
- // --- то в результат пойдёт количество строк
- $result = $STH->rowCount();
+ // - выполняем запрос
+ $executed = $stmt->execute($params);
+
+ // - если успешно, то возвращаем количество изменённых строк, иначе - false
+ return ($executed) ? $stmt->rowCount() : false;
}
}
catch (PDOException $e) {
- $this->HandleException($e);
- }
-
- // Если в результате false
- if ($result === false)
- // - то возвращаю его
+ // - в случае ошибки, обрабатываю её
+ $this->HandleException($e, false);
+
+ // - прерываю выполнение
return false;
-
- // Возвращаю результат
- return $result;
+ }
}
}
\ No newline at end of file
diff --git a/sources/traits/Database/DatabaseSpecial.php b/sources/traits/Database/DatabaseSpecial.php
index e331c85..3153606 100644
--- a/sources/traits/Database/DatabaseSpecial.php
+++ b/sources/traits/Database/DatabaseSpecial.php
@@ -10,6 +10,7 @@
use goodboyalex\php_db_components_pack\attributes\ConvertToDB;
use goodboyalex\php_db_components_pack\attributes\FieldName;
use goodboyalex\php_db_components_pack\attributes\IgnoredInDB;
+ use goodboyalex\php_db_components_pack\attributes\PrimaryKey;
use goodboyalex\php_db_components_pack\classes\ConditionBuilder;
use goodboyalex\php_db_components_pack\enums\DBOperation;
use goodboyalex\php_db_components_pack\interfaces\IDBItem;
@@ -29,32 +30,6 @@
*/
trait DatabaseSpecial
{
- /**
- * Подготавливает массив параметров для запроса.
- *
- * @param array $array Массив.
- *
- * @return array Массив с подготовленными параметрами.
- */
- private static function PrepareArray (array $array): array
- {
- // Результирующий массив
- $result = [];
-
- // Для каждого элемента массива
- foreach ($array as $key => $value)
- // - если ключ начинается с ":"
- if ($key[0] == ":")
- // - то сразу добавляем его в результирующий массив
- $result[$key] = $value;
- else
- // - в противном случае, предварительно добавим в имя ключа ":"
- $result[':' . $key] = $value;
-
- // Возвращаем результат
- return $result;
- }
-
/**
* Находит атрибут в массиве.
*
@@ -69,16 +44,112 @@
}
/**
- * Проверяет, является ли свойство публичным.
+ * Получает публичные свойства класса и их значения.
*
- * @param object $obj Объект.
- * @param string $propertyName Имя свойства.
+ * @param object $obj Класс.
*
- * @return bool Возвращает true
, если свойство публичное, иначе false
.
+ * @return array Массив публичных свойств и их значений.
*/
- private static function IsPublicProperty (object $obj, string $propertyName): bool
+ private static function GetPublicProperties (object $obj): array
{
- return property_exists($obj, $propertyName) && new ReflectionProperty($obj, $propertyName)->isPublic();
+ // Создаю массив свойств
+ $result = [];
+
+ // Получаю массив свойств
+ $properties = get_class_vars(get_class($obj));
+
+ // Для каждого свойства
+ foreach ($properties as $key => $value) {
+ // - пропускаю не свойства
+ if (!property_exists($obj, $key))
+ // -- пропускаю
+ continue;
+
+ // - получаю рефлексию
+ $rProperty = new ReflectionProperty($obj, $key);
+
+ // - пропускаю не публичные свойства
+ if (!$rProperty->isPublic())
+ // -- пропускаю
+ continue;
+
+ // - добавляю в массив свойство и его значение
+ $result[$key] = $value;
+ }
+
+ // Возвращаю результат
+ return $result;
+ }
+
+ /**
+ * Получает первичные ключи класса.
+ *
+ * @param IDBItem $source Класс.
+ * @param DBOperation $operation Текущая операция.
+ *
+ * @return array Массив первичных ключей и их значений.
+ */
+ private function FindPrimaryKeys (IDBItem $source, DBOperation $operation): array
+ {
+ // Создаю массив первичных ключей
+ $pKeys = [];
+
+ // Получаю массив свойств
+ $properties = self::GetPublicProperties($source);
+
+ // Для каждого свойства
+ foreach ($properties as $key => $value) {
+ // - получаю рефлексию
+ // - для класса
+ $reflectedClass = new ReflectionClass(get_class($source));
+ try {
+ // - для свойства
+ $reflectionProperty = $reflectedClass->getProperty($key);
+ }
+ catch (ReflectionException) {
+ // - если ошибка, то вывожу пустой массив
+ return [];
+ }
+
+ // - получаю атрибуты
+ $attributes = $reflectionProperty->getAttributes();
+
+ /**
+ * Фильтруем поля, игнорируемые для данной операции
+ *
+ * @var PrimaryKey|null $pkAttr Атрибут первичного ключа.
+ */
+ $pkAttr = self::FindAttribute($attributes, PrimaryKey::class);
+
+ // - если на свойстве нет атрибута первичного ключа
+ if ($pkAttr === null)
+ // -- то пропускаю
+ continue;
+
+ /**
+ * Фильтруем поля, игнорируемые для данной операции
+ *
+ * @var IgnoredInDB|null $ignoreAttr Атрибут игнорирования.
+ */
+ $ignoreAttr = self::FindAttribute($attributes, IgnoredInDB::class);
+
+ // - если поле игнорируется
+ if ($ignoreAttr !== null) {
+ // -- то проверяю, игнорируется ли данное поле
+ $isIgnore = $ignoreAttr->IgnoredOperations->IsExist(fn (DBOperation $oper) => $oper == $operation);
+
+ // -- если игнорируется
+ if ($isIgnore)
+ // --- то пропускаю
+ continue;
+ }
+
+ // - добавляю первичный ключ и его значение
+ $pKeys[$key] = $value;
+ }
+
+ // Возвращаю найденные ключи
+ return $pKeys;
}
/**
@@ -94,14 +165,10 @@
$result = [];
// Получаю массив свойств
- $properties = get_class_vars(get_class($source));
+ $properties = self::GetPublicProperties($source);
// Для каждого свойства
foreach ($properties as $key => $value) {
- // - пропускаю не публичные свойства
- if (!self::IsPublicProperty($source, $key))
- continue;
-
// Получаю рефлексию
// - для класса
$reflectedClass = new ReflectionClass(get_class($source));
@@ -169,20 +236,135 @@
return $result;
}
+ /**
+ * Восстанавливает объект из БД.
+ *
+ * @param array $row Строка БД.
+ * @param string $className Имя класса объекта.
+ * @param DBOperation $operation Операция.
+ *
+ * @return object|null Экземпляр класса объекта или null
, если ошибка.
+ */
+ private function RestoreItem (array $row, string $className, DBOperation $operation): ?object
+ {
+ // Если целевой класс не реализует интерфейс IDBItem
+ if (in_array(IDBItem::class, class_implements($className)))
+ // - то прерываем и возвращаем null
+ return null;
+
+ // Создаём объект желаемого класса
+ $class = new $className();
+
+ // Получаем публичные свойства класса
+ try {
+ $properties = new ReflectionClass($class)->getProperties(ReflectionProperty::IS_PUBLIC);
+ }
+ catch (ReflectionException $exception) {
+ // - в случае ошибки, обрабатываем её
+ $this->HandleException($exception, false);
+
+ // - и возвращаем null
+ return null;
+ }
+
+ // Массив отношений, содержащий имя поля, имя свойства, метод преобразования
+ $propertiesRelationship = [];
+
+ // Для каждого свойства
+ foreach ($properties as $property) {
+ // - получаю атрибуты
+ $attributes = $property->getAttributes();
+
+ /**
+ * Фильтруем поля, игнорируемые для данной операции
+ *
+ * @var IgnoredInDB|null $ignoreAttr Атрибут игнорирования.
+ */
+ $ignoreAttr = self::FindAttribute($attributes, IgnoredInDB::class);
+
+ // - если поле игнорируется
+ if ($ignoreAttr !== null) {
+ // -- то проверяю, игнорируется ли данное поле
+ $isIgnore = $ignoreAttr->IgnoredOperations->IsExist(fn (DBOperation $oper) => $oper == $operation);
+
+ // -- если игнорируется
+ if ($isIgnore)
+ // --- то пропускаю
+ continue;
+ }
+
+ /**
+ * Получаю значение имени поля
+ *
+ * @var FieldName|null $fieldNameAttr Атрибут имени поля.
+ */
+ $fieldNameAttr = self::FindAttribute($attributes, FieldName::class);
+
+ // - если есть атрибут имени поля, то беру его имя, иначе беру имя свойства
+ $fieldName = $fieldNameAttr !== null ? $fieldNameAttr->FieldName : $property->getName();
+
+ /**
+ * Преобразование типа.
+ *
+ * @var ConvertToDB|null $convertAttr Атрибут конвертации.
+ */
+ $convertAttr = self::FindAttribute($attributes, ConvertToDB::class);
+
+ // - если есть атрибут конвертации, то получаю значение функции конвертации, иначе null
+ $converterFunc = ($convertAttr) ? $convertAttr->ConvertFromDB : null;
+
+ // - добавляю в массив отношений
+ $propertiesRelationship[$fieldName] = [$property->getName(), $converterFunc];
+ }
+
+ // Проходим элементы массива из БД
+ foreach ($row as $column => $value) {
+ // - если свойство не найдено
+ if (!array_key_exists($column, $propertiesRelationship))
+ // -- то пропускаем
+ continue;
+
+ // - получаю имя свойства и процедуру конвертации
+ [$property, $converter] = $propertiesRelationship[$column];
+
+ // - применяем процедуру конвертации (если указана)
+ if ($converter !== null)
+ // - выполняю функцию конвертации
+ $value = call_user_func($converter, $value);
+
+ // - устанавливаем значение свойства
+ $class->$property = $value;
+ }
+
+ // Возвращаю класс
+ return $class;
+ }
+
/**
* Обрабатывает исключение.
*
* @param Exception $exception Исключение.
+ * @param bool $terminate Прерывать выполнение (true
) или просто зафиксировать
+ * (false
).
*
* @return void
*/
- private function HandleException (Exception $exception): void
+ private function HandleException (Exception $exception, bool $terminate = true): void
{
// Выбираю обработчик исключений
- $onException = $this->OnException ?? fn (Exception $e) => die($e->getMessage());
+ $onException = $this->OnException ?? function (Exception $e, bool $isTerminate): void
+ {
+ // Если требуется прерывать выполнение
+ if ($isTerminate)
+ // - то прерываем
+ die($e->getMessage());
+ else
+ // - в противном случае, выводим сообщение
+ echo $e->getMessage();
+ };
// Выполняю обработчик исключений
- $onException($exception);
+ $onException($exception, $terminate);
}
/**
@@ -231,19 +413,21 @@
*
* @param string $table Имя таблицы
* @param array $columns Колонки, которые нужно включить в запрос
- * @param array $where Параметры выборки
+ * @param ConditionBuilder $whereConditions Параметры выборки
* @param array $params Параметры и их значения
*
* @return string SQL-запрос
*/
- private function PrepareSQLForRowsQuery (string $table, array $columns, ConditionBuilder $where,
+ private function PrepareSQLForRowsQuery (string $table, array $columns, ConditionBuilder $whereConditions,
array &$params = []): string
{
- // Очищаю параметры
- $params = [];
-
- // Строковая интерпретация массива условий
- $sql_where = $this->PrepareQueryWhere($where, $params);
+ /**
+ * Собираю условие в виде пригодном для SQL
+ *
+ * @var string $sql_where where-запрос SQL
+ * @var array $params Параметры и их значения (для защиты от SQL-инъекции)
+ */
+ [$sql_where, $params] = $whereConditions->Build();
// Колонки
$sql_columns = count($columns) > 0 ? implode(', ', $this->PrepareColumn($columns)) : "*";
@@ -252,50 +436,11 @@
$sql = "SELECT $sql_columns FROM $this->DBSignOpen$table$this->DBSignClose";
// Если заданы where-параметры
- if (count($where) > 0) {
+ if ($whereConditions->Count() > 0)
// - то добавляю их
$sql .= " WHERE $sql_where";
- }
// Возвращаю запрос
return $sql;
}
-
- /**
- * Готовит выражение для WHERE-запроса
- *
- * @param array $where Массив условий
- * @param array $params Очищенные параметры
- *
- * @return string Строка WHERE-запроса
- */
- private function PrepareQueryWhere (array $where, array &$params): string
- {
- // Очищаю параметры
- $params = [];
-
- // Задаю результат
- $result = "";
-
- // Если массив условий не пуст
- if (count($where) > 0) {
- // - то для каждого условия
- foreach ($where as $key => $value) {
- // -- получаю ключ 100%-но без ":" в начале
- $where_key = $key[0] == ":" ? substr($key, 1) : $key;
-
- // -- добавляю префикс для 2 или более итерации
- $prefix = $result == "" ? "" : " AND ";
-
- // -- добавляю данные в $sql_where
- $result .= $prefix . $this->DBSignOpen . $where_key . $this->DBSignClose . " = :" . $where_key;
-
- // -- добавляю данные в параметры
- $params[$where_key] = "$value";
- }
- }
-
- // Вывожу результат
- return $result;
- }
}
\ No newline at end of file
diff --git a/sources/traits/Database/DatabaseUpdate.php b/sources/traits/Database/DatabaseUpdate.php
new file mode 100644
index 0000000..d062eba
--- /dev/null
+++ b/sources/traits/Database/DatabaseUpdate.php
@@ -0,0 +1,165 @@
+ $value) {
+ // - получаю ключ 100%-но без ":" в начале
+ $set_key = $key[0] == ":" ? substr($key, 1) : $key;
+
+ // - добавляю префикс для 2 или более итерации
+ $prefix = $sql_set == "" ? "" : ", ";
+
+ // - добавляю данные в sql_set
+ $sql_set .= "$prefix$this->DBSignOpen$set_key$this->DBSignClose=:$set_key";
+
+ // - добавляю данные в параметры
+ $params_set[":" . $set_key] = $value;
+ }
+
+ // Обработанные параметры
+ $params_where = [];
+
+ // Строковая интерпретация массива условий
+ $sql_where = $this->PrepareQueryWhere(where: $where, params: $params_where);
+
+ // Создаю параметры
+ $params = array_merge($params_set, $params_where);
+
+ // Создаю запрос
+ $sql = "UPDATE $this->DBSignOpen$table$this->DBSignClose SET $sql_set";
+
+ // Если заданы where-параметры
+ if (count($where) > 0)
+ // - то добавляю их
+ $sql .= " WHERE $sql_where";
+
+ // Выполняю запрос
+ $count = $this->Execute($sql, $params);
+
+ // Если результат - false
+ if ($count === false)
+ // - то и общий результат - false
+ return false;
+
+ // Если изменено 0 строк
+ if ($count === 0)
+ // - то и общий результат - false
+ return false;
+
+ // Вывожу результат -- успех
+ return true;
+ }
+
+ public function Update (string $table, IDBItem $item): bool
+ {
+ $primaryKeys = $this->FindPrimaryKeys($item, DBOperation::Update);
+
+ $pk_keys = array_keys($primaryKeys);
+
+ $where = new ConditionBuilder();
+
+ for ($i = 0; $i < count($primaryKeys); $i++) {
+ if ($i > 0)
+ $where = $where->And();
+
+ $whereKey = $pk_keys[$i];
+
+ $whereValue = $primaryKeys[$whereKey];
+
+ $where = $where->WhereEquals($whereKey, $whereValue);
+ }
+
+ $dbItem = $this->GetRow($table, $where, className: get_class($item));
+
+ // Создаю массив параметров
+ $params_set = [];
+
+ // Строковая интерпретация массива для изменения
+ $sql_set = "";
+
+ // Для каждых данных для изменения
+ foreach ($set as $key => $value) {
+ // - получаю ключ 100%-но без ":" в начале
+ $set_key = $key[0] == ":" ? substr($key, 1) : $key;
+
+ // - добавляю префикс для 2 или более итерации
+ $prefix = $sql_set == "" ? "" : ", ";
+
+ // - добавляю данные в sql_set
+ $sql_set .= "$prefix$this->DBSignOpen$set_key$this->DBSignClose=:$set_key";
+
+ // - добавляю данные в параметры
+ $params_set[":" . $set_key] = $value;
+ }
+
+ // Обработанные параметры
+ $params_where = [];
+
+ // Строковая интерпретация массива условий
+ $sql_where = $this->PrepareQueryWhere(where: $where, params: $params_where);
+
+ // Создаю параметры
+ $params = array_merge($params_set, $params_where);
+
+ // Создаю запрос
+ $sql = "UPDATE $this->DBSignOpen$table$this->DBSignClose SET $sql_set";
+
+ // Если заданы where-параметры
+ if (count($where) > 0)
+ // - то добавляю их
+ $sql .= " WHERE $sql_where";
+
+ // Выполняю запрос
+ $count = $this->Execute($sql, $params);
+
+ // Если результат - false
+ if ($count === false)
+ // - то и общий результат - false
+ return false;
+
+ // Если изменено 0 строк
+ if ($count === 0)
+ // - то и общий результат - false
+ return false;
+
+ // Вывожу результат -- успех
+ return true;
+ }
+ }
\ No newline at end of file