This commit is contained in:
Александр Бабаев 2025-07-29 17:50:57 +03:00
parent e4c8d7e6c8
commit 8436569ce5
9 changed files with 1223 additions and 839 deletions

70
.idea/codeStyles/Project.xml generated Normal file
View File

@ -0,0 +1,70 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<PHPCodeStyleSettings>
<option name="INDENT_CODE_IN_PHP_TAGS" value="true" />
<option name="PHPDOC_BLANK_LINE_BEFORE_TAGS" value="true" />
<option name="PHPDOC_BLANK_LINES_AROUND_PARAMETERS" value="true" />
<option name="PHPDOC_WRAP_LONG_LINES" value="true" />
<option name="ANONYMOUS_BRACE_STYLE" value="5" />
<option name="LINK_WEIGHT" value="11" />
<option name="AUTHOR_WEIGHT" value="3" />
<option name="USES_WEIGHT" value="9" />
<option name="VERSION_WEIGHT" value="5" />
<option name="COPYRIGHT_WEIGHT" value="7" />
<option name="PACKAGE_WEIGHT" value="4" />
<option name="SEE_WEIGHT" value="8" />
<option name="SINCE_WEIGHT" value="6" />
<option name="TODO_WEIGHT" value="10" />
<option name="LOWER_CASE_BOOLEAN_CONST" value="true" />
<option name="LOWER_CASE_NULL_CONST" value="true" />
<option name="ELSE_IF_STYLE" value="SEPARATE" />
<option name="SPACE_BEFORE_SHORT_CLOSURE_LEFT_PARENTHESIS" value="true" />
<option name="FORCE_SHORT_DECLARATION_ARRAY_STYLE" value="true" />
<option name="SPACE_AROUND_ASSIGNMENT_IN_DECLARE" value="true" />
<option name="FORCE_EMPTY_CLASSES_IN_ONE_LINE" value="true" />
<option name="MULTILINE_CLOSURE_LAMBDA_ON_NEW_LINE" value="true" />
<option name="ATTRIBUTES_WRAP" value="1" />
<option name="PARAMETERS_ATTRIBUTES_WRAP" value="1" />
</PHPCodeStyleSettings>
<codeStyleSettings language="PHP">
<option name="LAMBDA_BRACE_STYLE" value="5" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="WHILE_ON_NEW_LINE" value="true" />
<option name="CATCH_ON_NEW_LINE" value="true" />
<option name="FINALLY_ON_NEW_LINE" value="true" />
<option name="SPECIAL_ELSE_IF_TREATMENT" value="true" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="ALIGN_MULTILINE_EXTENDS_LIST" value="true" />
<option name="SPACE_BEFORE_METHOD_PARENTHESES" value="true" />
<option name="CALL_PARAMETERS_WRAP" value="1" />
<option name="METHOD_PARAMETERS_WRAP" value="1" />
<option name="EXTENDS_KEYWORD_WRAP" value="1" />
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
<option name="BINARY_OPERATION_WRAP" value="1" />
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
<option name="TERNARY_OPERATION_WRAP" value="1" />
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
<option name="FOR_STATEMENT_WRAP" value="1" />
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
<option name="ARRAY_INITIALIZER_LBRACE_ON_NEXT_LINE" value="true" />
<option name="ARRAY_INITIALIZER_RBRACE_ON_NEXT_LINE" value="true" />
<option name="ASSIGNMENT_WRAP" value="1" />
<option name="WRAP_ON_TYPING" value="1" />
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
<arrangement>
<groups>
<group>
<type>GETTERS_AND_SETTERS</type>
<order>KEEP</order>
</group>
<group>
<type>OVERRIDDEN_METHODS</type>
<order>KEEP</order>
</group>
</groups>
</arrangement>
</codeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
<?php
namespace goodboyalex\php_db_components_pack\interfaces;
/**
* Интерфейс моделей и классов, хранящие свойства в SQL базе данных.
*
* @author Александр Бабаев
* @package php_db_components_pack
* @version 1.0.0
* @since 1.0
*/
interface IDBItem
{
/**
* Вывод параметров добавления/обновления данных в базу данных.
*
* @param bool $withId Нужно ли добавлять Id в массив
*
* @return array Массив параметров
*/
public function ToSQL (bool $withId = true): array;
/**
* Преобразование данных из базы данных в модель, класс.
*
* @param array $sqlData Данные из базы данных
*
* @return self Класс модели с заполненными данными из базы данных
*/
public function FromSQL (array $sqlData): self;
}

