From e1a64835565acbc3c887e2c98e65f6755a50eb8a Mon Sep 17 00:00:00 2001 From: Arsen Mirzaev Tatyano-Muradovich Date: Fri, 11 Oct 2024 10:14:33 +0300 Subject: [PATCH] collectors, new router, errors handlers, refactoring --- mirzaev/minimal/system/controller.php | 164 ++++----- mirzaev/minimal/system/core.php | 379 ++++++++++---------- mirzaev/minimal/system/model.php | 87 +---- mirzaev/minimal/system/router.php | 445 ++++++++++++++++-------- mirzaev/minimal/system/traits/magic.php | 81 +++++ 5 files changed, 647 insertions(+), 509 deletions(-) create mode 100755 mirzaev/minimal/system/traits/magic.php diff --git a/mirzaev/minimal/system/controller.php b/mirzaev/minimal/system/controller.php index a393e07..0f65302 100755 --- a/mirzaev/minimal/system/controller.php +++ b/mirzaev/minimal/system/controller.php @@ -4,119 +4,91 @@ declare(strict_types=1); namespace mirzaev\minimal; -// Файлы проекта -use mirzaev\minimal\model; +// Files of the project +use mirzaev\minimal\model, + mirzaev\minimal\traits\magic; // Встроенные библиотеки use exception; /** - * Контроллер + * Controller (base) * * @package mirzaev\minimal + * + * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License * @author Arsen Mirzaev Tatyano-Muradovich */ class controller { - /** - * Постфикс - */ - public const POSTFIX = '_controller'; + use magic; - /** - * Инстанция модели - */ - protected model $model; + /** + * Postfix of file names + */ + public const string POSTFIX = '_controller'; - /** - * Инстанция шаблонизатора представления - */ - protected object $view; + /** + * Instance of the model connected in the router + */ + protected model $model; - /** - * Конструктор - */ - public function __construct() - { - } + /** + * View template engine instance (twig) + */ + protected object $view; - /** - * Записать свойство - * - * @param string $name Название - * @param mixed $value Значение - * - * @return void - */ - public function __set(string $name, mixed $value = null): void - { - match ($name) { - 'model' => (function () use ($value) { - if ($this->__isset('model')) throw new exception('Запрещено реинициализировать свойство с инстанцией модели ($this->model)', 500); - else { - // Свойство не инициализировано + /** + * Constructor + */ + public function __construct() {} - if (is_object($value)) $this->model = $value; - else throw new exception('Свойство $this->model должно хранить инстанцию модели (объект)', 500); - } - })(), - 'view' => (function () use ($value) { - if ($this->__isset('view')) throw new exception('Запрещено реинициализировать свойство с инстанцией шаблонизатора представления ($this->view)', 500); - else { - // Свойство не инициализировано + /** + * 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) { + 'model' => (function () use ($value) { + if ($this->__isset('model')) throw new exception('Can not reinitialize property: ' . static::class . '::$model', 500); + else { + // Property not initialized - if (is_object($value)) $this->view = $value; - else throw new exception('Свойство $this->view должно хранить инстанцию шаблонизатора представления (объект)', 500); - } - })(), - default => throw new exception("Свойство \"\$$name\" не найдено", 404) - }; - } + if (is_object($value)) $this->model = $value; + else throw new exception('Property "' . static::class . '::view" should store an instance of a model', 500); + } + })(), + 'view' => (function () use ($value) { + if ($this->__isset('view')) throw new exception('Can not reinitialize property: ' . static::class . '::$view', 500); + else { + // Property not initialized - /** - * Прочитать свойство - * - * @param string $name Название - * - * @return mixed Содержимое - */ - public function __get(string $name): mixed - { - return match ($name) { - 'model' => $this->model ?? throw new exception("Свойство \"\$model\" не инициализировано", 500), - 'view' => $this->view ?? throw new exception("Свойство \"\$view\" не инициализировано", 500), - default => throw new exception("Свойство \"\$$name\" не обнаружено", 404) - }; - } + 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); + } + })(), + default => throw new exception('Property "' . static::class . "::\$$name\" not found", 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}); - })() - }; - } + /** + * Read property + * + * @param string $name Name of the property + * + * @return mixed Value of the property + */ + public function __get(string $name): mixed + { + return match ($name) { + 'model' => $this->model ?? throw new exception('Property "' . static::class . '::$model" is not initialized', 500), + 'view' => $this->view ?? throw new exception('Property "' . static::class . '::$view" is not initialized', 500), + default => throw new exception('Property "' . static::class . "::\$$name\" not found", 404) + }; + } } diff --git a/mirzaev/minimal/system/core.php b/mirzaev/minimal/system/core.php index 7155d0d..3974a11 100755 --- a/mirzaev/minimal/system/core.php +++ b/mirzaev/minimal/system/core.php @@ -6,225 +6,222 @@ namespace mirzaev\minimal; // Файлы проекта use mirzaev\minimal\router, - mirzaev\minimal\controller, - mirzaev\minimal\model; + mirzaev\minimal\controller, + mirzaev\minimal\model; // Встроенные библиотеки use exception, - ReflectionClass as reflection; + ReflectionClass as reflection; /** - * Ядро + * Core * * @package mirzaev\minimal - * @author Arsen Mirzaev Tatyano-Muradovich * - * @todo - * 1. Добавить __isset() и __unset() + * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License + * @author Arsen Mirzaev Tatyano-Muradovich */ final class core { - /** - * Инстанция соединения с базой данных - */ - private object $db; + /** + * Инстанция соединения с базой данных + */ + private object $db; - /** - * Инстанция маршрутизатора - */ - private readonly router $router; + /** + * Инстанция маршрутизатора + */ + private readonly router $router; - /** - * Инстанция ядра контроллера - */ - private readonly controller $controller; + /** + * Инстанция ядра контроллера + */ + private readonly controller $controller; - /** - * Инстанция ядра модели - */ - private readonly model $model; + /** + * Инстанция ядра модели + */ + private readonly model $model; - /** - * Путь пространства имён (системное) - * - * Используется для поиска файлов по спецификации PSR-4 - */ - private readonly string $namespace; + /** + * Путь пространства имён (системное) + * + * Используется для поиска файлов по спецификации PSR-4 + */ + private readonly string $namespace; - /** - * Конструктор - * - * @param ?object $db Инстанция соединения с базой данных - * @param ?router $router Маршрутизатор - * @param ?controller $controller Инстанция ядра контроллера - * @param ?model $model Инстанция ядра модели - * @param ?string $namespace Пространство имён системного ядра - * - * @return self Инстанция ядра - */ - public function __construct( - ?object $db = null, - ?router $router = null, - ?controller $controller = null, - ?model $model = null, - ?string $namespace = null - ) { - // Инициализация свойств - if (isset($db)) $this->__set('db', $db); - if (isset($router)) $this->__set('router', $router); - if (isset($controller)) $this->__set('controller', $controller); - if (isset($model)) $this->__set('model', $model); - $this->__set('namespace', $namespace ?? (new reflection(self::class))->getNamespaceName()); - } + /** + * Конструктор + * + * @param ?object $db Инстанция соединения с базой данных + * @param ?router $router Маршрутизатор + * @param ?controller $controller Инстанция ядра контроллера + * @param ?model $model Инстанция ядра модели + * @param ?string $namespace Пространство имён системного ядра + * + * @return self Инстанция ядра + */ + public function __construct( + ?object $db = null, + ?router $router = null, + ?controller $controller = null, + ?model $model = null, + ?string $namespace = null + ) { + // Инициализация свойств + if (isset($db)) $this->__set('db', $db); + if (isset($router)) $this->__set('router', $router); + if (isset($controller)) $this->__set('controller', $controller); + if (isset($model)) $this->__set('model', $model); + $this->__set('namespace', $namespace ?? (new reflection(self::class))->getNamespaceName()); + } - /** - * Деструктор - * - */ - public function __destruct() - { - } + /** + * Деструктор + * + */ + public function __destruct() {} - /** - * Запуск - * - * @param ?string $uri Маршрут - * - * @return string|int|null Ответ - */ - public function start(string $uri = null): string|int|null - { - // Обработка запроса - return $this->__get('router')->handle($uri, core: $this); - } + /** + * Запуск + * + * @param ?string $uri Маршрут + * + * @return string|int|null Ответ + */ + public function start(string $uri = null): string|int|null + { + // Обработка запроса + return $this->__get('router')->handle($uri, core: $this); + } - /** - * Записать свойство - * - * @param string $name Название - * @param mixed $value Содержимое - * - * @return void - */ - public function __set(string $name, mixed $value = null): void - { - match ($name) { - 'db', 'database' => (function () use ($value) { - if ($this->__isset('db')) throw new exception('Запрещено реинициализировать инстанцию соединения с базой данных ($this->db)', 500); - else { - // Свойство ещё не было инициализировано + /** + * Записать свойство + * + * @param string $name Название + * @param mixed $value Содержимое + * + * @return void + */ + public function __set(string $name, mixed $value = null): void + { + match ($name) { + 'db', 'database' => (function () use ($value) { + if ($this->__isset('db')) throw new exception('Запрещено реинициализировать инстанцию соединения с базой данных ($this->db)', 500); + else { + // Свойство ещё не было инициализировано - if (is_object($value)) $this->db = $value; - else throw new exception('Свойство $this->db должно хранить инстанцию соединения с базой данных', 500); - } - })(), - 'router' => (function () use ($value) { - if ($this->__isset('router')) throw new exception('Запрещено реинициализировать инстанцию маршрутизатора ($this->router)', 500); - else { - // Свойство ещё не было инициализировано + if (is_object($value)) $this->db = $value; + else throw new exception('Свойство $this->db должно хранить инстанцию соединения с базой данных', 500); + } + })(), + 'router' => (function () use ($value) { + if ($this->__isset('router')) throw new exception('Запрещено реинициализировать инстанцию маршрутизатора ($this->router)', 500); + else { + // Свойство ещё не было инициализировано - if ($value instanceof router) $this->router = $value; - else throw new exception('Свойство $this->router должно хранить инстанцию маршрутизатора (mirzaev\minimal\router)"', 500); - } - })(), - 'controller' => (function () use ($value) { - if ($this->__isset('controller')) throw new exception('Запрещено реинициализировать инстанцию ядра контроллеров ($this->controller)', 500); - else { - // Свойство не инициализировано + if ($value instanceof router) $this->router = $value; + else throw new exception('Свойство $this->router должно хранить инстанцию маршрутизатора (mirzaev\minimal\router)"', 500); + } + })(), + 'controller' => (function () use ($value) { + if ($this->__isset('controller')) throw new exception('Запрещено реинициализировать инстанцию ядра контроллеров ($this->controller)', 500); + else { + // Свойство не инициализировано - if ($value instanceof controller) $this->controller = $value; - else throw new exception('Свойство $this->controller должно хранить инстанцию ядра контроллеров (mirzaev\minimal\controller)', 500); - } - })(), - 'model' => (function () use ($value) { - if ($this->__isset('model')) throw new exception('Запрещено реинициализировать инстанцию ядра моделей ($this->model)', 500); - else { - // Свойство не инициализировано + if ($value instanceof controller) $this->controller = $value; + else throw new exception('Свойство $this->controller должно хранить инстанцию ядра контроллеров (mirzaev\minimal\controller)', 500); + } + })(), + 'model' => (function () use ($value) { + if ($this->__isset('model')) throw new exception('Запрещено реинициализировать инстанцию ядра моделей ($this->model)', 500); + else { + // Свойство не инициализировано - if ($value instanceof model) $this->model = $value; - else throw new exception('Свойство $this->model должно хранить инстанцию ядра моделей (mirzaev\minimal\model)', 500); - } - })(), - 'namespace' => (function () use ($value) { - if ($this->__isset('namespace')) throw new exception('Запрещено реинициализировать путь пространства имён ($this->namespace)', 500); - else { - // Свойство не инициализировано + if ($value instanceof model) $this->model = $value; + else throw new exception('Свойство $this->model должно хранить инстанцию ядра моделей (mirzaev\minimal\model)', 500); + } + })(), + 'namespace' => (function () use ($value) { + if ($this->__isset('namespace')) throw new exception('Запрещено реинициализировать путь пространства имён ($this->namespace)', 500); + else { + // Свойство не инициализировано - if (is_string($value)) $this->namespace = $value; - else throw new exception('Свойство $this->namespace должно хранить строку с путём пространства имён', 500); - } - })(), - default => throw new exception("Свойство \"\$$name\" не найдено", 404) - }; - } + if (is_string($value)) $this->namespace = $value; + else throw new exception('Свойство $this->namespace должно хранить строку с путём пространства имён', 500); + } + })(), + default => throw new exception("Свойство \"\$$name\" не найдено", 404) + }; + } - /** - * Прочитать свойство - * - * Записывает значение по умолчанию, если свойство не инициализировано - * - * @param string $name Название - * - * @return mixed Содержимое - */ - public function __get(string $name): mixed - { - return match ($name) { - 'db', 'database' => $this->db ?? throw new exception("Свойство \"\$$name\" не инициализировано", 500), - 'router' => (function () { - // Инициализация со значением по умолчанию - if (!$this->__isset('router')) $this->__set('router', new router); + /** + * Прочитать свойство + * + * Записывает значение по умолчанию, если свойство не инициализировано + * + * @param string $name Название + * + * @return mixed Содержимое + */ + public function __get(string $name): mixed + { + return match ($name) { + 'db', 'database' => $this->db ?? throw new exception("Свойство \"\$$name\" не инициализировано", 500), + 'router' => (function () { + // Инициализация со значением по умолчанию + if (!$this->__isset('router')) $this->__set('router', new router); - // Возврат (успех) - return $this->router; - })(), - 'controller' => (function () { - // Инициализация со значением по умолчанию - if (!$this->__isset('controller')) $this->__set('controller', new controller); + // Возврат (успех) + return $this->router; + })(), + 'controller' => (function () { + // Инициализация со значением по умолчанию + if (!$this->__isset('controller')) $this->__set('controller', new controller); - // Возврат (успех) - return $this->controller; - })(), - 'model' => (function () { - // Инициализация со значением по умолчанию - if (!$this->__isset('model')) $this->__set('model', new model); + // Возврат (успех) + return $this->controller; + })(), + 'model' => (function () { + // Инициализация со значением по умолчанию + if (!$this->__isset('model')) $this->__set('model', new model); - // Возврат (успех) - return $this->model; - })(), - 'namespace' => $this->namespace ?? throw new exception("Свойство \"\$$name\" не инициализировано", 500), - default => throw new exception("Свойство \"\$$name\" не найдено", 404) - }; - } + // Возврат (успех) + return $this->model; + })(), + 'namespace' => $this->namespace ?? throw new exception("Свойство \"\$$name\" не инициализировано", 500), + 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 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}); - })() - }; - } + /** + * Удалить свойство + * + * @param string $name Название + * + * @return void + */ + public function __unset(string $name): void + { + match ($name) { + default => (function () use ($name) { + // Удаление + unset($this->{$name}); + })() + }; + } } diff --git a/mirzaev/minimal/system/model.php b/mirzaev/minimal/system/model.php index c19a65d..cce557c 100755 --- a/mirzaev/minimal/system/model.php +++ b/mirzaev/minimal/system/model.php @@ -4,86 +4,31 @@ declare(strict_types=1); namespace mirzaev\minimal; -// Встроенные библиотеки +// Files of the project +use mirzaev\minimal\traits\magic; + +// Built-in libraries use exception; /** - * Модель + * Model (base) * * @package mirzaev\minimal + * + * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License * @author Arsen Mirzaev Tatyano-Muradovich */ class model { - /** - * Постфикс - */ - public const POSTFIX = '_model'; + use magic; - /** - * Конструктор - */ - public function __construct() - { - } + /** + * Postfix of file names + */ + public const string POSTFIX = '_model'; - /** - * Записать свойство - * - * @param string $name Название - * @param mixed $value Содержимое - * - * @return void - */ - 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}); - })() - }; - } + /** + * Constructor + */ + public function __construct() {} } diff --git a/mirzaev/minimal/system/router.php b/mirzaev/minimal/system/router.php index 40e21f6..54ea522 100755 --- a/mirzaev/minimal/system/router.php +++ b/mirzaev/minimal/system/router.php @@ -11,165 +11,308 @@ use mirzaev\minimal\core; * Маршрутизатор * * @package mirzaev\minimal + * + * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License * @author Arsen Mirzaev Tatyano-Muradovich */ final class router { - /** - * @var array $router Реестр маршрутов - */ - protected array $routes = []; + /** + * @var array $router Реестр маршрутов + */ + /* protected array $routes = []; */ + public array $routes = []; - /** - * Записать маршрут - * - * @param string $route Маршрут - * @param string $handler Обработчик - инстанции контроллера и модели (не обязательно), без постфиксов - * @param ?string $method Вызываемый метод в инстанции контроллера обработчика - * @param ?string $request HTTP-метод запроса (GET, POST, PUT...) - * @param ?string $model Инстанция модели (переопределение инстанции модели в $target) - * - * @return void - */ - public function write( - string $route, - string $handler, - ?string $method = 'index', - ?string $request = 'GET', - ?string $model = null - ): void { - // Запись в реестр - $this->routes[$route][$request] = [ - 'controller' => $handler, - 'model' => $model ?? $handler, - 'method' => $method - ]; - } + /** + * Записать маршрут + * + * @param string $route Маршрут + * @param string $handler Обработчик - инстанции контроллера и модели (не обязательно), без постфиксов + * @param ?string $method Вызываемый метод в инстанции контроллера обработчика + * @param ?string $request HTTP-метод запроса (GET, POST, PUT...) + * @param ?string $model Инстанция модели (переопределение инстанции модели в $target) + * @param array $variables + * + * @return static The instance from which the method was called (fluent interface) + */ + public function write( + string $route, + string $handler, + ?string $method = 'index', + ?string $request = 'GET', + ?string $model = null, + array $variables = [] + ): static { + // Запись в реестр + $this->routes[$route][$request] = [ + 'controller' => $handler, + 'model' => $model ?? $handler, + 'method' => $method, + 'variables' => $variables + ]; - /** - * Обработать маршрут + // Exit (success) (fluent interface) + return $this; + } + + /** + * Handle a request + * + * @param string|null $uri URI (protocol://domain/foo/bar) + * @param string|null $method Method (GET, POST, PUT...) + * @param core|null $core Instence of the system core + * + * @return string|int|null Response + */ + public function handle( + ?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'] ?? '/'; + $method ??= $_SERVER["REQUEST_METHOD"]; + + // Initializing of URN (/foo/bar) + $urn = parse_url(urldecode($uri), PHP_URL_PATH); + + // Universalization of URN + $urn = self::universalize($urn); + + // Search directories of URN (explode() creates empty value in array) + preg_match_all('/(^\/$|[^\/]+)/', $urn, $directories); + $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) { + // Iteration over routes + + if (isset($data[$method])) { + // The request method matches the route method + + // Universalization of route + $route = self::universalize($route); + + // Skipping unmatched routes based on results of previous iterations + if (isset($matches[$route]) && $matches[$route] === false) continue; + + // Initializing of route directory + $route_directory = $routes[$route][$i] ?? null; + + if (isset($route_directory)) { + // Initialized of route directory + + if ($urn_directory === $route_directory) { + // The directory of URN is identical to the directory of route + + // Writing: end of URN directories XNOR end of route directories + $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 + $matches[$route] = !(isset($directories[$_i = $i + 1]) xor isset($routes[$route][$_i])); + } 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]; + + foreach (array_slice($directories, ++$i, preserve_keys: true) as &$urn_directory) { + // Перебор директорий URN + + // Запись в реестр переменных + $collector[] = $urn_directory; + } + + break; + } + } + + /** + * Initialization of route handlers + */ + + if (array_key_exists($method, $data)) { + // Идентифицирован метод маршрута (GET, POST, PUT...) + + // Инициализация обработчиков (controller, model, method) + $handlers = $data[$method]; + + if (class_exists($controller = $core->namespace . '\\controllers\\' . $handlers['controller'] . $core->controller::POSTFIX)) { + // Найден контроллер + + // Инициализация инстанции ядра контроллера + $controller = new $controller; + + // Вызов связанного с маршрутом метода и возврат (успех) + return $controller->{$handlers['method']}($handlers['variables'] + $_REQUEST, $_FILES, file_get_contents('php://input')); + } + } + } + } + + // Возврат (провал) + 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) * - * @param ?string $uri URI запроса (https://domain.com/foo/bar) - * @param ?string $method Метод запроса (GET, POST, PUT...) + * @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; + } + + /** + * Сгенерировать ответ с ошибкой + * + * Вызывает метод error404 в инстанции контроллера ошибок + * * @param ?core $core Инстанция системного ядра * - * @return string|int|null Ответ - */ - public function handle(?string $uri = null, ?string $method = null, ?core $core = new core): string|int|null - { - // Инициализация значений по умолчанию - $uri ??= $_SERVER['REQUEST_URI'] ?? '/'; - $method ??= $_SERVER["REQUEST_METHOD"] ?? 'GET'; + * @return ?string HTML-документ + */ + private function error(core $core = new core): ?string + { + return class_exists($class = '\\' . $core->namespace . '\\controllers\\errors' . $core->controller::POSTFIX) + && method_exists($class, $method = 'error404') + ? (new $class)->$method() + : null; + } - // Инициализация URL запроса (/foo/bar) - $url = parse_url($uri, PHP_URL_PATH); - - // Универсализация маршрута - $url = self::universalize($url); - - // Сортировка реестра маршрутов от большего ключа к меньшему (кешируется) - krsort($this->routes); - - // Поиск директорий в ссылке - preg_match_all('/[^\/]+/', $url, $directories); - - // Инициализация директорий - $directories = $directories[0]; - - foreach ($this->routes as $route => $data) { - // Перебор маршрутов - - // Универсализация маршрута - $route = self::universalize($route); - - // Поиск директорий - preg_match_all('/[^\/]+/', $route, $data['directories']); - - // Инициализация директорий - $data['directories'] = $data['directories'][0]; - - if (count($directories) === count($data['directories'])) { - // Входит в диапазон маршрут (совпадает количество директорий у ссылки и маршрута) - - // Инициализация реестра переменных - $data['vars'] = []; - - foreach ($data['directories'] as $index => &$directory) { - // Перебор директорий - - if (preg_match('/\$([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]+)/', $directory) === 1) { - // Директория является переменной (.../$variable/...) - - // Запись в реестр переменных - $directory = $data['vars'][trim($directory, '$')] = $directories[$index]; - } - } - - // Реиницилазция маршрута - $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)) { - // Идентифицирован метод маршрута (GET, POST, PUT...) - - $route = $data[$method]; - - if (class_exists($controller = $core->namespace . '\\controllers\\' . $route['controller'] . $core->controller::POSTFIX)) { - // Найден контроллер - - // Инициализация инстанции ядра контроллера - $controller = new $controller; - - // Инициализация инстанции ядра модели - if (class_exists($model = $core->namespace . '\\models\\' . $route['model'] . $core->model::POSTFIX)); - - // Вызов связанного с маршрутом методв и возврат (успех) - return $controller->{$route['method']}($data['vars'] + $_REQUEST, $_FILES); - } - } - - // Выход из цикла (провал) - break; - } - } - } - - // Возврат (провал) - return $this->error($core); - } - - /** - * Сгенерировать ответ с ошибкой - * - * Вызывает метод error404 в инстанции контроллера ошибок - * - * @param ?core $core Инстанция системного ядра - * - * @return ?string HTML-документ - */ - private function error(core $core = new core): ?string - { - return class_exists($class = '\\' . $core->namespace . '\\controllers\\errors' . $core->controller::POSTFIX) - && method_exists($class, $method = 'error404') - ? (new $class)->$method() - : null; - } - - /** - * Универсализировать маршрут - * - * @param string $route Маршрут - * - * @return string Универсализированный маршрут - */ - private function universalize(string $route): string - { - // Если не записан "/" в начале, то записать, затем, если записан "/" в конце, то удалить - return preg_replace('/(.+)\/$/', '$1', preg_replace('/^([^\/])/', '/$1', $route)); - } + /** + * Universalize URN + * + * Always "/" at the beginning and never "/" at the end + * + * @param string &$urn URN (/foo/bar) + * + * @return string Universalized URN + */ + private function universalize(string $urn): string + { + // Universalization of URN and exit (success) + return (string) preg_replace('/(.+)\/$/', '$1', preg_replace('/^([^\/])/', '/$1', $urn)); + } } diff --git a/mirzaev/minimal/system/traits/magic.php b/mirzaev/minimal/system/traits/magic.php new file mode 100755 index 0000000..25631ce --- /dev/null +++ b/mirzaev/minimal/system/traits/magic.php @@ -0,0 +1,81 @@ + + */ +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}) + }; + } +}