This commit is contained in:
2025-02-03 18:49:47 +03:00
parent f1a79d66ec
commit dd62ad0ca4
1739 changed files with 154102 additions and 0 deletions

View File

@@ -0,0 +1,363 @@
<?php
/**
* Отключаем ненужные проверки.
*
* @noinspection PhpUnusedParameterInspection, PhpUnused
*/
namespace goodboyalex\php_components_pack\classes;
use Closure;
use goodboyalex\php_components_pack\enums\MessageType;
use goodboyalex\php_components_pack\interfaces\ISerializable;
use goodboyalex\php_components_pack\models\ActionStateMessageModel;
/**
* Состояние действия.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
*/
final class ActionState implements ISerializable
{
/**
* @var mixed|null $Value Значение
*/
public mixed $Value;
/**
* @var array $Messages Список информации
*/
private array $Messages = [];
/**
* Конструктор
*
* @param mixed|null $defValue Значение по умолчанию
*/
public function __construct (mixed $defValue = null)
{
$this->Value = $defValue;
}
/**
* При выводе GetStringMessages выводит только ошибки.
*
* @return Closure Возвращает функцию, проверяющую сообщение на соответствие типу.
*/
public static function GET_STRING_ERROR_ONLY (): Closure
{
return fn (ActionStateMessageModel $message)
=> $message->MessageType === MessageType::Error;
}
/**
* При выводе GetStringMessages выводит ошибки и предупреждения.
*
* @return Closure Возвращает функцию, проверяющую сообщение на соответствие типу.
*/
public static function GET_STRING_ERROR_AND_WARNING (): Closure
{
return fn (ActionStateMessageModel $message)
=> $message->MessageType === MessageType::Error
|| $message->MessageType === MessageType::Warning;
}
/**
* При выводе GetStringMessages выводит все сообщения.
*
* @return Closure Возвращает функцию, проверяющую сообщение на соответствие типу.
*/
public static function GET_STRING_ALL (): Closure
{
return fn (ActionStateMessageModel $message) => true;
}
/**
* Добавляет данные из другого состояния.
*
* @param ActionState $state Другое состояние
* @param bool $clearAllBefore Очищать сообщения перед добавлением (true) или просто добавить к текущим (false)
*
* @return void
*/
public function AddState (ActionState $state, bool $clearAllBefore = false): void
{
// Если нужно очистить список сообщений
if ($clearAllBefore)
// - то очищаю список сообщений
$this->Clear(fn (ActionStateMessageModel $message) => true);
// Добавляю сообщения из другого состояния
$this->AddRange($state->GetMessages(function (ActionStateMessageModel $message)
{
return true;
}));
// Добавляю значение
$this->Value = $state->Value;
}
/**
* Очищает список сообщений, согласно условию.
*
* @param callable $predicate Условие выборки
*
* @return void
*/
public function Clear (callable $predicate): void
{
// Выбираю все сообщения, удовлетворяющие условию
$list = $this->GetMessages($predicate);
// Удаляю их из списка
$this->Messages = array_diff($this->Messages, $list);
}
/**
* Выбирает сообщения по условию predicate.
*
* @param callable $predicate Условие выборки
*
* @return array Список отобранных сообщений
*/
public function GetMessages (callable $predicate): array
{
// Получаю список элементов
$list = [];
// Для каждого элемента
foreach ($this->Messages as $actionStateMessage)
// - если он подходит под условие
if ($predicate($actionStateMessage))
// - добавляю его в список
$list[] = $actionStateMessage;
// Возвращаю список
return $list;
}
/**
* Добавляет список
*
* @param array $messages Список сообщений
*
* @return void
*/
public function AddRange (array $messages): void
{
$this->Messages = array_merge($this->Messages, $messages);
}
/**
* Возвращает список сообщений (параметр Message у каждого сообщения).
*
* @param callable $predicate Условие выборки
* @param string $separator Разделитель
*
* @return string Список сообщений
*/
public function GetStringMessages (callable $predicate, string $separator = '\n'): string
{
// Делаю выборку
$list = $this->GetMessages($predicate);
// Формирую список сообщений
$result = [];
// Для каждого сообщения из выборки
foreach ($list as $message) {
// - если оно не нужного нам класса
if (!$message instanceof ActionStateMessageModel)
// -- то пропускаю
continue;
// - добавляю сообщение
$result[] = $message->Message;
}
// Возвращаю список
return implode($separator, $result);
}
/**
* Добавляет сообщение о критической ошибке.
*
* @param string $message Сообщение
*
* @return void
*/
public function AddCritical (string $message): void
{
$this->Add(new ActionStateMessageModel(MessageType::Error, true, $message));
}
/**
* Добавление сообщения.
*
* @param ActionStateMessageModel $message Сообщение
*
* @return void
*/
public function Add (ActionStateMessageModel $message): void
{
$this->Messages[] = $message;
}
/**
* Добавляет сообщение об ошибке.
*
* @param string $message Сообщение
*
* @return void
*/
public function AddError (string $message): void
{
$this->Add(new ActionStateMessageModel(MessageType::Error, false, $message));
}
/**
* Добавляет предупреждение.
*
* @param string $message Сообщение.
*
* @return void
*/
public function AddWarning (string $message): void
{
$this->Add(new ActionStateMessageModel(MessageType::Warning, false, $message));
}
/**
* Добавляет информационное сообщение.
*
* @param string $message Сообщение.
*
* @return void
*/
public function AddInfo (string $message): void
{
$this->Add(new ActionStateMessageModel(MessageType::Info, false, $message));
}
/**
* Проверяет, есть ли информационные сообщения.
*
* @return bool Наличие сообщений
*/
public function HasInfos (): bool
{
return $this->Count(fn (ActionStateMessageModel $message) => $message->MessageType == MessageType::Info) > 0;
}
/**
* Количество сообщений, удовлетворяющих условию.
*
* @param callable $predicate Условие выборки
*
* @return int Число сообщений
*/
public function Count (callable $predicate): int
{
// Получаю список сообщений
$list = $this->GetMessages($predicate);
// Возвращаю результат
return count($list);
}
/**
* Проверяет, успешно ли завершилась операция.
*
* @param bool $onlyCritical Игнорировать все некритические ошибки и предупреждения (не рекомендуется!)
*
* @return bool Успешно ли завершилась операция.
*/
public function IsSuccess (bool $onlyCritical = false): bool
{
// Если нужно учитывать только критические ошибки
if ($onlyCritical)
// - то проверяю наличие критических ошибок
return !$this->HasErrors($onlyCritical);
// Возвращаю результат
return !$this->HasErrors() && !$this->HasWarnings();
}
/**
* Проверяет, есть ли ошибки.
*
* @param bool $onlyCritical Учитывать только критические ошибки.
*
* @return bool Наличие ошибок.
*/
public function HasErrors (bool $onlyCritical = false): bool
{
return $this->Count(function (ActionStateMessageModel $message) use ($onlyCritical): bool
{
// - сравниваю тип сообщения
if ($message->MessageType != MessageType::Error)
// -- если не совпадает, то возвращаю false
return false;
// - если нужно выводить только критические ошибки, а сообщение не критическое
if ($onlyCritical && !$message->IsCritical)
// -- то возвращаю false
return false;
// Возвращаю true
return true;
});
}
/**
* Проверяет, есть ли предупреждения.
*
* @return bool Наличие предупреждений
*/
public function HasWarnings (): bool
{
return $this->Count(fn (ActionStateMessageModel $message) => $message->MessageType == MessageType::Warning) > 0;
}
/**
* @inheritDoc
*/
public function Serialize (): string
{
// Создаю список сообщений
$list = [];
// Для каждого сообщения
foreach ($this->Messages as $message)
// - сериализую его и добавляю в список
$list[] = $message->Serialize();
// Возвращаю результат
return serialize($list);
}
/**
* @inheritDoc
*/
public function UnSerialize (string $serialized): void
{
// Очищаю список сообщений
$this->Clear(fn (ActionStateMessageModel $message) => true);
// Десериализую список сообщений
$list = unserialize($serialized);
// Для каждого сообщения
foreach ($list as $messageSerialized) {
// - создаю новое сообщение
$message = new ActionStateMessageModel();
// - десериализую его
$message->UnSerialize($messageSerialized);
// - добавляю в список
$this->Add($message);
}
}
}

