1 Commits
2.3.2 ... 2.1.1

Author SHA1 Message Date
24fe47f1e7 fix postfix private 2023-11-18 01:23:21 +07:00
10 changed files with 501 additions and 716 deletions

0
.gitignore vendored Executable file → Normal file
View File

11
LICENSE
View File

@@ -1,11 +0,0 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

View File

@@ -1,64 +0,0 @@
The MINIMAL framework that does **not limit your project with its own rules**, has **no dependencies**, implements the **best practices** of popular MVC-frameworks, it **VERY fast** and **optimized** for all the innovations in **PHP 8.2** 🤟
Can be configured to work with **any database** `core::$session` and **any HTML template engine** `$this->view`
*personally, i prefer **ArangoDB** and **Twig***
## Nearest plans (first half of 2025)
1. Add **middlewares** technology
2. Route sorting in the router `router::sort()`
3. Add trigger routes from within routes
4. Think about adding asynchronous executions
5. Write an article describing the principles of the framework
## Installation
Execute: `composer require mirzaev/minimal`
## Usage
*index.php*
```php
// Initializing the router
$router = new router;
// Initializing of routes
$router
->write('/', 'catalog', 'index', 'GET')
->write('/search', 'catalog', 'search', 'POST')
->write('/session/connect/telegram', 'session', 'telegram', 'POST')
->write('/product/$id', 'catalog', 'product', 'POST')
->write('/$categories...', 'catalog', 'index', 'POST'); // Collector (since 0.3.0)
// Initializing the core
$core = new core(namespace: __NAMESPACE__, router: $router, controller: new controller(false), model: new model(false));
// Handle the request
echo $core->start();
```
## Examples of projects based on MINIMAL
### ebala
**Repository:** https://git.mirzaev.sexy/mirzaev/ebala<br>
**Github mirror:** https://github.com/mature-woman/ebala<br>
*I earned more than a **million rubles** from this project*<br>
*Repositories **may** be closed at the request of the customer*<br>
### huesos
**Repository:** https://git.mirzaev.sexy/mirzaev/huesos<br>
**Guthub mirror:** https://github.com/mature-woman/huesos<br>
*The basis for developing chat-robots with Web App technology (for example for Telegram)*<br>
### arming_bot
**Repository:** https://git.mirzaev.sexy/mirzaev/arming_bot<br>
**Guthub mirror:** https://github.com/mature-woman/arming_bot<br>
*Chat-robot based on huesos*<br>
### notchat
**Repository:** https://git.mirzaev.sexy/mirzaev/notchat<br>
**Github mirror:** https://github.com/mature-woman/notchat<br>
*P2P chat project with different blockchains and smart stuff*<br>
### site-repression
**Link:** https://repression.mirzaev.sexy<br>
**Repository:** https://git.mirzaev.sexy/mirzaev/site-repression<br>
**Github mirror:** https://github.com/mature-woman/site-repression<br>
*A simple site for my article about **political repressions in Russia** and my **kidnapping by Wagner PMC operatives** from my house*<br>

0
composer.json Executable file → Normal file
View File

0
composer.lock generated Executable file → Normal file
View File

88
mirzaev/minimal/system/controller.php Executable file → Normal file
View File

