diff --git a/README.md b/README.md index 47e2667..3c62b98 100755 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ # MINIMAL -The best code-to-utility Framework +The best code-to-utility framework ## Nearest plans (2025) -1. **Middlewares** technology -2. Route sorting `router::sort()` -3. Processing routes from within routes (request emulation) +1. Routes sorting `router::sort()` ## Installation Execute: `composer require mirzaev/minimal` @@ -28,21 +26,3 @@ $core->router // Handling the request $core->start(); ``` - -## Projects based on MINIMAL - -### ebala -**Repository:** https://git.svoboda.works/mirzaev/ebala
-**Github mirror:** https://github.com/mature-woman/ebala
-*I earned more than a **million rubles** from this project*
-*Repositories **may** be closed at the request of the customer*
- -### Arming -**Repository:** https://git.svoboda.works/weby/arming
-**Guthub mirror:** https://github.com/WEBY-GROUP/arming
-*Telegram chat-robot marketplace*
- -### not.chat -**Repository:** https://git.svoboda.works/mirzaev/notchat
-**Github mirror:** https://github.com/mature-woman/notchat
-*P2P chat project with blockchains and smart stuff*
diff --git a/composer.json b/composer.json index 6ca3887..8488359 100755 --- a/composer.json +++ b/composer.json @@ -1,14 +1,14 @@ { "name": "mirzaev/minimal", "type": "framework", - "description": "My vision of a good framework", + "description": "The best code-to-utility framework", "keywords": [ "mvc", "framework", "lightweight" ], "license": "WTFPL", - "homepage": "https://git.mirzaev.sexy/mirzaev/minimal", + "homepage": "https://git.svoboda.works/mirzaev/minimal", "authors": [ { "name": "Arsen Mirzaev Tatyano-Muradovich", @@ -18,8 +18,8 @@ } ], "support": { - "docs": "https://git.mirzaev.sexy/mirzaev/minimal/wiki", - "issues": "https://git.mirzaev.sexy/mirzaev/minimal/issues" + "docs": "https://git.svoboda.works/mirzaev/minimal/wiki", + "issues": "https://git.svoboda.works/mirzaev/minimal/issues" }, "require": { "php": "~8.4" diff --git a/mirzaev/minimal/system/core.php b/mirzaev/minimal/system/core.php index 49ebf8a..92417cf 100755 --- a/mirzaev/minimal/system/core.php +++ b/mirzaev/minimal/system/core.php @@ -14,7 +14,8 @@ use mirzaev\minimal\router, mirzaev\minimal\http\enumerations\status; // Built-in libraries -use exception, +use Closure as closure, + Exception as exception, RuntimeException as exception_runtime, BadMethodCallException as exception_method, DomainException as exception_domain, @@ -113,8 +114,41 @@ final class core */ public function start(): ?string { - // Handle request and exit (success) - return $this->request(new request(environment: true)); + if (php_sapi_name() === 'cli') { + // Processing from CLI + + // Initializing options + $options = getopt('', [ + 'method::', + 'uri::', + 'protocol::' + ]); + + // Writing method into the environment constant + $_SERVER["REQUEST_METHOD"] = $options['method'] ?? 'GET'; + + // Writing URI into the environment constant + $_SERVER['REQUEST_URI'] = $options['uri'] ?? '/'; + + // Writing verstion of HTTP protocol into the environment constant + $_SERVER['SERVER_PROTOCOL'] = $options['protocol'] ?? 'CLI'; + } + + // Preparing the route function + $action = fn(): string => (string) $this->request(new request(environment: true)); + + foreach ($this->router->middlewares as $middleware) { + // Iterating over the router middlewares + + // Preparing the middleware function + $action = fn(): string => $middleware(next: $action); + } + + // Processing middlewares and the router request function + $response = $action(); + + // Exit (success) + return $response; } /** @@ -123,30 +157,43 @@ final class core * Handle request * * @param request $request The request - * @paam array $parameters parameters for merging with route parameters + * @param array $parameters parameters for merging with route parameters * * @return string|null Response */ public function request(request $request, array $parameters = []): ?string { - // Matching a route + // Matching the route $route = $this->router->match($request); if ($route) { - // Initialized a route + // Initialized the route if (!empty($parameters)) { // Recaived parameters - // Merging parameters with route parameters + // Merging parameters with the route parameters $route->parameters = $parameters + $route->parameters; } - // Writing request options from route options + // Writing the request options from the route options $request->options = $route->options; - // Handling a route and exit (success) - return $this->route($route, $request); + // Preparing the route function + $action = fn(): string => (string) $this->route($route, $request); + + foreach ($route->middlewares as $middleware) { + // Iterating over the route middlewares + + // Preparing the middleware function + $action = fn(): string => $middleware(next: $action); + } + + // Processing middlewares and the route functions + $response = $action(); + + // Exit (success) + return $response; } // Exit (fail) @@ -171,7 +218,7 @@ final class core { // 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")) { @@ -240,10 +287,9 @@ final class core // Exit (fail) throw new exception_runtime('Caught an error while processing the route', status::internal_server_error->value, $exception); } - } else { // Not found the method of the controller - + // Exit (fail) throw new exception_method('Failed to find method of the controller: ' . $route->controller::class . "->$route->method()", status::not_implemented->value); } diff --git a/mirzaev/minimal/system/http/enumerations/protocol.php b/mirzaev/minimal/system/http/enumerations/protocol.php index 3589ac2..23b3e44 100755 --- a/mirzaev/minimal/system/http/enumerations/protocol.php +++ b/mirzaev/minimal/system/http/enumerations/protocol.php @@ -21,4 +21,6 @@ enum protocol: string case http_1_1 = 'HTTP/1.1'; case http_1 = 'HTTP/1.0'; case http_0_9 = 'HTTP/0.9'; + + case cli = 'CLI'; } diff --git a/mirzaev/minimal/system/http/request.php b/mirzaev/minimal/system/http/request.php index c221fec..062b529 100755 --- a/mirzaev/minimal/system/http/request.php +++ b/mirzaev/minimal/system/http/request.php @@ -6,16 +6,16 @@ namespace mirzaev\minimal\http; // Files of the project use mirzaev\minimal\http\enumerations\method, -mirzaev\minimal\http\enumerations\protocol, -mirzaev\minimal\http\enumerations\status, -mirzaev\minimal\http\enumerations\content, -mirzaev\minimal\http\response; + mirzaev\minimal\http\enumerations\protocol, + mirzaev\minimal\http\enumerations\status, + mirzaev\minimal\http\enumerations\content, + mirzaev\minimal\http\response; // Built-in libraries use DomainException as exception_domain, -InvalidArgumentException as exception_argument, -RuntimeException as exception_runtime, -LogicException as exception_logic; + InvalidArgumentException as exception_argument, + RuntimeException as exception_runtime, + LogicException as exception_logic; /** * Request @@ -280,16 +280,16 @@ final class request // Writing $this->options = 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 => throw new exception_domain("Failed to recognize option: $key", status::internal_server_error->value) - }, - ARRAY_FILTER_USE_KEY + $value, + fn(string $key) => match ($key) { + 'post_max_size', + 'max_input_vars', + 'max_multipart_body_parts', + 'max_file_uploads', + 'upload_max_filesize' => true, + default => throw new exception_domain("Failed to recognize option: $key", status::internal_server_error->value) + }, + ARRAY_FILTER_USE_KEY ); } @@ -335,36 +335,42 @@ final class request if (isset($protocol)) $this->protocol = $protocol; - if (isset($headers)) { - // Received headers + // Declaring the buffer of headers + $buffer = []; - // Declaring the buffer of headers - $buffer = []; + foreach ( + match (true) { + isset($headers) => $headers, + php_sapi_name() !== 'cli' => getallheaders(), + default => [] + } as $name => $value + ) { + // Iterating over headers - foreach ($headers ?? [] as $name => $value) { - // Iterating over headers + // Normalizing name of header (https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2) + $name = mb_strtolower($name, 'UTF-8'); - // Normalizing name of header (https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2) - $name = mb_strtolower($name, 'UTF-8'); + if (empty($name)) { + // Not normalized name of header - if (empty($name)) { - // Not normalized name of header - - // Exit (fail) - throw new exception_domain('Failed to normalize name of header', status::internal_server_error->value); - } - - // Writing into the buffer of headers - $buffer[$name] = $value; + // Exit (fail) + throw new exception_domain('Failed to normalize name of header', status::internal_server_error->value); } - // Writing headers from argument into the property - $this->headers = $buffer; - - // Deinitializing the buffer of headers - unset($buffer); + // Writing into the buffer of headers + $buffer[$name] = $value; } + if (!empty($buffer)) { + // Initialized at lease one header + + // Writing headers into the property + $this->headers = $buffer; + } + + // Deinitializing the buffer of headers + unset($buffer); + // Writing parameters from argument into the property if (isset($parameters)) $this->parameters = $parameters; @@ -385,36 +391,6 @@ final class request // Writing verstion of HTTP protocol from environment into the property $this->protocol ??= $_SERVER['SERVER_PROTOCOL']; - if (!isset($headers)) { - // Received headers - - // Declaring the buffer of headers - $buffer = []; - - foreach (getallheaders() ?? [] as $name => $value) { - // Iterating over headers - - // Normalizing name of header (https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2) - $name = mb_strtolower($name, 'UTF-8'); - - if (empty($name)) { - // Not normalized name of header - - // Exit (fail) - throw new exception_domain('Failed to normalize name of header', status::internal_server_error->value); - } - - // Writing into the buffer of headers - $buffer[$name] = $value; - } - - // Writing headers from environment into the property - $this->headers = $buffer; - - // Deinitializing the buffer of headers - unset($buffer); - } - if (str_starts_with($this->headers['content-type'] ?? '', content::json->value)) { // The body contains "application/json" diff --git a/mirzaev/minimal/system/middleware.php b/mirzaev/minimal/system/middleware.php new file mode 100755 index 0000000..c14934a --- /dev/null +++ b/mirzaev/minimal/system/middleware.php @@ -0,0 +1,63 @@ + + */ +final class middleware +{ + /** + * Function + * + * @var closure $function Function + */ + public readonly closure $function; + + /** + * Constructor + * + * @param closure $function Function + * + * @return void + */ + public function __construct(closure $function) + { + // Writing the function + $this->function = $function; + } + + /** + * Invoke + * + * @param callable $next + * + * @return string Output + */ + public function __invoke(callable $next): string + { + // Processing the middleware (entering into recursion) + + return (string) ($this->function)(next: $next); + } +} diff --git a/mirzaev/minimal/system/route.php b/mirzaev/minimal/system/route.php index 8a556da..b0a492c 100755 --- a/mirzaev/minimal/system/route.php +++ b/mirzaev/minimal/system/route.php @@ -4,6 +4,11 @@ declare(strict_types=1); namespace mirzaev\minimal; +// Files of the project +use mirzaev\minimal\controller, + mirzaev\minimal\middleware, + mirzaev\minimal\traits\middleware as middleware_trait; + /** * Route * @@ -14,14 +19,18 @@ namespace mirzaev\minimal; * @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)` + * @param array $middlewares Stack of middlewares * - * @method void __construct(string|controller $controller, ?string $method, string|model|null $model, array $parameters, array $options) Constructor + * @method void __construct(string|controller $controller, ?string $method, string|model|null $model, array $parameters, array $options, array $middlewares) Constructor + * @method self middleware(middleware $middleware) Middleware * * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License * @author Arsen Mirzaev Tatyano-Muradovich */ final class route { + use middleware_trait; + /** * Controller * @@ -37,7 +46,7 @@ final class route * * @var string $method Name of the method of the method of $this->controller */ - public string $method{ + public string $method { // Read get => $this->method; } @@ -69,8 +78,8 @@ final class route * * Required if $this->method !== method::post * - * @see https://wiki.php.net/rfc/rfc1867-non-post about request_parse_body() - * @see https://wiki.php.net/rfc/property-hooks (find a table about backed and virtual hooks) + * @see https://wiki.php.net/rfc/rfc1867-non-post About request_parse_body() + * @see https://wiki.php.net/rfc/property-hooks Hooks (find a table about backed and virtual hooks) * * @throws exception_runtime if reinitialize the property * @@ -78,7 +87,7 @@ final class route */ public array $options { // Write - set (array $value) { + set(array $value) { if (isset($this->{__PROPERTY__})) { // The property is already initialized @@ -113,6 +122,7 @@ final class route * @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) + * @param array $middlewares Middlewares stack * * @return void */ @@ -121,7 +131,8 @@ final class route ?string $method = 'index', string|model|null $model = null, array $parameters = [], - array $options = [] + array $options = [], + array $middlewares = [] ) { // Writing name of the controller $this->controller = $controller; @@ -135,6 +146,32 @@ final class route // Writing parameters $this->parameters = $parameters; + // Declaring the register of the middlewares stack validity + $stack = true; + + foreach ($middlewares as $middleware) { + // Iterating over middlewares + + if ($middleware instanceof middleware) { + // Initialized the middleware + } else { + // Not initialized the middleware + + // Writing the register of the middlewares stack validity + $stack = false; + + // Exit (fail) + break; + } + } + + if ($stack) { + // The middlewares stack is valid + + // Writing the middlewares stack + $this->middlewares = $middlewares; + } + // Writing options if (match ($method) { 'GET', 'PUT', 'PATCH', 'DELETE' => true, diff --git a/mirzaev/minimal/system/router.php b/mirzaev/minimal/system/router.php index b28ade4..0d1e0d8 100755 --- a/mirzaev/minimal/system/router.php +++ b/mirzaev/minimal/system/router.php @@ -7,7 +7,8 @@ namespace mirzaev\minimal; // Files of the project use mirzaev\minimal\route, mirzaev\minimal\http\request, - mirzaev\minimal\traits\singleton; + mirzaev\minimal\traits\singleton, + mirzaev\minimal\traits\middleware as middleware_trait; // Build-ing libraries use InvalidArgumentException as exception_argument; @@ -21,15 +22,17 @@ use InvalidArgumentException as exception_argument; * * @method self write(string $urn, route $route, string|array $method) Write route to registry of routes (fluent interface) * @method route|null match(request $request) Match request URI with registry of routes - * @method self sort() Sort routes (DEV) + * @method self sort() Sort routes (DEVELOPMENT) * @method string universalize(string $urn) Universalize URN + * @method self middleware(middleware $middleware) Middleware + * @param array $middlewares Stack of middlewares * * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License * @author Arsen Mirzaev Tatyano-Muradovich */ final class router { - use singleton; + use singleton, middleware_trait; /** * Routes diff --git a/mirzaev/minimal/system/traits/middleware.php b/mirzaev/minimal/system/traits/middleware.php new file mode 100755 index 0000000..f416f9d --- /dev/null +++ b/mirzaev/minimal/system/traits/middleware.php @@ -0,0 +1,53 @@ + + */ +trait middleware +{ + /** + * Middlewares + * + * @var array $middlewares The middlewares stack + */ + public array $middlewares = [] { + // Read + &get => $this->middlewares; + } + + /** + * Middleware + * + * Write the middleware into the middlewares stack + * + * @param instance $middleware The middleware + * + * @return self The instance from which the method was called (fluent interface) + */ + public function middleware(instance $middleware): self + { + // Writing into the middlewares stack + $this->middlewares[] = $middleware; + + // Exit (success) + return $this; + } +}