View File

@@ -0,0 +1,335 @@
<?php
namespace goodboyalex\php_components_pack\classes;
use DateTimeImmutable;
use DateTimeInterface;
use Exception;
use ReflectionClass;
use ReflectionException;
use stdClass;
use UnitEnum;
/**
* Класс сопоставлений классов и объектов.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
*/
final class ClassMapper
{
/**
* @var array $DefaultOptions Настройки
*/
public const array DefaultOptions = [
'ignored' => [],
'allowed' => []
];
/**
* Передаёт одинаковые параметры из класса $from в класс $to, учитывая игнорируемые ($ignoredProperties) и
* разрешенные ($allowedProperties) свойства.
*
* @param object $from Класс-донор
* @param object $to Класс-приемник
* @param array $options Параметры привязки свойств (см атрибут Bind).
*
* @return void
*/
public static function MapClass (object $from, object $to, array $options = self::DefaultOptions): void
{
// Если есть игнорируемые или разрешенные свойства
if (!(count($options['ignored']) == 0 && count($options['allowed']) == 0))
// -- то для каждого игнорируемого свойства
foreach ($options['ignored'] as $ignoredProperty)
// --- и если оно есть в массиве разрешенных
if (in_array($ignoredProperty, $options['allowed']))
// ---- то исключаю его из массива разрешенных
unset($options['allowed'][array_search($ignoredProperty, $options['allowed'])]);
// Получаю массив свойств
$properties = get_class_vars(get_class($from));
// Для каждого элемента массива
foreach ($properties as $name => $value) {
// - если свойство игнорируется
if (in_array($name, $options['ignored']))
// -- пропускаю
continue;
// - если свойство не разрешено
if (count($options['allowed']) > 0 && !in_array($name, $options['allowed']))
// -- пропускаю
continue;
// - если свойство есть в объекте
if (property_exists($to, $name))
// -- то присваиваю значение
$to->$name = $from->$name;
}
}
/**
* Подготавливает значения свойств класса.
*
* @param array $params Данные запроса.
* @param ReflectionClass $classReflector Анализатор класса.
* @param array $options Массив свойств привязки.
*
* @return array Массив данных класса.
*/
public static function PrepareClassProperties (array $params, ReflectionClass $classReflector,
array $options = self::DefaultOptions): array
{
// Создаю массив данных класса
$classData = [];
// Для каждого свойства класса
foreach ($classReflector->getProperties() as $property) {
// - получаю имя свойства
$propertyName = $property->getName();
// - если это свойство задано в массиве параметров
if (array_key_exists($propertyName, $params)) {
// -- если задан массив разрешённых свойств
if (!empty($options["allowed"]))
// --- если свойство не разрешено
if (!in_array($propertyName, $options["allowed"]))
// ---- то пропускаю
continue;
// -- если задан массив запрещённых свойств
if (!empty($options["ignored"]))
// --- если свойство должно игнорироваться
if (in_array($propertyName, $options["ignored"]))
// ---- то пропускаю
continue;
// -- добавляю значение свойства в результат
$classData[$propertyName] = $params[$propertyName];
}
else {
// - в противном случае, пробегаю массив параметров
foreach ($params as $key => $value) {
// -- если в имени параметра есть разделитель "_"
if (str_starts_with($key, $propertyName . "_")) {
// -- разбиваю имя параметра на части
$keyParts = explode("_", $key);
// -- добавляю значение свойства в результат
self::GetClassParametersArrayParser($classData, $keyParts, $value);
}
}
}
}
// Возвращаю массив данных класса
return $classData;
}
/**
* Парсит массив свойств класса.
*
* @param array $source Исходный массив (он же и результат парсинга).
* @param array $parametersKeys Массив имен свойств. Например, Page_Meta_Id должен быть разбит на
* ["Page", "Meta", "Id"].
* @param mixed $value Значение свойства.
* @param array $options Массив параметров привязки свойств.
*
* @return void
*/
public static function GetClassParametersArrayParser (array &$source, array $parametersKeys, mixed $value,
array $options = self::DefaultOptions): void
{
// Если массив имен свойств пустой
if (empty($parametersKeys))
// - то прерываю парсинг
return;
// Получаю имя текущего свойства
$currentName = array_shift($parametersKeys);
// Если текущего свойства нет в массиве
if (!isset($source[$currentName]))
// - то создаю его
$source[$currentName] = [];
// Если массив имен свойств содержит только одно свойство
if (count($parametersKeys) === 0) {
// - если задан массив разрешённых свойств
if (!empty($options["allowed"]))
// --- если свойство не разрешено
if (!in_array($currentName, $options["allowed"]))
// ---- то пропускаю
return;
// -- если задан массив запрещённых свойств
if (!empty($options["ignored"]))
// --- если свойство должно игнорироваться
if (in_array($currentName, $options["ignored"]))
// ---- то пропускаю
return;
// - добавляю значение свойства в результат
$source[$currentName] = $value;
}
else
// - иначе продолжаю парсинг
self::GetClassParametersArrayParser($source[$currentName], $parametersKeys, $value, $options);
}
/**
* Переводит данные из массива в объект класса.
*
* @param string $className Имя класса
* @param array $properties Массив данных
*
* @return mixed Объект класса
* @throws Exception
*/
public static function MapToClassProperty (string $className, array $properties): mixed
{
// Создаю
try {
$classReflector = new ReflectionClass($className);
// Создаю объект класса
$classObj = new $className();
// Для каждого свойства класса
foreach ($properties as $key => $value) {
// - проверяю наличие свойства
if (!$classReflector->hasProperty($key))
// -- иду к следующему
continue;
// - получаю данные про свойство
// -- само свойство
$property = $classReflector->getProperty($key);
// -- тип свойства
$propertyType = $property->getType();
// - если значение является классом
if (!$propertyType->isBuiltin() && is_array($value)) {
// -- присваиваю объект
self::SetParameterToClass($classReflector, $key, $classObj,
self::MapToClassProperty($propertyType->getName(), $value));
// -- следующий элемент
continue;
}
// - если значение является датой
if ($classObj->$key instanceof DateTimeInterface) {
// -- получаю дату
$dateValue = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $value . " 00:00:00");
// -- если не получилось
if ($dateValue === false)
// --- то добавляю дату по умолчанию (1970-01-01 00:00:00)
$dateValue = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', "1970-01-01 00:00:00");
// -- присваиваю дату
self::SetParameterToClass($classReflector, $key, $classObj, $dateValue);
// -- следующий элемент
continue;
}
// - если значение является перечислением
if ($classObj->$key instanceof UnitEnum) {
// -- если значение уже является перечислением
if ($value instanceof UnitEnum)
// --- присваиваю перечисление
self::SetParameterToClass($classReflector, $key, $classObj, $value);
else
// -- иначе
self::SetParameterToClass($classReflector, $key, $classObj,
is_numeric($value) ? $classObj->$key::FromInt($value) : $classObj->$key::FromName($value));
// -- следующий элемент
continue;
}
// - если значение является NULL
if ($value == "null") {
// -- присваиваю NULL
self::SetParameterToClass($classReflector, $key, $classObj,
is_array($key) ? [] : null);
// -- следующий элемент
continue;
}
// - присваиваю значение
self::SetParameterToClass($classReflector, $key, $classObj, $value);
}
// Возвращаю объект класса
return $classObj;
}
catch (Exception $exception) {
throw new Exception($exception->getMessage());
}
}
/**
* Присваивает значение параметра объекту класса.
*
* @param ReflectionClass $classReflector Рефлектор класса.
* @param string $propertyName Имя свойства.
* @param mixed $classObj Объект класса.
* @param mixed $value Значение.
*
* @throws Exception
*/
public static function SetParameterToClass (ReflectionClass $classReflector, string $propertyName,
mixed $classObj, mixed $value): void
{
try {
// Получаю свойство
$property = $classReflector->getProperty($propertyName);
/**
* Устанавливаю доступ значения свойства.
*
* @noinspection PhpExpressionResultUnusedInspection
*/
$property->setAccessible(true);
// Если значение null
if ($value == null || $value == "null")
// - то присваиваю значение по умолчанию
$value = self::GetDefaults($property->getType()->getName());
// Присваиваю значение
$property->setValue($classObj, $value);
}
catch (ReflectionException $exception) {
// Выбрасываю исключение
throw new Exception($exception->getMessage());
}
}
/**
* Получает значение по умолчанию для разных типов данных.
*
* @param string $typeName Имя типа данных.
*
* @return mixed|null Результат.
*/
public static function GetDefaults (string $typeName): mixed
{
return match ($typeName) {
'int' => 0,
'float' => 0.0,
'bool' => false,
'string' => '',
'array' => [],
'object' => new stdClass(),
default => null,
};
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace goodboyalex\php_components_pack\classes;
use ArrayAccess;
use Countable;
use goodboyalex\php_components_pack\interfaces\ISerializable;
use goodboyalex\php_components_pack\traits\ObjectArray\ObjectArrayBasicTrait;
use goodboyalex\php_components_pack\traits\ObjectArray\ObjectArrayConstantsTrait;
use goodboyalex\php_components_pack\traits\ObjectArray\ObjectArrayLINQTrait;
use goodboyalex\php_components_pack\traits\ObjectArray\ObjectArraySearchAndSortTrait;
use IteratorAggregate;
/**
* Класс, описывающий массив объектов.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
*/
final class ObjectArray implements ArrayAccess, IteratorAggregate, Countable, ISerializable
{
/**
* @var array $Container Массив объектов, хранящихся в данном классе.
*/
private array $Container;
// Реализация наследуемых интерфейсов и классов
use ObjectArrayBasicTrait;
// Константы
use ObjectArrayConstantsTrait;
// Поиск элемента
use ObjectArraySearchAndSortTrait;
// LINQ-подобные методы
use ObjectArrayLINQTrait;
/**
* Конструктор класса.
*
* @param array $array Массив объектов, который будет храниться в данном классе.
*/
public function __construct (array $array = [])
{
$this->Container = $array;
}
/**
* Возвращает массив объектов, хранящихся в данном классе.
*
* @return array Массив объектов, хранящихся в данном классе.
*/
public function ToArray (): array
{
return $this->Container;
}
}