@@ -4,50 +4,47 @@ declare(strict_types=1);
namespace mirzaev\minimal; namespace mirzaev\minimal;
// Files of the project // Файлы проекта
use mirzaev\minimal\model, use mirzaev\minimal\model;
mirzaev\minimal\traits\magic;
// Встроенные библиотеки // Встроенные библиотеки
use exception; use exception;
/** /**
* Controller (base) * Контроллер
* *
* @package mirzaev\minimal * @package mirzaev\minimal
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
class controller class controller
{ {
use magic;
/** /**
* Postfix of file names * Постфикс
*/ */
public const string POSTFIX = '_controller'; public const POSTFIX = '_controller';
/** /**
* Instance of the model connected in the router * Инстанция модели
*/ */
protected model $model; protected model $model;
/** /**
* View template engine instance (twig) * Инстанция шаблонизатора представления
*/ */
protected object $view; protected object $view;
/** /**
* Constructor * Конструктор
*/ */
public function __construct() {} public function __construct()
{
}
/** /**
* Write property * Записать свойство
* *
* @param string $name Name of the property * @param string $name Название
* @param mixed $value Value of the property * @param mixed $value Значение
* *
* @return void * @return void
*/ */
@@ -55,40 +52,71 @@ class controller
{ {
match ($name) { match ($name) {
'model' => (function () use ($value) { 'model' => (function () use ($value) {
if ($this->__isset('model')) throw new exception('Can not reinitialize property: ' . static::class . '::$model', 500); if ($this->__isset('model')) throw new exception('Запрещено реинициализировать свойство с инстанцией модели ($this->model)', 500);
else { else {
// Property not initialized // Свойство не инициализировано
if (is_object($value)) $this->model = $value; if (is_object($value)) $this->model = $value;
else throw new exception('Property "' . static::class . '::view" should store an instance of a model', 500); else throw new exception('Свойство $this->model должно хранить инстанцию модели (объект)', 500);
} }
})(), })(),
'view' => (function () use ($value) { 'view' => (function () use ($value) {
if ($this->__isset('view')) throw new exception('Can not reinitialize property: ' . static::class . '::$view', 500); if ($this->__isset('view')) throw new exception('Запрещено реинициализировать свойство с инстанцией шаблонизатора представления ($this->view)', 500);
else { else {
// Property not initialized // Свойство не инициализировано
if (is_object($value)) $this->view = $value; if (is_object($value)) $this->view = $value;
else throw new exception('Property "' . static::class . '::view" should store an instance of a view template engine', 500); else throw new exception('Свойство $this->view должно хранить инстанцию шаблонизатора представления (объект)', 500);
} }
})(), })(),
default => throw new exception('Property "' . static::class . "::\$$name\" not found", 404) default => throw new exception("Свойство \"\$$name\" не найдено", 404)
}; };
} }
/** /**
* Read property * Прочитать свойство
* *
* @param string $name Name of the property * @param string $name Название
* *
* @return mixed Value of the property * @return mixed Содержимое
*/ */
public function __get(string $name): mixed public function __get(string $name): mixed
{ {
return match ($name) { return match ($name) {
'model' => $this->model ?? throw new exception('Property "' . static::class . '::$model" is not initialized', 500), 'model' => $this->model ?? throw new exception("Свойство \"\$model\" не инициализировано", 500),
'view' => $this->view ?? throw new exception('Property "' . static::class . '::$view" is not initialized', 500), 'view' => $this->view ?? throw new exception("Свойство \"\$view\" не инициализировано", 500),
default => throw new exception('Property "' . static::class . "::\$$name\" not found", 404) default => throw new exception("Свойство \"\$$name\" не обнаружено", 404)
};
}
/**
* Проверить свойство на инициализированность
*
* @param string $name Название
*
* @return bool Инициализировано свойство?
*/
public function __isset(string $name): bool
{
return match ($name) {
default => isset($this->{$name})
};
}
/**
* Удалить свойство
*
* @param string $name Название
*
* @return void
*/
public function __unset(string $name): void
{
match ($name) {
default => (function () use ($name) {
// Удаление
unset($this->{$name});
})()
}; };
} }
} }

15
mirzaev/minimal/system/core.php Executable file → Normal file
View File