View File

@ -1,32 +0,0 @@
<?php
namespace goodboyalex\php_db_components_pack\interfaces;
/**
* Интерфейс поддержки моделей и классов, реализующих хранение свойств в SQL базе данных.
*
* @author Александр Бабаев
* @package php_db_components_pack
* @version 1.0.0
* @since 1.0
*/
interface IStoredAtSQL
{
/**
* Вывод параметров добавления/обновления данных в базу данных.
*
* @param bool $withId Нужно ли добавлять Id в массив
*
* @return array Массив параметров
*/
public function ToSQL (bool $withId = true): array;
/**
* Преобразование данных из базы данных в модель, класс.
*
* @param array $sqlData Данные из базы данных
*
* @return self Класс модели с заполненными данными из базы данных
*/
public function FromSQL (array $sqlData): self;
}

View File

@ -0,0 +1,153 @@
<?php
/**
* @noinspection SqlNoDataSourceInspection
*/
namespace goodboyalex\php_db_components_pack\traits\Database;
use Exception;
use goodboyalex\php_components_pack\classes\Tuple;
use goodboyalex\php_db_components_pack\interfaces\IDBItem;
use PDO;
use PDOException;
/**
* Трейт для работы со вставкой строк в базу данных.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
* @see PDO
*/
trait DatabaseInsert
{
/**
* Вставляет строку в базу данных.
*
* @param string $table Имя таблицы.
* @param IDBItem $row Модель или класс, реализующий интерфейс IDBItem, для вставки.
* @param array $options Массив дополнительных параметров. Может содержать следующие ключи:
*
* - <code>ignore: array</code> - игнорировать перечисленные поля. Когда массив пуст, то ничего
* игнорироваться не будет. По умолчанию - пустой массив.
* - <code>allow: array</code> - включать только перечисленные поля. Когда массив пуст, то все поля будут
* включены. По умолчанию - пустой массив.
*
* @return string|false В случае успеха выведет: id созданной записи, -1, если запись создана, но id не получен
* (глюк?) и false, если ошибка
*/
public function Insert (string $table, IDBItem $row, array $options = []): string|false
{
// Подготавливаю запрос
[$sql, $params] = $this->PrepareInsertSQL($table, $row, $options);
// Выполняю запрос
$count = $this->Execute($sql, $params);
// Если результат - false или добавлена не одна запись
if (($count === false) || ($count < 1) || ($count > 1))
// - то и общий результат - false
return false;
$lastId = -1;
try {
// Получаю последний id этой записи
$lastIdResult = $this->DataBaseHandle->lastInsertId();
// Если получение неудачное
if ($lastIdResult === false)
// - то вывожу -1
return $lastId;
// Устанавливаю последний id
$lastId = $lastIdResult;
}
catch (PDOException $e) {
$this->HandleException($e);
}
// Вывожу последний id
return $lastId;
}
/**
* Вставляет несколько строк в базу данных.
*
* @param string $table Имя таблицы.
* @param array $options Параметры.
* @param IDBItem ...$sources Модели или классы, реализующие интерфейс IDBItem, для вставки.
*
* @return void
*/
public function InsertMany (string $table, array $options, IDBItem ...$sources): void
{
// Инициализирую транзакцию
$this->InitTransaction();
try {
// Для каждого источника
foreach ($sources as $source)
// - вставляю строку
$this->Insert($table, $source, $options);
// Если вставка успешна, то подтверждаю транзакцию
$this->Commit();
}
catch (Exception $exception) {
// - если ошибка, то откатываю транзакцию
$this->RollBack();
// - и вывожу ошибку
$this->HandleException($exception);
}
}
/**
* Подготавливает запрос для вставки строки в базу данных.
*
* @param string $table Имя таблицы.
* @param IDBItem $row Элемент.
* @param array $options Параметры.
*
* @return Tuple Возвращает [запрос, параметры запроса].
*/
private function PrepareInsertSQL (string $table, IDBItem $row, array $options = []): Tuple
{
// Подготавливаю массив параметров
$params = $this->PrepareParamsArray(source: $row, options: $options);
// Получаю ключи параметров
$keys = array_keys($params);
// Создаю результирующий массив имён ключей параметров
$keysReal = [];
// Для каждого ключа параметра
foreach ($keys as $key) {
// - получаю его имя
$keyResult = $key[0] == ":" ? substr($key, 1) : $key;
// - заключаю в кавычки
$keyResult = "$this->DBSignOpen$keyResult$this->DBSignClose";
// - добавляю в результирующий массив ключей
$keysReal[] = $keyResult;
}
// Ключи sql запроса
$sql_keys = implode(', ', $keysReal);
// Значения sql запроса
$sql_values = implode(', ', $keys);
// Создаю запрос
$sql = "INSERT INTO $this->DBSignOpen$table$this->DBSignClose ($sql_keys) VALUES ($sql_values);";
// Возвращаю результат
return new Tuple($sql, $params);
}
}

