2 Commits
2.3.1 ... 3.0.0

9 changed files with 662 additions and 351 deletions

View File

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

View File

@@ -1,7 +1,7 @@
{ {
"name": "mirzaev/minimal", "name": "mirzaev/minimal",
"type": "framework", "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": [ "keywords": [
"mvc", "mvc",
"framework", "framework",
@@ -14,7 +14,7 @@
"name": "Arsen Mirzaev Tatyano-Muradovich", "name": "Arsen Mirzaev Tatyano-Muradovich",
"email": "arsen@mirzaev.sexy", "email": "arsen@mirzaev.sexy",
"homepage": "https://mirzaev.sexy", "homepage": "https://mirzaev.sexy",
"role": "Developer" "role": "Programmer"
} }
], ],
"support": { "support": {
@@ -22,16 +22,11 @@
"issues": "https://git.mirzaev.sexy/mirzaev/minimal/issues" "issues": "https://git.mirzaev.sexy/mirzaev/minimal/issues"
}, },
"require": { "require": {
"php": "~8.2" "php": "~8.4"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"mirzaev\\minimal\\": "mirzaev/minimal/system" "mirzaev\\minimal\\": "mirzaev/minimal/system"
} }
},
"autoload-dev": {
"psr-4": {
"mirzaev\\minimal\\tests\\": "mirzaev/minimal/tests"
}
} }
} }

View File

@@ -6,13 +6,22 @@ namespace mirzaev\minimal;
// Files of the project // Files of the project
use mirzaev\minimal\model, use mirzaev\minimal\model,
mirzaev\minimal\core,
mirzaev\minimal\traits\magic; mirzaev\minimal\traits\magic;
// Встроенные библиотеки // Build-in libraries
use exception; 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 * @package mirzaev\minimal
* *
@@ -24,71 +33,54 @@ class controller
use magic; 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 * Constructor
*
* @param core $core The instance of the core
*
* @return self
*/ */
public function __construct() {} public function __construct(core $core) {
// Writing the core into the property
/** $this->core = $core;
* 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)
};
} }
} }

View File

@@ -4,18 +4,33 @@ declare(strict_types=1);
namespace mirzaev\minimal; namespace mirzaev\minimal;
// Файлы проекта // Files of the project
use mirzaev\minimal\router, use mirzaev\minimal\router,
mirzaev\minimal\route,
mirzaev\minimal\controller, mirzaev\minimal\controller,
mirzaev\minimal\model; mirzaev\minimal\model;
// Встроенные библиотеки // Built-in libraries
use exception, use exception,
BadMethodCallException as exception_method,
DomainException as exception_domain,
InvalidArgumentException as exception_argument,
UnexpectedValueException as exception_value,
ReflectionClass as reflection; ReflectionClass as reflection;
/** /**
* Core * 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 * @package mirzaev\minimal
* *
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
@@ -24,204 +39,326 @@ use exception,
final class core final class core
{ {
/** /**
* Инстанция соединения с базой данных * Namespace
*/
private object $db;
/**
* Инстанция маршрутизатора
*/
private readonly router $router;
/**
* Инстанция ядра контроллера
*/
private readonly controller $controller;
/**
* Инстанция ядра модели
*/
private readonly model $model;
/**
* Путь пространства имён (системное)
* *
* Используется для поиска файлов по спецификации PSR-4 * @var string $namespace Namespace where the core was initialized from
*/
private readonly string $namespace;
/**
* Конструктор
* *
* @param ?object $db Инстанция соединения с базой данных * @see https://www.php-fig.org/psr/psr-4/
* @param ?router $router Маршрутизатор
* @param ?controller $controller Инстанция ядра контроллера
* @param ?model $model Инстанция ядра модели
* @param ?string $namespace Пространство имён системного ядра
*
* @return self Инстанция ядра
*/ */
public function __construct( public string $namespace {
?object $db = null, // Read
?router $router = null, get => $this->namespace;
?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());
} }
/** /**
* Деструктор * 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() {} 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
{ {
// Обработка запроса // Matching a route
return $this->__get('router')->handle($uri, core: $this); $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 Название * Handle the route
* @param mixed $value Содержимое
* *
* @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) { // Initializing name of the controller class
'db', 'database' => (function () use ($value) { $controller = $route->controller;
if ($this->__isset('db')) throw new exception('Запрещено реинициализировать инстанцию соединения с базой данных ($this->db)', 500);
else {
// Свойство ещё не было инициализировано
if (is_object($value)) $this->db = $value; if ($route->controller instanceof controller) {
else throw new exception('Свойство $this->db должно хранить инстанцию соединения с базой данных', 500); // Initialized the controller
} else if (class_exists($controller = "$this->namespace\\controllers\\$controller")) {
// Found the controller by its name
// 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; // Executing method of the controller that handles the route and exit (success)
else throw new exception('Свойство $this->router должно хранить инстанцию маршрутизатора (mirzaev\minimal\router)"', 500); 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; // Executing method of the controller that handles the route and exit (success)
else throw new exception('Свойство $this->controller должно хранить инстанцию ядра контроллеров (mirzaev\minimal\controller)', 500); 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; // Executing method of the controller that handles the route and exit (success)
else throw new exception('Свойство $this->model должно хранить инстанцию ядра моделей (mirzaev\minimal\model)', 500); 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; // Executing method of the controller that handles the route and exit (success)
else throw new exception('Свойство $this->namespace должно хранить строку с путём пространства имён', 500); return $route->controller->{$route->method}($route->parameters + $_PATCH, $_FILES);
} } else {
})(), // Not found the method of the controller
default => throw new exception("Свойство \"\$$name\" не найдено", 404)
};
}
/** // 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)) {
return $this->router; // Found the method of the controller
})(),
'controller' => (function () {
// Инициализация со значением по умолчанию
if (!$this->__isset('controller')) $this->__set('controller', new controller);
// Возврат (успех) // Executing method of the controller that handles the route and exit (success)
return $this->controller; return $route->controller->{$route->method}($route->parameters);
})(), } else {
'model' => (function () { // Not found the method of the controller
// Инициализация со значением по умолчанию
if (!$this->__isset('model')) $this->__set('model', new model);
// Возврат (успех) // Exit (fail)
return $this->model; throw new exception('Failed to find the method of the controller: ' . $route->method);
})(), }
'namespace' => $this->namespace ?? throw new exception("Свойство \"\$$name\" не инициализировано", 500), } else if ($method === 'OPTIONS') {
default => throw new exception("Свойство \"\$$name\" не найдено", 404) // OPTIONS
};
}
/** if (method_exists($route->controller, $route->method)) {
* Проверить свойство на инициализированность // Found the method of the controller
*
* @param string $name Название
*
* @return bool Инициализировано свойство?
*/
public function __isset(string $name): bool
{
return match ($name) {
default => isset($this->{$name})
};
}
/** // Executing method of the controller that handles the route and exit (success)
* Удалить свойство return $route->controller->{$route->method}($route->parameters);
* } else {
* @param string $name Название // Not found the method of the controller
*
* @return void // Exit (fail)
*/ throw new exception('Failed to find the method of the controller: ' . $route->method);
public function __unset(string $name): void }
{ } else if ($method === 'CONNECT') {
match ($name) { // CONNECT
default => (function () use ($name) {
// Удаление if (method_exists($route->controller, $route->method)) {
unset($this->{$name}); // 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 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;
} }
} }

