From 6a4ea3f35194b6b5714e661daa28daa779e796eb Mon Sep 17 00:00:00 2001 From: Arsen Mirzaev Tatyano-Muradovich Date: Thu, 31 Oct 2024 23:34:35 +0300 Subject: [PATCH] executing requests from a controller + boosted by PHP 8.4 + the router rebuild. hell fucking yeah --- composer.json | 11 +- mirzaev/minimal/system/controller.php | 106 +++-- mirzaev/minimal/system/core.php | 465 +++++++++++++------- mirzaev/minimal/system/model.php | 14 +- mirzaev/minimal/system/route.php | 127 ++++++ mirzaev/minimal/system/router.php | 159 +++---- mirzaev/minimal/system/traits/magic.php | 4 +- mirzaev/minimal/system/traits/singleton.php | 85 ++++ 8 files changed, 636 insertions(+), 335 deletions(-) create mode 100755 mirzaev/minimal/system/route.php create mode 100644 mirzaev/minimal/system/traits/singleton.php diff --git a/composer.json b/composer.json index c101799..6ca3887 100755 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "mirzaev/minimal", "type": "framework", - "description": "Lightweight MVC framework that manages only the basic mechanisms, leaving the development of the programmer and not overloading the project", + "description": "My vision of a good framework", "keywords": [ "mvc", "framework", @@ -14,7 +14,7 @@ "name": "Arsen Mirzaev Tatyano-Muradovich", "email": "arsen@mirzaev.sexy", "homepage": "https://mirzaev.sexy", - "role": "Developer" + "role": "Programmer" } ], "support": { @@ -22,16 +22,11 @@ "issues": "https://git.mirzaev.sexy/mirzaev/minimal/issues" }, "require": { - "php": "~8.2" + "php": "~8.4" }, "autoload": { "psr-4": { "mirzaev\\minimal\\": "mirzaev/minimal/system" } - }, - "autoload-dev": { - "psr-4": { - "mirzaev\\minimal\\tests\\": "mirzaev/minimal/tests" - } } } diff --git a/mirzaev/minimal/system/controller.php b/mirzaev/minimal/system/controller.php index 0f65302..8fe351c 100755 --- a/mirzaev/minimal/system/controller.php +++ b/mirzaev/minimal/system/controller.php @@ -6,13 +6,22 @@ namespace mirzaev\minimal; // Files of the project use mirzaev\minimal\model, + mirzaev\minimal\core, mirzaev\minimal\traits\magic; -// Встроенные библиотеки +// Build-in libraries use exception; /** - * Controller (base) + * Controller + * + * @var core $core An instance of the core + * @var model $model An instance of the model connected in the core + * @var view $view View template engine instance (twig) + * @var core $core An instance of the core + * @var core $core An instance of the core + * + * @method self __construct(core $core) Constructor * * @package mirzaev\minimal * @@ -24,71 +33,54 @@ class controller use magic; /** - * Postfix of file names + * Core + * + * @var core $core An instance of the core */ - public const string POSTFIX = '_controller'; + public core $core { + // Read + get => $this->core; + } /** - * Instance of the model connected in the router + * Model + * + * @var model $model An instance of the model connected in the core */ - protected model $model; + public model $model { + // Write + set (model $model) { + $this->model ??= $model; + } + + // Read + get => $this->model; + } /** - * View template engine instance (twig) + * View + * + * @var view $view View template engine instance (twig) */ - protected object $view; + public object $view { + // Write + set (object $view) { + $this->view ??= $view; + } + + // Read + get => $this->view; + } /** * Constructor + * + * @param core $core The instance of the core + * + * @return self */ - public function __construct() {} - - /** - * 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->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 - - 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) - }; - } - - /** - * 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) - }; + public function __construct(core $core) { + // Writing the core into the property + $this->core = $core; } } diff --git a/mirzaev/minimal/system/core.php b/mirzaev/minimal/system/core.php index 3974a11..67b3547 100755 --- a/mirzaev/minimal/system/core.php +++ b/mirzaev/minimal/system/core.php @@ -4,18 +4,33 @@ declare(strict_types=1); namespace mirzaev\minimal; -// Файлы проекта +// Files of the project use mirzaev\minimal\router, + mirzaev\minimal\route, mirzaev\minimal\controller, mirzaev\minimal\model; -// Встроенные библиотеки +// Built-in libraries use exception, + BadMethodCallException as exception_method, + DomainException as exception_domain, + InvalidArgumentException as exception_argument, + UnexpectedValueException as exception_value, ReflectionClass as reflection; /** * Core * + * @param string $namespace Namespace where the core was initialized from + * @param controller $controller An instance of the controller + * @param model $model An instance of the model + * @param router $router An instance of the router + * + * @mathod self __construct(?string $namespace) Constructor + * @method void __destruct() Destructor + * @method string|null request(?string $uri, ?string $method, array $variabls) Handle the request + * @method string|null route(route $route, string $method) Handle the route + * * @package mirzaev\minimal * * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License @@ -24,204 +39,326 @@ use exception, final class core { /** - * Инстанция соединения с базой данных - */ - private object $db; - - /** - * Инстанция маршрутизатора - */ - private readonly router $router; - - /** - * Инстанция ядра контроллера - */ - private readonly controller $controller; - - /** - * Инстанция ядра модели - */ - private readonly model $model; - - /** - * Путь пространства имён (системное) + * Namespace * - * Используется для поиска файлов по спецификации PSR-4 - */ - private readonly string $namespace; - - /** - * Конструктор + * @var string $namespace Namespace where the core was initialized from * - * @param ?object $db Инстанция соединения с базой данных - * @param ?router $router Маршрутизатор - * @param ?controller $controller Инстанция ядра контроллера - * @param ?model $model Инстанция ядра модели - * @param ?string $namespace Пространство имён системного ядра - * - * @return self Инстанция ядра + * @see https://www.php-fig.org/psr/psr-4/ */ - 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 string $namespace { + // Read + get => $this->namespace; } /** - * Деструктор + * Controller * + * @var controller $controller An instance of the controller + */ + private controller $controller { + // Read + get => $this->controller ??= new controller; + } + + /** + * Model + * + * @var model $model An instance of the model + */ + private model $model { + // Read + get => $this->model ??= new model; + } + + /** + * Router + * + * @var router $router An instance of the router + */ + public router $router { + get => $this->router ??= router::initialize(); + } + + /** + * Constrictor + * + * @param ?string $namespace Пространство имён системного ядра + * + * @return self The instance of the core + */ + public function __construct( + ?string $namespace = null + ) { + // Writing a namespace to the property + $this->namespace = $namespace ?? (new reflection(self::class))->getNamespaceName(); + } + + + /** + * Destructor */ public function __destruct() {} /** - * Запуск + * Request * - * @param ?string $uri Маршрут + * Handle the request * - * @return string|int|null Ответ + * @param string|null $uri URI of the request (value by default: $_SERVER['REQUEST_URI']) + * @param string|null $method Method of the request (GET, POST, PUT...) (value by default: $_SERVER["REQUEST_METHOD"]) + * @paam array $parameters parameters for merging with route parameters + * + * @return string|null Response */ - public function start(string $uri = null): string|int|null + public function request(?string $uri = null, ?string $method = null, array $parameters = []): ?string { - // Обработка запроса - return $this->__get('router')->handle($uri, core: $this); + // Matching a route + $route = $this->router->match($uri ??= $_SERVER['REQUEST_URI'] ?? '/', $method ??= $_SERVER["REQUEST_METHOD"]); + + if ($route) { + // Initialized the route + + if (!empty($parameters)) { + // Recaived parameters + + // Merging parameters with route parameters + $route->parameters = $parameters + $route->parameters; + } + + // Handling the route and exit (success) + return $this->route($route, $method); + } + + // Exit (fail) + return null; } /** - * Записать свойство + * Route * - * @param string $name Название - * @param mixed $value Содержимое + * Handle the route * - * @return void + * @param route $route The route + * @param string $method Method of requests (GET, POST, PUT, DELETE, COOKIE...) + * + * @return string|null Response, if generated */ - public function __set(string $name, mixed $value = null): void + public function route(route $route, string $method = 'GET'): ?string { - match ($name) { - 'db', 'database' => (function () use ($value) { - if ($this->__isset('db')) throw new exception('Запрещено реинициализировать инстанцию соединения с базой данных ($this->db)', 500); - else { - // Свойство ещё не было инициализировано + // Initializing name of the controller class + $controller = $route->controller; + + if ($route->controller instanceof controller) { + // Initialized the controller + } else if (class_exists($controller = "$this->namespace\\controllers\\$controller")) { + // Found the controller by its name - if (is_object($value)) $this->db = $value; - else throw new exception('Свойство $this->db должно хранить инстанцию соединения с базой данных', 500); + // Initializing the controller + $route->controller = new $controller(core: $this); + } else if (!empty($route->controller)) { + // Not found the controller and $route->controller has a value + + // Exit (fail) + throw new exception_domain('Failed to found the controller: ' . $route->controller); + } else { + // Not found the controller and $route->controller is empty + + // Exit (fail) + throw new exception_argument('Failed to initialize the controller: ' . $route->controller); + } + + // Deinitializing name of the controller class + unset($controller); + + // Initializing name if the model class + $model = $route->model; + + if ($route->model instanceof model) { + // Initialized the model + } else if (class_exists($model = "$this->namespace\\models\\$model")) { + // Found the model by its name + + // Initializing the model + $route->model = new $model; + } else if (!empty($route->model)) { + // Not found the model and $route->model has a value + + // Exit (fail) + throw new exception_domain('Failed to initialize the model: ' . ($route->model ?? $route->controller)); + } + + // Deinitializing name of the model class + unset($model); + + if ($route->model instanceof model) { + // Initialized the model + + // Writing the model to the controller + $route->controller->model = $route->model; + } + + if ($method === 'POST') { + // POST + + if (method_exists($route->controller, $route->method)) { + // Found the method of the controller + + // Executing method of the controller that handles the route and exit (success) + return $route->controller->{$route->method}($route->parameters + $_POST, $_FILES); + } else { + // Not found the method of the controller + + // Exit (fail) + throw new exception('Failed to find the method of the controller: ' . $route->method); + } + } else if ($method === 'GET') { + // GET + + if (method_exists($route->controller, $route->method)) { + // Found the method of the controller + + if ($_SERVER["CONTENT_TYPE"] === 'multipart/form-data' || $_SERVER["CONTENT_TYPE"] === 'application/x-www-form-urlencoded') { + // The requeset content type is the "multipart/form-data" or "application/x-www-form-urlencoded" + + // Analysis of the request + [$_GET, $_FILES] = request_parse_body($route->options); } - })(), - '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); + // Executing method of the controller that handles the route and exit (success) + return $route->controller->{$route->method}($route->parameters + $_GET, $_FILES); + } else { + // Not found the method of the controller + + // Exit (fail) + throw new exception('Failed to find the method of the controller: ' . $route->method); + } + } else if ($method === 'PUT') { + // PUT + + if (method_exists($route->controller, $route->method)) { + // Found the method of the controller + + if ($_SERVER["CONTENT_TYPE"] === 'multipart/form-data' || $_SERVER["CONTENT_TYPE"] === 'application/x-www-form-urlencoded') { + // The requeset content type is the "multipart/form-data" or "application/x-www-form-urlencoded" + + // Analysis of the request + [$_PUT, $_FILES] = request_parse_body($route->options); } - })(), - '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); + // Executing method of the controller that handles the route and exit (success) + return $route->controller->{$route->method}($route->parameters + $_PUT, $_FILES); + } else { + // Not found the method of the controller + + // Exit (fail) + throw new exception('Failed to find the method of the controller: ' . $route->method); + } + } else if ($method === 'DELETE') { + // DELETE + + if (method_exists($route->controller, $route->method)) { + // Found the method of the controller + + if ($_SERVER["CONTENT_TYPE"] === 'multipart/form-data' || $_SERVER["CONTENT_TYPE"] === 'application/x-www-form-urlencoded') { + // The requeset content type is the "multipart/form-data" or "application/x-www-form-urlencoded" + + // Analysis of the request + [$_DELETE, $_FILES] = request_parse_body($route->options); } - })(), - '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); + // Executing method of the controller that handles the route and exit (success) + return $route->controller->{$route->method}($route->parameters + $_DELETE, $_FILES); + } else { + // Not found the method of the controller + + // Exit (fail) + throw new exception('Failed to find the method of the controller: ' . $route->method); + } + } else if ($method === 'PATCH') { + // PATCH + + if (method_exists($route->controller, $route->method)) { + // Found the method of the controller + + if ($_SERVER["CONTENT_TYPE"] === 'multipart/form-data' || $_SERVER["CONTENT_TYPE"] === 'application/x-www-form-urlencoded') { + // The requeset content type is the "multipart/form-data" or "application/x-www-form-urlencoded" + + // Analysis of the request + [$_PATCH, $_FILES] = request_parse_body($route->options); } - })(), - '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) - }; - } + // Executing method of the controller that handles the route and exit (success) + return $route->controller->{$route->method}($route->parameters + $_PATCH, $_FILES); + } else { + // Not found the method of the controller + + // Exit (fail) + throw new exception('Failed to find the method of the controller: ' . $route->method); + } + } else if ($method === 'HEAD') { + // HEAD - /** - * Прочитать свойство - * - * Записывает значение по умолчанию, если свойство не инициализировано - * - * @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); + if (method_exists($route->controller, $route->method)) { + // Found the method of the controller - // Возврат (успех) - return $this->router; - })(), - 'controller' => (function () { - // Инициализация со значением по умолчанию - if (!$this->__isset('controller')) $this->__set('controller', new controller); + // Executing method of the controller that handles the route and exit (success) + return $route->controller->{$route->method}($route->parameters); + } else { + // Not found the method of the controller + + // Exit (fail) + throw new exception('Failed to find the method of the controller: ' . $route->method); + } + } else if ($method === 'OPTIONS') { + // OPTIONS - // Возврат (успех) - return $this->controller; - })(), - 'model' => (function () { - // Инициализация со значением по умолчанию - if (!$this->__isset('model')) $this->__set('model', new model); + if (method_exists($route->controller, $route->method)) { + // Found the method of the controller - // Возврат (успех) - return $this->model; - })(), - 'namespace' => $this->namespace ?? throw new exception("Свойство \"\$$name\" не инициализировано", 500), - default => throw new exception("Свойство \"\$$name\" не найдено", 404) - }; - } + // Executing method of the controller that handles the route and exit (success) + return $route->controller->{$route->method}($route->parameters); + } else { + // Not found the method of the controller + + // Exit (fail) + throw new exception('Failed to find the method of the controller: ' . $route->method); + } + } else if ($method === 'CONNECT') { + // CONNECT - /** - * Проверить свойство на инициализированность - * - * @param string $name Название - * - * @return bool Инициализировано свойство? - */ - public function __isset(string $name): bool - { - return match ($name) { - default => isset($this->{$name}) - }; - } + if (method_exists($route->controller, $route->method)) { + // Found the method of the controller - /** - * Удалить свойство - * - * @param string $name Название - * - * @return void - */ - public function __unset(string $name): void - { - match ($name) { - default => (function () use ($name) { - // Удаление - unset($this->{$name}); - })() - }; + // Executing method of the controller that handles the route and exit (success) + return $route->controller->{$route->method}($route->parameters); + } else { + // Not found the method of the controller + + // Exit (fail) + throw new exception('Failed to find the method of the controller: ' . $route->method); + } + } else if ($method === 'TRACE') { + // TRACE + + if (method_exists($route->controller, $route->method)) { + // Found the method of the controller + + // Executing method of the controller that handles the route and exit (success) + return $route->controller->{$route->method}($route->parameters); + } else { + // Not found the method of the controller + + // Exit (fail) + throw new exception('Failed to find the method of the controller: ' . $route->method); + } + } else { + // Not recognized method of the request + + // Exit (fail) + throw new exception_value('Failed to recognize the method: ' . $method); + } + + // Exit (fail) + return null; } } diff --git a/mirzaev/minimal/system/model.php b/mirzaev/minimal/system/model.php index cce557c..dff3327 100755 --- a/mirzaev/minimal/system/model.php +++ b/mirzaev/minimal/system/model.php @@ -7,11 +7,10 @@ namespace mirzaev\minimal; // Files of the project use mirzaev\minimal\traits\magic; -// Built-in libraries -use exception; - /** - * Model (base) + * Model + * + * @method self __construct() Constructor * * @package mirzaev\minimal * @@ -22,13 +21,10 @@ class model { use magic; - /** - * Postfix of file names - */ - public const string POSTFIX = '_model'; - /** * Constructor + * + * @return self */ public function __construct() {} } diff --git a/mirzaev/minimal/system/route.php b/mirzaev/minimal/system/route.php new file mode 100755 index 0000000..710c4ed --- /dev/null +++ b/mirzaev/minimal/system/route.php @@ -0,0 +1,127 @@ +controller + * @param string|model $model Name of the model + * @param array $parameters Arguments for the $this->method (will be concatenated together with generated request parameters) + * @param array $options Options for `request_parse_body($options)` + * + * @method self __construct(string $controller,?string $method, ?string $model, array $variables, array $options) Constructor + * + * @package mirzaev\minimal + * + * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License + * @author Arsen Mirzaev Tatyano-Muradovich + */ +final class route +{ + /** + * Controller + * + * @var string|model $controller Name of the controller or an instance of the controller + */ + public string|controller $controller { + // Read + get => $this->controller; + } + + /** + * Method + * + * @var string $method Name of the method of the method of $this->controller + */ + public string $method{ + // Read + get => $this->method; + } + + /** + * Model + * + * @var string|model|null $model Name of the model of an instance of the model + */ + public string|model|null $model { + // Read + get => $this->model; + } + + /** + * Parameters + * + * @var array $parameters Arguments for the $this->method (will be concatenated together with generated request parameters) + */ + public array $parameters { + // Read + get => $this->parameters; + } + + /** + * Options + * + * Used only for GET, PUT, PATCH and DELETE + * + * @var string $options Options for `request_parse_body($options)` + */ + public array $options { + // Write + set (array $value) => array_filter( + $value, + fn(string $key) => match ($key) { + 'post_max_size', + 'max_input_vars', + 'max_multipart_body_parts', + 'max_file_uploads', + 'upload_max_filesize' => true, + default => false + }, + ARRAY_FILTER_USE_KEY + ); + + // Read + get => $this->options; + } + + /** + * Constructor + * + * @param string|controller $controller Name of the controller + * @param string|null $method Name of the method of the method of $controller + * @param string|model|null $model Name of the model + * @param array $parameters Arguments for the $method (will be concatenated together with generated request parameters) + * @param array $options Options for `request_parse_body` (Only for POST method) + * + * @return self + */ + public function __construct( + string|controller $controller, + ?string $method = 'index', + string|model|null $model = null, + array $parameters = [], + array $options = [] + ) { + // Writing name of the controller + $this->controller = $controller; + + // Writing name of the model + $this->model = $model; + + // Writing name of the method of the controller + $this->method = $method; + + // Writing parameters + $this->parameters = $parameters; + + // Writing options + if (match ($method) { + 'GET', 'PUT', 'PATCH', 'DELETE' => true, + default => false + }) $this->options = $options; + } +} diff --git a/mirzaev/minimal/system/router.php b/mirzaev/minimal/system/router.php index 54ea522..c61b350 100755 --- a/mirzaev/minimal/system/router.php +++ b/mirzaev/minimal/system/router.php @@ -4,11 +4,21 @@ declare(strict_types=1); namespace mirzaev\minimal; -// Файлы проекта -use mirzaev\minimal\core; +// Files of the project +use mirzaev\minimal\route, + mirzaev\minimal\traits\singleton; + +// Build-ing libraries +use InvalidArgumentException as exception_argument; /** - * Маршрутизатор + * Router + * + * @param array $routes The registry of routes + * + * @method self write(string $uri, string $method) + * @method self sort() Sort routes (DEV) + * @method string universalize(string $urn) Universalize URN * * @package mirzaev\minimal * @@ -17,58 +27,57 @@ use mirzaev\minimal\core; */ final class router { - /** - * @var array $router Реестр маршрутов - */ - /* protected array $routes = []; */ - public array $routes = []; + use singleton; /** - * Записать маршрут + * Routes * - * @param string $route Маршрут - * @param string $handler Обработчик - инстанции контроллера и модели (не обязательно), без постфиксов - * @param ?string $method Вызываемый метод в инстанции контроллера обработчика - * @param ?string $request HTTP-метод запроса (GET, POST, PUT...) - * @param ?string $model Инстанция модели (переопределение инстанции модели в $target) - * @param array $variables + * @var array $routes The registry of routes + */ + protected array $routes = []; + + /** + * Write a route * - * @return static The instance from which the method was called (fluent interface) + * @param string $urn URN of the route ('/', '/page', '/page/$variable', '/page/$collector...'...) + * @param route $route The route + * @param string|array $method Method of requests (GET, POST, PUT, DELETE, COOKIE...) + * + * @return self 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 - ]; + string $urn, + route $route, + null|string|array $method = 'GET' + ): self { + foreach (is_array($method) ? $method : [$method] as $method) { + // Iterate over methods of requests + + // Validating method + $method = match ($method) { + 'POST', 'GET', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'CONNECT', 'TRACE' => $method, + default => throw new exception_argument("Failed to initialize method: \"$method\"") + }; + + // Writing to the registry of routes + $this->routes[$urn][$method] = $route; + } // Exit (success) (fluent interface) return $this; } /** - * Handle a request + * Match * - * @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 + * Match request URI with registry of routes * - * @return string|int|null Response + * @param string $uri URI (protocol://domain/foo/bar) + * @param string $method Method of the request (GET, POST, PUT...) + * + * @return route|null Route, if found */ - public function handle( - ?string $uri = null, - ?string $method = null, - ?core $core = new core - ): string|int|null { + public function match(string $uri, string $method): ?route { // Declaration of the registry of routes directoies $routes = []; @@ -81,17 +90,10 @@ final class router } if (count($routes) === count($this->routes)) { - // Initialized the registry of routes directoies + // 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); + // Universalization of URN (/foo/bar) + $urn = self::universalize(parse_url(urldecode($uri), PHP_URL_PATH)); // Search directories of URN (explode() creates empty value in array) preg_match_all('/(^\/$|[^\/]+)/', $urn, $directories); @@ -171,12 +173,12 @@ final class router // The directory is a variable ($variable) // Запись в реестр переменных и перещапись директории в маршруте - $data[$method]['variables'][trim($route_directory, '$')] = $directories[$i]; + $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 = &$data[$method]->variables[trim($route_directory, '$.')]; // Инициализаия массива сборщика $collector ??= []; @@ -195,31 +197,13 @@ final class router } } - /** - * 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')); - } - } + // Exit (success or fail) + return $data[$method] ?? null; } } - // Возврат (провал) - return $this->error($core); + // Exit (fail) + return null; } /** @@ -236,11 +220,11 @@ final class router * * Добавить чтобы сначала текст потом переменная затем после переменной первыми тексты и в конце перменные опять и так рекурсивно * - * @return static The instance from which the method was called (fluent interface) - * + * @return self The instance from which the method was called (fluent interface) + * * @todo ПЕРЕДЕЛАТЬ ПОЛНОСТЬЮ */ - public function sort(): static + public function sort(): self { uksort($this->routes, function (string $a, string $b) { // Sorting routes @@ -284,35 +268,18 @@ final class router return $this; } - /** - * Сгенерировать ответ с ошибкой - * - * Вызывает метод 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; - } - /** * Universalize URN * * Always "/" at the beginning and never "/" at the end * - * @param string &$urn URN (/foo/bar) + * @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)); + return (string) '/' . mb_trim($urn, '/'); } } diff --git a/mirzaev/minimal/system/traits/magic.php b/mirzaev/minimal/system/traits/magic.php index 25631ce..ba67085 100755 --- a/mirzaev/minimal/system/traits/magic.php +++ b/mirzaev/minimal/system/traits/magic.php @@ -10,12 +10,14 @@ use exception; /** * Trait of magical methods * - * @method void __set(string $name, mixed $value = null) Write property + * @method void __set(string $name, mixed $value) 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 + * + * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License * @author Arsen Mirzaev Tatyano-Muradovich */ trait magic diff --git a/mirzaev/minimal/system/traits/singleton.php b/mirzaev/minimal/system/traits/singleton.php new file mode 100644 index 0000000..cf25e17 --- /dev/null +++ b/mirzaev/minimal/system/traits/singleton.php @@ -0,0 +1,85 @@ + + */ +trait singleton +{ + /** + * Constructor (blocked) + * + * @return void + */ + final private function __construct() + { + // Initializing the indicator that an instance of static::class has already been initialized + static $instance = false; + + if ($instance) { + // An instance of static has already been initialized + + // Exit (fail) + throw new exception('Has already been initialized an instance of the ' . static::class); + } + + // Writing the indicator that the instance of static been initialized + $instance = true; + } + + /** + * Initialize an instance + * + * Create an instance of static::class, or return an already created static::class instance + * + * @return static + */ + public static function initialize(): static + { + // Initialize the buffer of the instance of static::class + static $instance; + + // Exit (success) + return $instance ??= new static; + } + + /** + * Clone (blocked) + */ + private function __clone() + { + // Exit (fail) + throw new exception('Cloning is inadmissible'); + } + + /** + * Sleep (blocked) + */ + public function __sleep() + { + // Exit (fail) + throw new exception('Serialization is inadmissible'); + } + + /** + * Wake up (blocked) + */ + public function __wakeup() + { + // Exit (fail) + throw new exception('Deserialisation is inadmissible'); + } +}