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