@@ -14,12 +14,13 @@ use exception,
ReflectionClass as reflection; ReflectionClass as reflection;
/** /**
* Core * Ядро
* *
* @package mirzaev\minimal * @package mirzaev\minimal
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*
* @todo
* 1. Добавить __isset() и __unset()
*/ */
final class core final class core
{ {
@@ -80,16 +81,18 @@ final class core
* Деструктор * Деструктор
* *
*/ */
public function __destruct() {} public function __destruct()
{
}
/** /**
* Запуск * Запуск
* *
* @param ?string $uri Маршрут * @param ?string $uri Маршрут
* *
* @return string|int|null Ответ * @return ?string Сгенерированный ответ (HTML, JSON...)
*/ */
public function start(string $uri = null): string|int|null public function start(string $uri = null): ?string
{ {
// Обработка запроса // Обработка запроса
return $this->__get('router')->handle($uri, core: $this); return $this->__get('router')->handle($uri, core: $this);

79
mirzaev/minimal/system/model.php Executable file → Normal file
View File

@@ -4,31 +4,86 @@ declare(strict_types=1);
namespace mirzaev\minimal; namespace mirzaev\minimal;
// Files of the project // Встроенные библиотеки
use mirzaev\minimal\traits\magic;
// Built-in libraries
use exception; use exception;
/** /**
* Model (base) * Модель
* *
* @package mirzaev\minimal * @package mirzaev\minimal
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
class model class model
{ {
use magic; /**
* Постфикс
*/
public const POSTFIX = '_model';
/** /**
* Postfix of file names * Конструктор
*/ */
public const string POSTFIX = '_model'; public function __construct()
{
}
/** /**
* Constructor * Записать свойство
*
* @param string $name Название
* @param mixed $value Содержимое
*
* @return void
*/ */
public function __construct() {} public function __set(string $name, mixed $value = null): void
{
match ($name) {
default => throw new exception("Свойство \"\$$name\" не найдено", 404)
};
}
/**
* Прочитать свойство
*
* @param string $name Название
*
* @return mixed Содержимое
*/
public function __get(string $name): mixed
{
return match ($name) {
default => throw new exception("Свойство \"\$$name\" не обнаружено", 404)
};
}
/**
* Проверить свойство на инициализированность
*
* @param string $name Название
*
* @return bool Инициализировано свойство?
*/
public function __isset(string $name): bool
{
return match ($name) {
default => isset($this->{$name})
};
}
/**
* Удалить свойство
*
* @param string $name Название
*
* @return void
*/
public function __unset(string $name): void
{
match ($name) {
default => (function () use ($name) {
// Удаление
unset($this->{$name});
})()
};
}
} }

273
mirzaev/minimal/system/router.php Executable file → Normal file
View File

@@ -11,8 +11,6 @@ use mirzaev\minimal\core;
* Маршрутизатор * Маршрутизатор
* *
* @package mirzaev\minimal * @package mirzaev\minimal
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class router final class router
@@ -20,8 +18,7 @@ final class router
/** /**
* @var array $router Реестр маршрутов * @var array $router Реестр маршрутов
*/ */
/* protected array $routes = []; */ protected array $routes = [];
public array $routes = [];
/** /**
* Записать маршрут * Записать маршрут
@@ -31,190 +28,112 @@ final class router
* @param ?string $method Вызываемый метод в инстанции контроллера обработчика * @param ?string $method Вызываемый метод в инстанции контроллера обработчика
* @param ?string $request HTTP-метод запроса (GET, POST, PUT...) * @param ?string $request HTTP-метод запроса (GET, POST, PUT...)
* @param ?string $model Инстанция модели (переопределение инстанции модели в $target) * @param ?string $model Инстанция модели (переопределение инстанции модели в $target)
* @param array $variables
* *
* @return static The instance from which the method was called (fluent interface) * @return void
*/ */
public function write( public function write(
string $route, string $route,
string $handler, string $handler,
?string $method = 'index', ?string $method = 'index',
?string $request = 'GET', ?string $request = 'GET',
?string $model = null, ?string $model = null
array $variables = [] ): void {
): static {
// Запись в реестр // Запись в реестр
$this->routes[$route][$request] = [ $this->routes[$route][$request] = [
'controller' => $handler, 'controller' => $handler,
'model' => $model ?? $handler, 'model' => $model ?? $handler,
'method' => $method, 'method' => $method
'variables' => $variables
]; ];
// Exit (success) (fluent interface)
return $this;
} }
/** /**
* Handle a request * Обработать маршрут
* *
* @param string|null $uri URI (protocol://domain/foo/bar) * @param ?string $uri URI запроса (https://domain.com/foo/bar)
* @param string|null $method Method (GET, POST, PUT...) * @param ?string $method Метод запроса (GET, POST, PUT...)
* @param core|null $core Instence of the system core * @param ?core $core Инстанция системного ядра
*
* @return string|int|null Response
*/ */
public function handle( public function handle(?string $uri = null, ?string $method = null, ?core $core = new core): ?string
?string $uri = null, {
?string $method = null, // Инициализация значений по умолчанию
?core $core = new core
): string|int|null {
// Declaration of the registry of routes directoies
$routes = [];
foreach ($this->routes as $route => $data) {
// Iteration over routes
// Search directories of route (explode() creates empty value in array)
preg_match_all('/(^\/$|[^\/]+)/', $route, $data['directories']);
$routes[$route] = $data['directories'][0];
}
if (count($routes) === count($this->routes)) {
// Initialized the registry of routes directoies
// Initializing default values
$uri ??= $_SERVER['REQUEST_URI'] ?? '/'; $uri ??= $_SERVER['REQUEST_URI'] ?? '/';
$method ??= $_SERVER["REQUEST_METHOD"]; $method ??= $_SERVER["REQUEST_METHOD"] ?? 'GET';
// Initializing of URN (/foo/bar) // Инициализация URL запроса (/foo/bar)
$urn = parse_url(urldecode($uri), PHP_URL_PATH); $url = parse_url($uri, PHP_URL_PATH);
// Universalization of URN // Универсализация маршрута
$urn = self::universalize($urn); $url = self::universalize($url);
// Search directories of URN (explode() creates empty value in array) // Сортировка реестра маршрутов от большего ключа к меньшему (кешируется)
preg_match_all('/(^\/$|[^\/]+)/', $urn, $directories); krsort($this->routes);
// Поиск директорий в ссылке
preg_match_all('/[^\/]+/', $url, $directories);
// Инициализация директорий
$directories = $directories[0]; $directories = $directories[0];
/**
* Initialization of the route
*/
// Initializing the buffer of matches of route directories with URN directories
$matches = [];
foreach ($directories as $i => $urn_directory) {
// Iteration over directories of URN
foreach ($this->routes as $route => $data) { foreach ($this->routes as $route => $data) {
// Iteration over routes // Перебор маршрутов
if (isset($data[$method])) { // Универсализация маршрута
// The request method matches the route method
// Universalization of route
$route = self::universalize($route); $route = self::universalize($route);
// Skipping unmatched routes based on results of previous iterations // Поиск директорий
if (isset($matches[$route]) && $matches[$route] === false) continue; preg_match_all('/[^\/]+/', $route, $data['directories']);
// Initializing of route directory // Инициализация директорий
$route_directory = $routes[$route][$i] ?? null; $data['directories'] = $data['directories'][0];
if (isset($route_directory)) { if (count($directories) === count($data['directories'])) {
// Initialized of route directory // Входит в диапазон маршрут (совпадает количество директорий у ссылки и маршрута)
if ($urn_directory === $route_directory) { // Инициализация реестра переменных
// The directory of URN is identical to the directory of route $data['vars'] = [];
// Writing: end of URN directories XNOR end of route directories foreach ($data['directories'] as $index => &$directory) {
$matches[$route] = !(isset($directories[$_i = $i + 1]) xor isset($routes[$route][$_i])); // Перебор директорий
} else if (preg_match('/^\$([a-zA-Z_\x80-\xff]+)$/', $route_directory) === 1) {
// The directory of route is a variable ($variable)
// Writing: end of URN directories XNOR end of route directories if (preg_match('/\$([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]+)/', $directory) === 1) {
$matches[$route] = !(isset($directories[$_i = $i + 1]) xor isset($routes[$route][$_i])); // Директория является переменной (.../$variable/...)
} else if (
!isset($routes[$route][$i + 1])
&& preg_match('/^\$([a-zA-Z_\x80-\xff]+\.\.\.)$/', $route_directory) === 1
) {
// The directory of route is a collector ($variable...)
// AND this is the end of route directories
// Writing
$matches[$route] = 'collector';
} else $matches[$route] = false;
} else if ($matches[$route] === 'collector') {
} else $matches[$route] = false;
}
}
}
// Finding a priority route from match results
foreach ($matches as $route => $match) if ($match !== false) break;
if ($route && !empty($data = $this->routes[$route])) {
// Route found
// Universalization of route
$route = self::universalize($route);
/**
* Initialization of route variables
*/
foreach ($routes[$route] as $i => $route_directory) {
// Iteration over directories of route
if (preg_match('/^\$([a-zA-Z_\x80-\xff]+)$/', $route_directory) === 1) {
// The directory is a variable ($variable)
// Запись в реестр переменных и перещапись директории в маршруте
$data[$method]['variables'][trim($route_directory, '$')] = $directories[$i];
} else if (preg_match('/^\$([a-zA-Z_\x80-\xff]+\.\.\.)$/', $route_directory) === 1) {
// The directory of route is a collector ($variable...)
// Инициализаия ссылки на массив сборщика
$collector = &$data[$method]['variables'][trim($route_directory, '$.')];
// Инициализаия массива сборщика
$collector ??= [];
// Запись в реестр переменных // Запись в реестр переменных
$collector[] = $directories[$i]; $directory = $data['vars'][trim($directory, '$')] = $directories[$index];
foreach (array_slice($directories, ++$i, preserve_keys: true) as &$urn_directory) {
// Перебор директорий URN
// Запись в реестр переменных
$collector[] = $urn_directory;
}
break;
} }
} }
/** // Реиницилазция маршрута
* Initialization of route handlers $route = self::universalize(implode('/', $data['directories']));
*/
// Проверка на пустой маршрут
if (empty($route)) $route = '/';
if (mb_stripos($route, $url, 0, "UTF-8") === 0 && mb_strlen($route, 'UTF-8') <= mb_strlen($url, 'UTF-8')) {
// Идентифицирован маршрут (длина не меньше длины запрошенного URL)
if (array_key_exists($method, $data)) { if (array_key_exists($method, $data)) {
// Идентифицирован метод маршрута (GET, POST, PUT...) // Идентифицирован метод маршрута (GET, POST, PUT...)
// Инициализация обработчиков (controller, model, method) $route = $data[$method];
$handlers = $data[$method];
if (class_exists($controller = $core->namespace . '\\controllers\\' . $handlers['controller'] . $core->controller::POSTFIX)) { if (class_exists($controller = $core->namespace . '\\controllers\\' . $route['controller'] . $core->controller::POSTFIX)) {
// Найден контроллер // Найден контроллер
// Инициализация инстанции ядра контроллера // Инициализация инстанции ядра контроллера
$controller = new $controller; $controller = new $controller;
// Вызов связанного с маршрутом метода и возврат (успех) // Инициализация инстанции ядра модели
return $controller->{$handlers['method']}($handlers['variables'] + $_REQUEST, $_FILES, file_get_contents('php://input')); if (class_exists($model = $core->namespace . '\\models\\' . $route['model'] . $core->model::POSTFIX)) $controller->model = new $model;
// Вызов связанного с маршрутом методв и возврат (успех)
return $controller->{$route['method']}($data['vars'] + $_REQUEST, $_FILES);
} }
} }
// Выход из цикла (провал)
break;
}
} }
} }
@@ -222,68 +141,6 @@ final class router
return $this->error($core); return $this->error($core);
} }
/**
* Sorting routes
*
* 1. Short routes
* 2. Long routes
* 3. Short routes with variables (position of variables from "right" to "left")
* 4. Long routes with variables (position of variables from "right" to "left")
* 5. Short routes with collector
* 6. Long routes with collector
* 7. Short routes with variables and collector (position of variables from "right" to "left" then by amount)
* 8. Long routes with variables and collector (position of variables from "right" to "left")
*
* Добавить чтобы сначала текст потом переменная затем после переменной первыми тексты и в конце перменные опять и так рекурсивно
*
* @return static The instance from which the method was called (fluent interface)
*
* @todo ПЕРЕДЕЛАТЬ ПОЛНОСТЬЮ
*/
public function sort(): static
{
uksort($this->routes, function (string $a, string $b) {
// Sorting routes
// Initialization of string lengths (multibyte-safe)
$length = [
$a => mb_strlen($a),
$b => mb_strlen($b)
];
// Initialization of the presence of variables
$variables = [
$a => preg_match('/\$([a-zA-Z_\x80-\xff]+)(\/|$)/', $a) === 1,
$b => preg_match('/\$([a-zA-Z_\x80-\xff]+)(\/|$)/', $b) === 1
];
// Initialization of the presence of collectors
$collectors = [
$a => preg_match('/\$([a-zA-Z_\x80-\xff]+)\.\.\.$/', $a) === 1,
$b => preg_match('/\$([a-zA-Z_\x80-\xff]+)\.\.\.$/', $b) === 1
];
if ($variables[$a] && !$variables[$b]) return 1;
else if (!$variables[$a] && $variables[$b]) return -1;
else if ($variables[$a] && $variables[$b]) {
} else if ($collectors[$a] && !$collectors[$b]) return 1;
else if (!$collectors[$a] && $collectors[$b]) return -1;
else {
// NOR variables and XAND collectors (both are not found or both are found)
// The routes are the same length (no changes)
if ($length[$a] === $length[$b]) return 0;
// The longer route moves to the end
return $length[$a] > $length[$b] ? 1 : -1;
}
});
// Exit (success) (fluent interface)
return $this;
}
/** /**
* Сгенерировать ответ с ошибкой * Сгенерировать ответ с ошибкой
* *
@@ -302,17 +159,15 @@ final class router
} }
/** /**
* Universalize URN * Универсализировать маршрут
* *
* Always "/" at the beginning and never "/" at the end * @param string $route Маршрут
* *
* @param string &$urn URN (/foo/bar) * @return string Универсализированный маршрут
*
* @return string Universalized URN
*/ */
private function universalize(string $urn): string private function universalize(string $route): string
{ {
// Universalization of URN and exit (success) // Если не записан "/" в начале, то записать, затем, если записан "/" в конце, то удалить
return (string) preg_replace('/(.+)\/$/', '$1', preg_replace('/^([^\/])/', '/$1', $urn)); return preg_replace('/(.+)\/$/', '$1', preg_replace('/^([^\/])/', '/$1', $route));
} }
} }

