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;
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace goodboyalex\php_components_pack\enums;
use goodboyalex\php_components_pack\traits\EnumExtensionsTrait;
/**
* Перечисление типов сообщений.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
*/
enum MessageType: int
{
// Подключаю расширение для Enum
use EnumExtensionsTrait;
/**
* Успех
*/
case Success = 0;
/**
* Информация
*/
case Info = 1;
/**
* Предупреждение
*/
case Warning = 2;
/**
* Ошибка
*/
case Error = 3;
}

View File

@@ -0,0 +1,60 @@
<?php
namespace goodboyalex\php_components_pack\extensions;
/**
* Расширение массивов.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
*/
final class ArrayExtension
{
/**
* Удаляет пустые строки в массиве.
*
* @param array $array Исходный массив
* @param bool $reOrder Переиндексировать массив
*
* @return array Результирующим массив без пустых строк
*/
public static function RemoveEmpties (array $array, bool $reOrder = false): array
{
// Удаляем пустые строки
$result = array_filter($array, fn ($value) => !is_null($value) && $value !== '');
// Переиндексируем массив
if ($reOrder)
$result = array_values($result);
// Возвращаем результат
return $result;
}
/**
* Выделяет в смешанном массиве ассоциативный массив
*
* @param array $array Исходный массив
*
* @return array Ассоциативный массив, содержащийся в исходном массиве
*/
public static function GetAssociativePart (array $array): array
{
// Фильтруем массив, оставляя только элементы со строковыми ключами
return array_filter($array, fn ($key) => self::IsStringKey($key), ARRAY_FILTER_USE_KEY);
}
/**
* Функция для проверки, является ли ключ строковым
*
* @param mixed $key Ключ
*
* @return bool Является ли ключ строковым типом
*/
public static function IsStringKey (mixed $key): bool
{
return !is_int($key);
}
}

View File

@@ -0,0 +1,147 @@
<?php
namespace goodboyalex\php_components_pack\extensions;
use Random\RandomException;
/**
* Расширение Guid.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
*/
final class GUIDExtension
{
/**
* Пустой Guid.
*/
public const string GUIDEmpty = "00000000-0000-0000-0000-000000000000";
/**
* Генерирует Guid.
*
* @return string Сгенерированный Guid.
*/
public static function Generate (): string
{
// Цикл создания Guid
do
$guid = self::DoGenerate();
// - пока Guid не будет корректен
while (!self::Validate($guid));
// Возвращаем Guid
return $guid;
}
/**
* Генерирует Guid.
*
* @return string Сгенерированный Guid.
*/
private static function DoGenerate (): string
{
try {
return sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
// 32 bits for "time_low"
random_int(0, 0xffff),
random_int(0, 0xffff),
// 16 bits for "time_mid"
random_int(0, 0xffff),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 4
random_int(0, 0x0fff) | 0x4000,
// 16 bits, 8 bits for "clk_seq_hi_res",
// 8 bits for "clk_seq_low",
// two most significant bits holds zero and one for variant DCE1.1
random_int(0, 0x3fff) | 0x8000,
// 48 bits for "node"
random_int(0, 0xffff),
random_int(0, 0xffff),
random_int(0, 0xffff)
);
}
catch (RandomException) {
return self::GUIDEmpty;
}
}
/**
* Проверяет Guid на корректность.
*
* @param string|null $str Guid на проверку
*
* @return bool Корректен ли Guid.
*/
public static function Validate (?string $str): bool
{
// Если Guid пустой
if (StringExtension::IsNullOrWhitespace($str))
// - возвращаем false
return false;
// Проверяем длину
$isLenCorrect = strlen($str) == 36;
// Если длина не корректна
if (!$isLenCorrect)
// - возвращаем false
return false;
// Разбиваем на части
$explodedStr = explode("-", $str);
// Если количество частей не равно 5
if (count($explodedStr) !== 5)
// - возвращаем false
return false;
// Проверяем длину каждой части
// - первая часть должна быть длиной 8 символов
if (strlen($explodedStr[0]) !== 8)
// -- возвращаем false
return false;
// - вторая часть должна быть длиной 4 символа
if (strlen($explodedStr[1]) !== 4)
// -- возвращаем false
return false;
// - третья часть должна быть длиной 4 символа
if (strlen($explodedStr[2]) !== 4)
// -- возвращаем false
return false;
// - четвертая часть должна быть длиной 4 символа
if (strlen($explodedStr[3]) !== 4)
// -- возвращаем false
return false;
// - пятая часть должна быть длиной 12 символов
if (strlen($explodedStr[4]) !== 12)
// -- возвращаем false
return false;
// Проверка пройдена
return true;
}
/**
* Проверяет Guid на пустоту.
*
* @param string|null $str Guid на проверку
*
* @return bool Пустой ли GUID
*/
public static function IsNotValidOrEmpty (?string $str): bool
{
return !self::Validate($str) || $str == self::GUIDEmpty;
}
}