View File

@@ -7,11 +7,10 @@ namespace mirzaev\minimal;
// Files of the project // Files of the project
use mirzaev\minimal\traits\magic; use mirzaev\minimal\traits\magic;
// Built-in libraries
use exception;
/** /**
* Model (base) * Model
*
* @method self __construct() Constructor
* *
* @package mirzaev\minimal * @package mirzaev\minimal
* *
@@ -22,13 +21,10 @@ class model
{ {
use magic; use magic;
/**
* Postfix of file names
*/
public const string POSTFIX = '_model';
/** /**
* Constructor * Constructor
*
* @return self
*/ */
public function __construct() {} public function __construct() {}
} }

127
mirzaev/minimal/system/route.php Executable file
View File

@@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
namespace mirzaev\minimal;
/**
* Route
*
* @param string|controller $controller Name of the controller
* @param string $method Name of the method of the method of $this->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 <arsen@mirzaev.sexy>
*/
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;
}
}

View File

@@ -4,11 +4,21 @@ declare(strict_types=1);
namespace mirzaev\minimal; namespace mirzaev\minimal;
// Файлы проекта // Files of the project
use mirzaev\minimal\core; 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 * @package mirzaev\minimal
* *
@@ -17,58 +27,57 @@ use mirzaev\minimal\core;
*/ */
final class router final class router
{ {
/** use singleton;
* @var array $router Реестр маршрутов
*/
/* protected array $routes = []; */
public array $routes = [];
/** /**
* Записать маршрут * Routes
* *
* @param string $route Маршрут * @var array $routes The registry of routes
* @param string $handler Обработчик - инстанции контроллера и модели (не обязательно), без постфиксов */
* @param ?string $method Вызываемый метод в инстанции контроллера обработчика protected array $routes = [];
* @param ?string $request HTTP-метод запроса (GET, POST, PUT...)
* @param ?string $model Инстанция модели (переопределение инстанции модели в $target) /**
* @param array $variables * 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( public function write(
string $route, string $urn,
string $handler, route $route,
?string $method = 'index', null|string|array $method = 'GET'
?string $request = 'GET', ): self {
?string $model = null, foreach (is_array($method) ? $method : [$method] as $method) {
array $variables = [] // Iterate over methods of requests
): static {
// Запись в реестр // Validating method
$this->routes[$route][$request] = [ $method = match ($method) {
'controller' => $handler, 'POST', 'GET', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'CONNECT', 'TRACE' => $method,
'model' => $model ?? $handler, default => throw new exception_argument("Failed to initialize method: \"$method\"")
'method' => $method, };
'variables' => $variables
]; // Writing to the registry of routes
$this->routes[$urn][$method] = $route;
}
// Exit (success) (fluent interface) // Exit (success) (fluent interface)
return $this; return $this;
} }
/** /**
* Handle a request * Match
* *
* @param string|null $uri URI (protocol://domain/foo/bar) * Match request URI with registry of routes
* @param string|null $method Method (GET, POST, PUT...)
* @param core|null $core Instence of the system core
* *
* @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( public function match(string $uri, string $method): ?route {
?string $uri = null,
?string $method = null,
?core $core = new core
): string|int|null {
// Declaration of the registry of routes directoies // Declaration of the registry of routes directoies
$routes = []; $routes = [];
@@ -83,15 +92,8 @@ final class router
if (count($routes) === count($this->routes)) { if (count($routes) === count($this->routes)) {
// Initialized the registry of routes directoies // Initialized the registry of routes directoies
// Initializing default values // Universalization of URN (/foo/bar)
$uri ??= $_SERVER['REQUEST_URI'] ?? '/'; $urn = self::universalize(parse_url(urldecode($uri), PHP_URL_PATH));
$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) // Search directories of URN (explode() creates empty value in array)
preg_match_all('/(^\/$|[^\/]+)/', $urn, $directories); preg_match_all('/(^\/$|[^\/]+)/', $urn, $directories);
@@ -171,12 +173,12 @@ final class router
// The directory is a variable ($variable) // 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) { } else if (preg_match('/^\$([a-zA-Z_\x80-\xff]+\.\.\.)$/', $route_directory) === 1) {
// The directory of route is a collector ($variable...) // The directory of route is a collector ($variable...)
// Инициализаия ссылки на массив сборщика // Инициализаия ссылки на массив сборщика
$collector = &$data[$method]['variables'][trim($route_directory, '$.')]; $collector = &$data[$method]->variables[trim($route_directory, '$.')];
// Инициализаия массива сборщика // Инициализаия массива сборщика
$collector ??= []; $collector ??= [];
@@ -195,31 +197,13 @@ final class router
} }
} }
/** // Exit (success or fail)
* Initialization of route handlers return $data[$method] ?? null;
*/
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 (fail)
return $this->error($core); 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 ПЕРЕДЕЛАТЬ ПОЛНОСТЬЮ * @todo ПЕРЕДЕЛАТЬ ПОЛНОСТЬЮ
*/ */
public function sort(): static public function sort(): self
{ {
uksort($this->routes, function (string $a, string $b) { uksort($this->routes, function (string $a, string $b) {
// Sorting routes // Sorting routes
@@ -284,35 +268,18 @@ final class router
return $this; 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 * Universalize URN
* *
* Always "/" at the beginning and never "/" at the end * 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 * @return string Universalized URN
*/ */
private function universalize(string $urn): string private function universalize(string $urn): string
{ {
// Universalization of URN and exit (success) // Universalization of URN and exit (success)
return (string) preg_replace('/(.+)\/$/', '$1', preg_replace('/^([^\/])/', '/$1', $urn)); return (string) '/' . mb_trim($urn, '/');
} }
} }

View File

@@ -10,12 +10,14 @@ use exception;
/** /**
* Trait of magical methods * 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 mixed __get(string $name) Read property
* @method void __unset(string $name) Delete property * @method void __unset(string $name) Delete property
* @method bool __isset(string $name) Check property for initialization * @method bool __isset(string $name) Check property for initialization
* *
* @package mirzaev\minimal\traits * @package mirzaev\minimal\traits
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
trait magic trait magic

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace mirzaev\minimal\traits;
// Built-in libraries
use exception;
/**
* Trait of singleton
*
* @method static initialize() Initialize an instance
*
* @package mirzaev\minimal\traits
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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');
}
}