View File

@@ -1,81 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\minimal\traits;
// Built-in libraries
use exception;
/**
* Trait of magical methods
*
* @method void __set(string $name, mixed $value = null) Write property
* @method mixed __get(string $name) Read property
* @method void __unset(string $name) Delete property
* @method bool __isset(string $name) Check property for initialization
*
* @package mirzaev\minimal\traits
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
trait magic
{
/**
* Write property
*
* @param string $name Name of the property
* @param mixed $value Value of the property
*
* @return void
*/
public function __set(string $name, mixed $value = null): void
{
match ($name) {
default => throw new exception('Property "' . static::class . "::\$$name\" not found", 404)
};
}
/**
* Read property
*
* @param string $name Name of the property
*
* @return mixed Value of the property
*/
public function __get(string $name): mixed
{
return match ($name) {
default => throw new exception('Property "' . static::class . "::\$$name\" not found", 404)
};
}
/**
* Delete property
*
* @param string $name Name of the property
*
* @return void
*/
public function __unset(string $name): void
{
match ($name) {
default => (function () use ($name) {
unset($this->{$name});
})()
};
}
/**
* Check property for initialization
*
* @param string $name Name of the property
*
* @return bool Is the property initialized?
*/
public function __isset(string $name): bool
{
return match ($name) {
default => isset($this->{$name})
};
}
}