View File

@ -0,0 +1,182 @@
<?php
/**
* @noinspection SqlNoDataSourceInspection
*/
namespace goodboyalex\php_db_components_pack\traits\Database;
use PDO;
use PDOException;
/**
* Трейт для работы с запросами к базе данных типа Query и Execute.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
* @see PDO
*/
trait DatabaseQueryExecute
{
/**
* Запрос строк из базы данных.
*
* @param string $query Запрос
* @param array $params Параметры запроса
*
* @return false|array Ассоциированный массив с результатом запроса или false в случае ошибки
*/
public function Query (string $query, array $params = []): false|array
{
// По умолчанию, результат пуст
$result = false;
try {
// Подготавливаю запрос
$STH = $this->DataBaseHandle->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY]);
// Выполняю запрос
$STH->execute($params);
// Указываю, что данные, которые я хочу получить, должны быть в ассоциативном массиве
$STH->setFetchMode(PDO::FETCH_ASSOC);
// Получаю все данные
$result = $STH->fetchAll();
}
catch (PDOException $e) {
$this->HandleException($e);
}
// Вывожу результат
return $result;
}
/**
* Выполняем запрос на получение одной строки (аналог QueryFirst)
*
* @param string $query Запрос
* @param array $params Параметры запроса
*
* @return false|array Строка в формате массива или false в случае ошибки
*
* @see Query
* @see QueryFirst
* @see QueryLast
* @see GetRow
*/
public function QueryScalar (string $query, array $params = []): false|array
{
return $this->QueryFirst($query, $params);
}
/**
* Выполняем запрос на получение первой строки
*
* @param string $query Запрос
* @param array $params Параметры запроса
*
* @return false|array Строка в формате массива или false в случае ошибки
*
* @see Query
* @see QueryLast
* @see QueryScalar
*/
public function QueryFirst (string $query, array $params = []): false|array
{
// Выполняю запрос
$result = $this->Query($query, $params);
// Если в результате запроса получили ошибку или количество строк = 0
if ($result === false || count($result) == 0)
// - то возвращаем ошибку
return false;
// Получаю первый ключ массива
$firstKey = array_key_first($result);
// Возвращаем первую строку
return $result[$firstKey];
}
/**
* Выполняем запрос на получение последней строки
*
* @param string $query Запрос
* @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];
}
/**
* Выполнение запроса. Обычно используется для операций,
* которые не возвращают никаких данных, кроме количества
* затронутых ими записей. Например,
*
* <code>$db->Execute('DELETE FROM table WHERE id=1');</code>
*
* @param string $query Запрос
* @param array $params Параметры запроса
*
* @return int|false Количество затронутых строк или false в случае ошибки
*/
public function Execute (string $query, array $params = []): int|false
{
// По умолчанию результат false
$result = false;
try {
// Если параметры не заданы
if (count($params) == 0) {
// - то выполняю запрос
$result = $this->DataBaseHandle->exec($query);
}
else {
// - в противном случае
// -- подготавливаю запрос
$STH = $this->DataBaseHandle->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY]);
// -- выполняю запрос
$opResult = $STH->execute($params);
// -- и если выполнение успешное,
if ($opResult)
// --- то в результат пойдёт количество строк
$result = $STH->rowCount();
}
}
catch (PDOException $e) {
$this->HandleException($e);
}
// Если в результате false
if ($result === false)
// - то возвращаю его
return false;
// Возвращаю результат
return $result;
}
}