View File

@@ -0,0 +1,210 @@
<?php
namespace goodboyalex\php_components_pack\extensions;
/**
* Расширение строк.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
*/
final class StringExtension
{
/**
* @var array|string[] $RussianLetters Набор русских букв
*/
private static array $RussianLetters =
[
'а', 'б', 'в', 'г', 'д', 'е', 'ё', 'ж', 'з', 'и', 'й',
'к', 'л', 'м', 'н', 'о', 'п', 'р', 'с', 'т', 'у', 'ф',
'х', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы', 'ь', 'э', 'ю', 'я',
'А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ё', 'Ж', 'З', 'И', 'Й',
'К', 'Л', 'М', 'Н', 'О', 'П', 'Р', 'С', 'Т', 'У', 'Ф',
'Х', 'Ц', 'Ч', 'Ш', 'Щ', 'Ъ', 'Ы', 'Ь', 'Э', 'Ю', 'Я'
];
/**
* @var array|string[] $RussianLettersTransliteration Набор русских букв в транслитерации
*/
private static array $RussianLettersTransliteration =
[
"a", "b", "v", "g", "d", "e", "yo", "zh", "z", "i", "j",
"k", "l", "m", "n", "o", "p", "r", "s", "t", "u", "f",
"h", "c", "ch", "sh", "sch", "j", "i", "j", "e", "yu", "ya",
"A", "B", "V", "G", "D", "E", "Yo", "Zh", "Z", "I", "J",
"K", "L", "M", "N", "O", "P", "R", "S", "T", "U", "F",
"H", "C", "Ch", "Sh", "Sch", "J", "I", "J", "E", "Yu", "Ya"
];
/**
* Сравнивает две строки.
*
* @param string|null $str1 Первая строка.
* @param string|null $str2 Вторая строка.
* @param bool $ignoreCase Игнорировать регистр.
*
* @return int Результат сравнения. Возвращаемые значения:
*
* -1 | значение $str1 меньше значения $str2.
* 0 | значения $str1 и $str2 равны.
* 1 | значение $str1 больше значения $str2.
*/
public static function Compare (string|null $str1, string|null $str2, bool $ignoreCase = false): int
{
// Если оба пусты
if (self::IsNullOrWhitespace($str1) && self::IsNullOrWhitespace($str2))
// - то равны
return 0;
// Если первый из них не пуст, а второй пуст
if (!self::IsNullOrWhitespace($str1) && self::IsNullOrWhitespace($str2))
// - то первый больше
return 1;
// Если первый из них пуст, а второй не пуст
if (!self::IsNullOrWhitespace($str1) && self::IsNullOrWhitespace($str2))
// - то первый меньше
return -1;
// Если нужно игнорировать регистр
if ($ignoreCase)
// - то сравниваем по "человечески" без учёта регистра
return strnatcasecmp($str1, $str2);
// Иначе сравниваем по "человечески" с учётом регистра
return strnatcmp($str1, $str2);
}
/**
* Проверяет, пуста ли строка, содержит ли вместо текста только пробелы.
*
* @param string|null $str Проверяемая строка.
*
* @return bool Результат проверки.
*/
public static function IsNullOrWhitespace (string|null $str): bool
{
return self::IsNullOrEmpty($str) || trim($str) === '';
}
/**
* Проверяет, пуста ли строка.
*
* @param string|null $str Проверяемая строка.
*
* @return bool Результат проверки.
*/
public static function IsNullOrEmpty (string|null $str): bool
{
return is_null($str) || $str === '';
}
/**
* Конвертация в латиницу.
*
* @param string $source Исходное слово или выражение
*
* @return string Транслитерированное слово или выражение
*/
public static function ConvertToLatin (string $source): string
{
// Создаю результат
$result = "";
// Длина исходного слова
$length = mb_strlen($source);
// Для каждой буквы или символа из слова
for ($i = 0; $i < $length; $i++) {
// - получаю букву или символ
$letter = mb_substr($source, $i, 1);
// - если это буква из русского алфавита
if (self::IsRussianLetter($letter)) {
// -- транслитерирую эту букву
$resultTransliteration = self::TransliterationFromRussian($letter);
// -- если транслитерация не удалась
if ($resultTransliteration === false)
// --- вывожу оригинальную букву
$result = $result . $letter;
else
// --- вывожу транслитерированную букву
$result = $result . $resultTransliteration;
// -- и иду к следующей букве или символу
continue;
}
// - если до сих пор буква или символ не обработаны, то возвращаю их
$result = $result . $letter;
}
// Вывожу результат
return $result;
}
/**
* Проверяет, является ли буква русской.
*
* @param string $letter Буква
*
* @return bool Является ли буква русской
*/
private static function IsRussianLetter (string $letter): bool
{
return in_array($letter, self::$RussianLetters);
}
/**
* Получаю транслитерированную букву русского алфавита.
*
* @param string $letter Буква
*
* @return false|string Транслитерированная буква или false, если массивы не совпадают или буква не содержится
* в массивах.
*/
private static function TransliterationFromRussian (string $letter): false|string
{
// Если размерность массивов разная
if (count(self::$RussianLetters) !== count(self::$RussianLettersTransliteration))
// - то вывожу ошибку
return false;
// Получаю индекс буквы
$ind = array_search($letter, self::$RussianLetters, true);
// Если буква не найдена
if ($ind === false)
// - то вывожу ошибку
return false;
// Получаю транслитерированную букву
return self::$RussianLettersTransliteration[$ind];
}
/**
* Обрезает строку до указанных в параметре $maxLength символов
*
* @param string $text Исходный текст
* @param int $maxLength Максимальная длина текста
* @param string $endDots Суффикс, которым завершается обрезанная строка
*
* @return string Обрезанный текст
*/
public static function GetShortText (string $text, int $maxLength, string $endDots = ''): string
{
// Если длина текста меньше максимальной
if (mb_strlen($text) <= $maxLength)
// - то возвращаю исходный текст
return $text;
// Если длина текста больше максимальной, то получаю длину текста без суффикса
$lengthWithoutEndDots = $maxLength - mb_strlen($endDots);
// Возвращаю обрезанный текст
return mb_substr($text, 0, $lengthWithoutEndDots) . $endDots;
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace goodboyalex\php_components_pack\interfaces;
/**
* Интерфейс поддержки правильной сериализации объектов.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
*/
interface ISerializable
{
/**
* Сериализация модели
*
* @return string Сериализованная модель
*/
public function Serialize (): string;
/**
* Десериализует модель
*
* @param string $serialized Сериализованная модель
*
* @return void
*/
public function UnSerialize (string $serialized): void;
}

View File

@@ -0,0 +1,108 @@
<?php
namespace goodboyalex\php_components_pack\models;
use Exception;
use goodboyalex\php_components_pack\classes\ClassMapper;
use goodboyalex\php_components_pack\enums\MessageType;
use goodboyalex\php_components_pack\interfaces\ISerializable;
use UnitEnum;
/**
* Класс сообщения состояния.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
*/
final class ActionStateMessageModel implements ISerializable
{
/**
* @var MessageType $MessageType Тип сообщения
*/
public MessageType $MessageType;
/**
* @var bool $IsCritical Критичность сообщения
*/
public bool $IsCritical;
/**
* @var string $Message Текст сообщения
*/
public string $Message;
/**
* Конструктор
*
* @param MessageType $type Тип сообщения
* @param bool $isCritical Критичность сообщения
* @param string $message Текст сообщения
*/
public function __construct (MessageType $type = MessageType::Info, bool $isCritical = false, string $message = "")
{
$this->MessageType = $type;
$this->IsCritical = $isCritical;
$this->Message = $message;
}
/**
* @inheritDoc
*/
public function UnSerialize (string $serialized): void
{
// Десериализую массив
$thisNew = unserialize($serialized);
try {
// Получаю класс
$class = ClassMapper::MapToClassProperty(get_class($this), $thisNew);
}
catch (Exception) {
$class = new ActionStateMessageModel();
}
// Заполняю текущий класс
ClassMapper::MapClass($class, $this);
}
/**
* @inheritDoc
*/
public function Serialize (): string
{
// Создаю массив результата
$result = [];
// Получаю свойства класса
$properties = get_object_vars($this);
// Для каждого свойства класса
foreach ($properties as $key => $value) {
// - если значение является перечислением
if ($value instanceof UnitEnum) {
// -- получаю перечисление
$result[$key] = $value->name;
// -- следующий элемент
continue;
}
// - если значение является NULL
if ($value == null) {
// -- присваиваю NULL
$result[$key] = "null";
// -- следующий элемент
continue;
}
// - присваиваю значение
$result[$key] = $value;
}
// Сериализую массив и вывожу его
return serialize($result);
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace goodboyalex\php_components_pack\traits;
use goodboyalex\php_components_pack\enums\MessageType;
use InvalidArgumentException;
use ReflectionEnum;
use ReflectionException;
/**
* Расширение перечислений.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
*/
trait EnumExtensionsTrait
{
/**
* Получает значение перечисления по его имени.
*
* @param string $name Имя значения перечисления
*
* @return MessageType|EnumExtensionsTrait Перечисление
*/
public static function FromName (string $name): self
{
// Создаём экземпляр
$reflection = new ReflectionEnum(static::class);
// Проверяем, есть ли такая переменная
if (!$reflection->hasCase($name))
// - если нет - ошибка
throw new InvalidArgumentException(sprintf("Enumeration name '%s' does not exist!", $name));
try {
$result = $reflection->getCase($name)->getValue();
}
catch (ReflectionException) {
$result = new static();
}
return $result;
}
/**
* Получает значение перечисления
*
* @return string Значение перечисления
*/
public function GetValue (): string
{
return $this->name;
}
/**
* Конвертирует в перечисление из int.
*
* @param int $value Значение перечисления в формате int
*
* @return MessageType|EnumExtensionsTrait Объект
* перечисления
*/
public static function FromInt (int $value): self
{
return self::tryFrom($value);
}
/**
* Переводит тип в INT
*
* @return int Значение типа в INT
*/
public function ToInt (): int
{
return $this->value;
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace goodboyalex\php_components_pack\traits\ObjectArray;
use ArrayIterator;
/**
* Часть кода класса ObjectArray, отвечающая за функции из наследуемых интерфейсов и классов.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
*/
trait ObjectArrayBasicTrait
{
/**
* @inheritDoc
*/
public function offsetExists (mixed $offset): bool
{
return isset($this->Container[$offset]);
}
/**
* @inheritDoc
*/
public function offsetGet (mixed $offset): mixed
{
return $this->container[$offset] ?? null;
}
/**
* @inheritDoc
*/
public function offsetSet (mixed $offset, mixed $value): void
{
// Если смещение не указано
if (is_null($offset))
// - то добавляем объект в конец массива.
$this->Container[] = $value;
else
// - иначе добавляем объект по указанному смещению.
$this->Container[$offset] = $value;
}
/**
* @inheritDoc
*/
public function offsetUnset (mixed $offset): void
{
unset($this->Container[$offset]);
}
/**
* @inheritDoc
*/
public function getIterator (): ArrayIterator
{
return new ArrayIterator($this->Container);
}
/**
* Существует ли элемент по указанному смещению.
*
* @param mixed $offset Смещение.
*
* @return bool Существует ли элемент по указанному смещению.
*/
public function __isset (mixed $offset): bool
{
return isset($this->data[$offset]);
}
/**
* @inheritDoc
*/
public function Serialize (): string
{
return serialize($this->Container);
}
/**
* @inheritDoc
*/
public function UnSerialize (string $serialized): void
{
$this->Container = unserialize($serialized);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace goodboyalex\php_components_pack\traits\ObjectArray;
use Closure;
/**
* Часть кода класса ObjectArray, отвечающая за константы.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
*/
trait ObjectArrayConstantsTrait
{
/**
* @var Closure Функция сравнения объектов по умолчанию.
*/
public Closure $DefaultComparerFunction {
get {
return fn (mixed $currentPropertyValue, mixed $searchPropertyValue): bool
=> $currentPropertyValue === $searchPropertyValue;
}
}
}

View File

@@ -0,0 +1,283 @@
<?php
namespace goodboyalex\php_components_pack\traits\ObjectArray;
use goodboyalex\php_components_pack\classes\ObjectArray;
/**
* Часть кода класса ObjectArray, отвечающая за LINQ-подобные операции.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
*/
trait ObjectArrayLINQTrait
{
/**
* Возвращает объект, значение свойства которого минимально.
*
* @param callable $itemValuePredicate Функция, возвращающая значение свойства объекта.
* Параметром является объект массива.
*
* @return mixed|null Объект, значение свойства которого минимально
*/
public function MinBy (callable $itemValuePredicate): mixed
{
return array_reduce($this->Container,
fn ($min, $item) => $min === null || $itemValuePredicate($item) < $itemValuePredicate($min) ? $item : $min);
}
/**
* Возвращает объект, значение свойства которого максимально.
*
* @param callable $itemValuePredicate Функция, возвращающая значение свойства объекта.
* Параметром является объект массива.
*
* @return mixed|null Объект, значение свойства которого максимально.
*/
public function MaxBy (callable $itemValuePredicate): mixed
{
return array_reduce($this->Container,
fn ($max, $item) => $max === null || $itemValuePredicate($item) > $itemValuePredicate($max) ? $item : $max);
}
/**
* Выделяет из массива объектов объект, удовлетворяющий условию. Если объектов несколько, то выбирается первый
* встречающийся объект из них. Если объектов нет, вернёт false.
*
* @param callable|null $selectCondition Функция выборки объектов <code>fn (mixed $item): bool</code>. Вместо неё
* можно использовать null, тогда функция выберет текущий объект. По умолчанию, null.
*
* @return false|mixed Выбранный объект, удовлетворяющий условию, или false, если таких нет.
*/
public function GetRow (?callable $selectCondition = null): mixed
{
// Если условие не задано
if ($selectCondition === null || !is_callable($selectCondition)) {
// - то если массив не пуст
if (count($this->Container) > 0)
// -- возвращаем первый элемент
return $this->Container[0];
else
// -- иначе возвращаем false
return false;
}
// Получаем результаты выборки из массива по условию $selectCondition
$result = $this->GetRows($selectCondition);
// Если результат не найден
if (count($result) == 0)
// - возвращаем false
return false;
// Возвращаем результат
return $result[0];
}
/**
* Выделяет из массива объектов объекты, удовлетворяющие условию.
*
* @param callable|null $selectCondition Функция выборки объектов <code>fn (mixed $item): bool</code>. Вместо неё
* можно использовать null, тогда функция будет выбирать все объекты. По умолчанию, null.
*
* @return ObjectArray Массив объектов, удовлетворяющих условию
*/
public function GetRows (?callable $selectCondition = null): ObjectArray
{
// Если условие не задано
if ($selectCondition === null || !is_callable($selectCondition))
// - то возвращаем все элементы
return new ObjectArray($this->Container);
// Создаём результирующий массив
$result = [];
// Проходим по массиву
foreach ($this->Container as $item) {
// - пропускаем не объекты
if (!is_object($item))
continue;
// - если объект удовлетворяет условию
if ($selectCondition($item))
// -- добавляем его в результат
$result[] = $item;
}
// Возвращаем результат
return new ObjectArray($result);
}
/**
* Получение значение единичного поля. Если полей по выборке будет больше одного, то вернёт первое из них.
*
* @param string $column Требуемый столбец
* @param callable|null $wherePredicate Условие выборки <code>fn (mixed $item): bool</code>, которое проверяет,
* подходит элемент или нет.
*
* @return mixed|null Результат запроса или null в случае ошибки
*/
public function GetValue (string $column, ?callable $wherePredicate = null): mixed
{
// Получаю колонку
$result = $this->GetColumn($column, $wherePredicate);
// Если результат пуст
if (count($result) == 0)
// -- возвращаю null
return null;
// Возвращаю результат
return $result[0];
}
/**
* Получает колонку в массиве данных.
*
* @param string $column Имя колонки.
* @param callable|null $wherePredicate Условие выборки <code>fn (mixed $item): bool</code>, которое проверяет,
* подходит элемент или нет.
*
* @return array Ассоциированный массив с результатом выборки.
*/
public function GetColumn (string $column, ?callable $wherePredicate = null): array
{
// Создаю результат
$result = [];
// Прохожу по массиву
foreach ($this->Container as $item) {
// - пропускаю не объекты
if (!is_object($item))
continue;
// - пропускаю не имеющие требуемого свойства
if (!property_exists($item, $column))
continue;
// - пропускаю не удовлетворяющие условию
if ($wherePredicate !== null && !$wherePredicate($item))
continue;
// - добавляю значение свойства в результат
$result[] = $item->$column;
}
// Возвращаю результат
return $result;
}
/**
* Заменяет данные в строке\ массива.
*
* @param mixed $setItem Заменяемые элемент.
* @param callable $wherePredicate Условие выборки <code>fn (mixed $item): bool</code>, которое проверяет,
* подходит элемент или нет.
*
* @return bool Результат выполнения
*/
public function Update (array $setItems, callable $wherePredicate): bool
{
// Удаляю элементы
$result = $this->Delete($wherePredicate);
// Добавляю новые элементы
foreach ($setItems as $item)
$this->Container[] = $item;
// Возвращаю результат
return $result;
}
/**
* Удаляет строки по условию.
*
* @param callable|null $wherePredicate Условие выборки <code>fn (mixed $item): bool</code>, которое проверяет,
* подходит элемент или нет.
*
* @return bool Результат выполнения. Будет true, если все строки, удовлетворяющие условию удалены успешно,
* иначе false.
*/
public function Delete (?callable $wherePredicate = null): bool
{
// Задаю результат
$result = true;
// Получаю массив строк
$array = $this->GetRows($wherePredicate);
// Каждый элемент в этих строках
foreach ($array as $item) {
// - получаю его индекс в основном массиве
$key = array_search($item, $this->Container);
// - и если индекс найден
if ($key !== false) {
// -- удаляю элемент из основного массива
$resultArray = array_splice($this->Container, $key, 1);
// -- и если удаление прошло успешно, то будет true
$result = $result && count($resultArray) > 0;
}
}
// Возвращаю результат
return $result;
}
/**
* Проверяет, существует ли объект в массиве.
*
* @param callable|null $where Условие выборки <code>fn (mixed $item): bool</code>, которое проверяет, подходит ли
* объект или нет. Может принимать вместо замыкания null, тогда вернёт false. По умолчанию null.
*
* @return bool Результат проверки
*/
public function IsExist (?callable $where = null): bool
{
// Если условие не задано
if ($where === null || !is_callable($where))
// - то возвращаем false
return false;
// Вывожу результат
return $this->Count($where) > 0;
}
/**
* Считает количество объектов в массиве, удовлетворяющих условию.
*
* @param callable|null $predicate Функция, возвращающая bool: true, если объект удовлетворяет условию, false, если
* не удовлетворяет. Параметром является объект массива. Вместо функции можно передать null, тогда результатом
* будет общее количество объектов в массиве. По умолчанию, null.
*
* @return int Количество объектов, удовлетворяющих условию
*/
public function Count (?callable $predicate = null): int
{
// Если условие не задано
if ($predicate === null || !is_callable($predicate))
// - то возвращаем общее количество объектов
return count($this->Container);
// Создаём результирующее число
$result = 0;
// Проходим по массиву
foreach ($this->Container as $item) {
// - пропускаем не объекты
if (!is_object($item))
continue;
// - если элемент удовлетворяет условию
if ($predicate($item))
// -- добавляем счётчик
$result++;
}
// Возвращаем результат
return $result;
}
}

View File

@@ -0,0 +1,129 @@
<?php
namespace goodboyalex\php_components_pack\traits\ObjectArray;
/**
* Часть кода класса ObjectArray, отвечающая за поиск и исключение объектов.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
*/
trait ObjectArraySearchAndSortTrait
{
/**
* Сортирует массив объектов, по пользовательской функции.
*
* @param callable $objectPropertyValuePredicate Функция <code>fn (mixed $item): mixed => $item-><PROPERTY></code>,
* возвращающая сравниваемый объект. Возвращаемое значение должно быть значением свойства этого объекта.
*
* <code>
* class Demo;
* {
* public string $Property1;
* public string $Property2;
* public function __construct(string $Property1, string $Property2)
* {
* $this->Property1 = $Property1;
* $this->Property2 = $Property2;
* }
* }
* $array = new ObjectArray (
* [
* new Demo('1', '2'),
* new Demo('5', '6'),
* new Demo('3', '4')
* ]);
*
* // output:
* // [Demo('1', '2'), Demo('3', '4'), Demo('5', '6')]
* $result = $array->SortCallback($array, fn ($item): mixed => $item->Property1);
* </code>
* @param bool $descending Направление сортировки
*
* @return array Отсортированный массив объектов
*/
public function SortCallback (callable $objectPropertyValuePredicate, bool $descending = false): array
{
// Создаём результирующий массив
$result = array_merge($this->Container, []);
// Сортируем массив
usort($result,
fn ($a, $b)
=> !$descending
? $objectPropertyValuePredicate($a) <=> $objectPropertyValuePredicate($b)
: $objectPropertyValuePredicate($b) <=> $objectPropertyValuePredicate($a));
// Возвращаем результат
return $result;
}
/**
* Сортирует массив объектов, по значению свойства объекта.
*
* @param string $objectProperty Имя свойства объекта
* @param bool $descending Направление сортировки
*
* @return array Отсортированный массив объектов
*/
public function Sort (string $objectProperty, bool $descending = false): array
{
// Создаём результирующий массив
$result = array_merge($this->Container, []);
// Сортируем массив
usort($result,
fn ($a, $b)
=> !$descending
? $a->$objectProperty <=> $b->$objectProperty
: $b->$objectProperty <=> $a->$objectProperty);
// Возвращаем результат
return $result;
}
/**
* Поиск объекта в массиве объектов по значению свойства объекта.
*
* @param string $forProperty Имя искомого свойства объекта.
* @param mixed $searchValue Искомое значение свойства объекта.
* @param callable|null $compareFunction Функция сравнения, принимающая два параметра: mixed $currentPropertyValue
* (текущее значение свойства) и mixed $searchPropertyValue (поисковое значение свойства) и возвращает bool -
* true, если значение свойства совпадает с искомым, false - если не совпадает. Если передаётся null, то будет
* использоваться DefaultComparerFunction. По умолчанию, null.
*
* @return mixed Объект, найденный по значению свойства или null, если не найден
*/
public function Search (string $forProperty, mixed $searchValue, ?callable $compareFunction = null): mixed
{
// Проходим по массиву
foreach ($this->Container as $item) {
// - пропускаем не объекты
if (!is_object($item))
continue;
// - пропускаем, если значение свойства нет в этом объекте
if (!property_exists($item, $forProperty))
continue;
// - пропускаем, если значение свойства не задано
if (!isset($item->$forProperty))
continue;
// - если не задана функция сравнения
if (is_null($compareFunction) || !is_callable($compareFunction))
// -- то устанавливаем функцию сравнения по умолчанию
$compareFunction = $this->DefaultComparerFunction;
// - если значение свойства совпадает с искомым
if ($compareFunction($item->$forProperty, $searchValue))
// -- возвращаем объект
return $item;
}
// Если мы сюда дошли, значить объект не найден - возвращаем null
return null;
}
}