View File

@ -0,0 +1,248 @@
<?php
/**
* @noinspection SqlNoDataSourceInspection
*/
namespace goodboyalex\php_db_components_pack\traits\Database;
use Exception;
use goodboyalex\php_components_pack\exceptions\TypeException;
use goodboyalex\php_components_pack\extensions\TypeExtension;
use goodboyalex\php_db_components_pack\interfaces\IDBItem;
use PDO;
/**
* Трейт для функций поддержки для работы с базой данных.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
* @see PDO
*/
trait DatabaseSpecial
{
/**
* Добавляет параметр в массив.
*
* @param array $array Массив.
* @param string $key Имя параметра.
* @param mixed $value Значение параметра.
*
* @return void
*/
private static function AddArrayItem (array &$array, string $key, mixed $value): void
{
// Если ключ параметра начинается с ":"
if ($key[0] == ":")
// - то сразу добавляем его в результирующий массив
$array[$key] = $value;
else
// - в противном случае, предварительно добавим в имя ключа ":"
$array[':' . $key] = $value;
}
/**
* Обрабатывает исключение.
*
* @param Exception $exception Исключение.
*
* @return void
*/
private function HandleException (Exception $exception): void
{
// Выбираю обработчик исключений
$onException = $this->OnException ?? fn (Exception $e) => die($e->getMessage());
// Выполняю обработчик исключений
$onException($exception);
}
/**
* Подготавливает массив параметров
*
* @param IDBItem $source Объект со свойствами.
* @param bool $withId Добавлять ли id в массив.
* @param array $options Опции.
*
* @return array|false Подготовленный массив параметров или false в случае ошибки
*/
private function PrepareParamsArray (IDBItem $source, bool $withId = true, array $options = []): array|false
{
// Создаём результирующий массив
$result = [];
// Если есть игнорируемые или разрешенные свойства
if (!(count($options['ignore']) == 0 && count($options['allow']) == 0))
// -- то для каждого игнорируемого свойства
foreach ($options['ignored'] as $ignoredProperty)
// --- и если оно есть в массиве разрешенных
if (in_array($ignoredProperty, $options['allowed']))
// ---- то исключаю его из массива разрешенных
unset($options['allowed'][array_search($ignoredProperty, $options['allowed'])]);
// Получаю массив свойств
$properties = $source->ToSQL($withId);
// Для каждого элемента массива
foreach ($properties as $name => $value) {
// - если свойство игнорируется
if (in_array($name, $options['ignored']))
// -- пропускаю
continue;
// - если свойство не разрешено
if (count($options['allowed']) > 0 && !in_array($name, $options['allowed']))
// -- пропускаю
continue;
// - если свойство является объектом
if (is_object($source->$name)) {
try {
// -- пытаюсь преобразовать его в массив
self::AddArrayItem($result, $name, json_encode(TypeExtension::ToArray($source->$name),
JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}
catch (TypeException) {
// -- если не получилось, то сериализую его
self::AddArrayItem($result, $name,
json_encode($source->$name, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}
// -- пропускаю
continue;
}
// - если свойство является массивом
if (is_array($source->$name)) {
// -- сериализую его
self::AddArrayItem($result, $name, json_encode($source->$name, JSON_UNESCAPED_UNICODE |
JSON_PRETTY_PRINT));
// -- пропускаю
continue;
}
// - иначе просто добавляю свойство в массив
self::AddArrayItem($result, $name, $source->$name);
}
// Вывожу результирующий массив
return $result;
}
/**
* Подготавливает массив столбцов для использования в базе данных
*
* @param array $columns Массив колонок.
*
* @return array Массив преобразованных колонок.
*/
private function PrepareColumn (array $columns): array
{
return array_map(function ($item)
{
// Результирующая строка
$result = "";
// Если длинна строки > 0
if (strlen($item) > 0) {
// - первый символ
$firstLetter = substr($item, 0, 1);
// - последний символ
$lastLetter = substr($item, -1);
// - если первый символ не $this->DBSignOpen
if ($firstLetter !== $this->DBSignOpen)
// -- то добавляем
$result .= $this->DBSignOpen;
// - добавляем строку
$result .= $item;
// - если последний символ не $this->DBSignClose
if ($lastLetter !== $this->DBSignClose)
// -- то добавляем
$result .= $this->DBSignClose;
}
// Возвращаем результат
return $result;
}, $columns);
}
/**
* Генерирует SQL запрос выборки строк.
*
* @param string $table Имя таблицы
* @param array $columns Колонки, которые нужно включить в запрос
* @param array $where Параметры выборки
* @param array $params Параметры и их значения
*
* @return string SQL-запрос
*/
private function PrepareSQLForRowsQuery (string $table, array $columns = [], array $where = [],
array &$params = []): string
{
// Очищаю параметры
$params = [];
// Строковая интерпретация массива условий
$sql_where = $this->PrepareQueryWhere($where, $params);
// Колонки
$sql_columns = count($columns) > 0 ? implode(', ', $this->PrepareColumn($columns)) : "*";
// Создаю запрос
$sql = "SELECT $sql_columns FROM $this->DBSignOpen$table$this->DBSignClose";
// Если заданы where-параметры
if (count($where) > 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;
}
}

View File

@ -0,0 +1,98 @@
<?php
/**
* @noinspection SqlNoDataSourceInspection
*/
namespace goodboyalex\php_db_components_pack\traits\Database;
use PDO;
use PDOException;
/**
* Трейт для работы с транзакциями базы данных.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
* @see PDO
*/
trait DatabaseTransactions
{
/**
* Начинает транзакцию.
*
* @return void
* @throws PDOException Если транзакция уже начата или не удалось начать.
*/
public function InitTransaction (): void
{
// Проверка, не находится ли текущая транзакция в процессе.
if ($this->InTransaction())
// - если находится, то выбрасываем исключение.
throw new PDOException("Транзакция уже начата! / Transaction already started!");
// Начинаем транзакцию
$isSuccess = $this->DataBaseHandle->beginTransaction();
// Если транзакция не началась
if (!$isSuccess)
// - то выбрасываем исключение.
throw new PDOException("Ошибка при начале транзакции! / Transaction start error!");
}
/**
* Проверяет, находится ли текущая транзакция в процессе.
*
* @return bool Результат проверки: <code>true</code> - в процессе, <code>false</code> - нет.
*/
public function InTransaction (): bool
{
return $this->DataBaseHandle->inTransaction();
}
/**
* Отправляет транзакцию в базу данных.
*
* @return void
* @throws PDOException Если транзакция не начата или не отправлена.
*/
public function Commit (): void
{
// Проверка, не находится ли текущая транзакция в процессе
if (!$this->InTransaction())
// - если нет, то выбрасываем исключение.
throw new PDOException("Транзакция не начата! / Transaction not started!");
// Отправляем транзакцию в базу данных
$isSuccess = $this->DataBaseHandle->commit();
// Если транзакция не отправлена
if (!$isSuccess)
// - то выбрасываем исключение.
throw new PDOException("Ошибка при отправке транзакции в базу данных! / Transaction send error!");
}
/**
* Откатывает транзакцию.
*
* @return void
* @throws PDOException Если транзакция не начата или не откатана.
*/
public function RollBack (): void
{
// Проверка, не находится ли текущая транзакция в процессе
if (!$this->InTransaction())
// - если нет, то выбрасываем исключение.
throw new PDOException("Транзакция не начата! / Transaction not started!");
// Откатываем транзакцию
$isSuccess = $this->DataBaseHandle->rollBack();
// Если транзакция не откатана
if (!$isSuccess)
// - то выбрасываем исключение.
throw new PDOException("Ошибка при откате транзакции! / Transaction rollback error!");
}
}