4 Commits
2.1.0 ... 2.3.1

10 changed files with 706 additions and 505 deletions

0
.gitignore vendored Normal file → Executable file
View File

11
LICENSE Normal file
View File

@@ -0,0 +1,11 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

54
README.md Normal file
View File

@@ -0,0 +1,54 @@
🤟 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`)
*personally, i prefer **ArangoDB** and **Twig***
## Nearest plans (first half of 2025)
1. Add **middlewares** technology
2. Route sorting in the router: `router::sort()`
3. Add trigger routes from within routes
4. Think about adding asynchronous executions
5. Write an article describing the principles of the framework
## Installation
Execute: `composer require mirzaev/minimal`
## Usage
*index.php*
```
// Initializing the router
$router = new router;
// Initializing of routes
$router
->write('/', 'catalog', 'index', 'GET')
->write('/search', 'catalog', 'search', 'POST')
->write('/session/connect/telegram', 'session', 'telegram', 'POST')
->write('/product/$id', 'catalog', 'product', 'POST')
->write('/$categories...', 'catalog', 'index', 'POST'); // Collector (since 0.3.0)
// Initializing the core
$core = new core(namespace: __NAMESPACE__, router: $router, controller: new controller(false), model: new model(false));
// Handle the request
echo $core->start();
```
## Examples of projects based on MINIMAL
### ebala (⚠VERY HUGE)
Repository: https://git.mirzaev.sexy/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**
### notchat
Repository: https://git.mirzaev.sexy/mirzaev/notchat
Github mirror: https://github.com/mature-woman/notchat
**P2P chat project with different blockchains and smart stuff**
### site-repression
Link: https://repression.mirzaev.sexy
Repository: https://git.mirzaev.sexy/mirzaev/site-repression
Github mirror: https://github.com/mature-woman/site-repression
**A simple site for my article about *political repressions in Russia* and my *abduction by Wagner PMC operatives* from my home**

0
composer.json Normal file → Executable file
View File

0
composer.lock generated Normal file → Executable file
View File

166
mirzaev/minimal/system/controller.php Normal file → Executable file
View File

@@ -4,121 +4,91 @@ declare(strict_types=1);
namespace mirzaev\minimal; namespace mirzaev\minimal;
// Файлы проекта // Files of the project
use mirzaev\minimal\model; use mirzaev\minimal\model,
mirzaev\minimal\traits\magic;
// Встроенные библиотеки // Встроенные библиотеки
use exception; use exception;
/** /**
* Контроллер * Controller (base)
* *
* @package mirzaev\minimal * @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> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
class controller class controller
{ {
/** use magic;
* Постфикс
*/
private const POSTFIX = '_controller';
/** /**
* Инстанция модели * Postfix of file names
*/ */
protected model $model; public const string POSTFIX = '_controller';
/** /**
* Инстанция шаблонизатора представления * Instance of the model connected in the router
*/ */
protected object $view; protected model $model;
/** /**
* Конструктор * View template engine instance (twig)
*/ */
public function __construct() protected object $view;
{
}
/** /**
* Записать свойство * Constructor
* */
* @param string $name Название public function __construct() {}
* @param mixed $value Значение
*
* @return void
*/
public function __set(string $name, mixed $value = null): void
{
match ($name) {
'POSTFIX' => throw new exception('Запрещено реинициализировать постфикс ($this::POSTFIX)', 500),
'model' => (function () use ($value) {
if ($this->__isset('model')) throw new exception('Запрещено реинициализировать свойство с инстанцией модели ($this->model)', 500);
else {
// Свойство не инициализировано
if (is_object($value)) $this->model = $value; /**
else throw new exception('Свойство $this->model должно хранить инстанцию модели (объект)', 500); * Write property
} *
})(), * @param string $name Name of the property
'view' => (function () use ($value) { * @param mixed $value Value of the property
if ($this->__isset('view')) throw new exception('Запрещено реинициализировать свойство с инстанцией шаблонизатора представления ($this->view)', 500); *
else { * @return void
// Свойство не инициализировано */
public function __set(string $name, mixed $value = null): void
{
match ($name) {
'model' => (function () use ($value) {
if ($this->__isset('model')) throw new exception('Can not reinitialize property: ' . static::class . '::$model', 500);
else {
// Property not initialized
if (is_object($value)) $this->view = $value; if (is_object($value)) $this->model = $value;
else throw new exception('Свойство $this->view должно хранить инстанцию шаблонизатора представления (объект)', 500); else throw new exception('Property "' . static::class . '::view" should store an instance of a model', 500);
} }
})(), })(),
default => throw new exception("Свойство \"\$$name\" не найдено", 404) '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);
* }
* @param string $name Название })(),
* default => throw new exception('Property "' . static::class . "::\$$name\" not found", 404)
* @return mixed Содержимое };
*/ }
public function __get(string $name): mixed
{
return match ($name) {
'POSTFIX' => $this::POSTFIX ?? throw new exception("Свойство \"POSTFIX\" не инициализировано", 500),
'model' => $this->model ?? throw new exception("Свойство \"\$model\" не инициализировано", 500),
'view' => $this->view ?? throw new exception("Свойство \"\$view\" не инициализировано", 500),
default => throw new exception("Свойство \"\$$name\" не обнаружено", 404)
};
}
/** /**
* Проверить свойство на инициализированность * Read property
* *
* @param string $name Название * @param string $name Name of the property
* *
* @return bool Инициализировано свойство? * @return mixed Value of the property
*/ */
public function __isset(string $name): bool public function __get(string $name): mixed
{ {
return match ($name) { return match ($name) {
default => isset($this->{$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)
};
/** }
* Удалить свойство
*
* @param string $name Название
*
* @return void
*/
public function __unset(string $name): void
{
match ($name) {
default => (function () use ($name) {
// Удаление
unset($this->{$name});
})()
};
}
} }

379
mirzaev/minimal/system/core.php Normal file → Executable file
View File

@@ -6,225 +6,222 @@ namespace mirzaev\minimal;
// Файлы проекта // Файлы проекта
use mirzaev\minimal\router, use mirzaev\minimal\router,
mirzaev\minimal\controller, mirzaev\minimal\controller,
mirzaev\minimal\model; mirzaev\minimal\model;
// Встроенные библиотеки // Встроенные библиотеки
use exception, use exception,
ReflectionClass as reflection; ReflectionClass as reflection;
/** /**
* Ядро * Core
* *
* @package mirzaev\minimal * @package mirzaev\minimal
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
* *
* @todo * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* 1. Добавить __isset() и __unset() * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class core final class core
{ {
/** /**
* Инстанция соединения с базой данных * Инстанция соединения с базой данных
*/ */
private object $db; private object $db;
/** /**
* Инстанция маршрутизатора * Инстанция маршрутизатора
*/ */
private readonly router $router; private readonly router $router;
/** /**
* Инстанция ядра контроллера * Инстанция ядра контроллера
*/ */
private readonly controller $controller; private readonly controller $controller;
/** /**
* Инстанция ядра модели * Инстанция ядра модели
*/ */
private readonly model $model; private readonly model $model;
/** /**
* Путь пространства имён (системное) * Путь пространства имён (системное)
* *
* Используется для поиска файлов по спецификации PSR-4 * Используется для поиска файлов по спецификации PSR-4
*/ */
private readonly string $namespace; private readonly string $namespace;
/** /**
* Конструктор * Конструктор
* *
* @param ?object $db Инстанция соединения с базой данных * @param ?object $db Инстанция соединения с базой данных
* @param ?router $router Маршрутизатор * @param ?router $router Маршрутизатор
* @param ?controller $controller Инстанция ядра контроллера * @param ?controller $controller Инстанция ядра контроллера
* @param ?model $model Инстанция ядра модели * @param ?model $model Инстанция ядра модели
* @param ?string $namespace Пространство имён системного ядра * @param ?string $namespace Пространство имён системного ядра
* *
* @return self Инстанция ядра * @return self Инстанция ядра
*/ */
public function __construct( public function __construct(
?object $db = null, ?object $db = null,
?router $router = null, ?router $router = null,
?controller $controller = null, ?controller $controller = null,
?model $model = null, ?model $model = null,
?string $namespace = null ?string $namespace = null
) { ) {
// Инициализация свойств // Инициализация свойств
if (isset($db)) $this->__set('db', $db); if (isset($db)) $this->__set('db', $db);
if (isset($router)) $this->__set('router', $router); if (isset($router)) $this->__set('router', $router);
if (isset($controller)) $this->__set('controller', $controller); if (isset($controller)) $this->__set('controller', $controller);
if (isset($model)) $this->__set('model', $model); if (isset($model)) $this->__set('model', $model);
$this->__set('namespace', $namespace ?? (new reflection(self::class))->getNamespaceName()); $this->__set('namespace', $namespace ?? (new reflection(self::class))->getNamespaceName());
} }
/** /**
* Деструктор * Деструктор
* *
*/ */
public function __destruct() public function __destruct() {}
{
}
/** /**
* Запуск * Запуск
* *
* @param ?string $uri Маршрут * @param ?string $uri Маршрут
* *
* @return ?string Сгенерированный ответ (HTML, JSON...) * @return string|int|null Ответ
*/ */
public function start(string $uri = null): ?string public function start(string $uri = null): string|int|null
{ {
// Обработка запроса // Обработка запроса
return $this->__get('router')->handle($uri, core: $this); return $this->__get('router')->handle($uri, core: $this);
} }
/** /**
* Записать свойство * Записать свойство
* *
* @param string $name Название * @param string $name Название
* @param mixed $value Содержимое * @param mixed $value Содержимое
* *
* @return void * @return void
*/ */
public function __set(string $name, mixed $value = null): void public function __set(string $name, mixed $value = null): void
{ {
match ($name) { match ($name) {
'db', 'database' => (function () use ($value) { 'db', 'database' => (function () use ($value) {
if ($this->__isset('db')) throw new exception('Запрещено реинициализировать инстанцию соединения с базой данных ($this->db)', 500); if ($this->__isset('db')) throw new exception('Запрещено реинициализировать инстанцию соединения с базой данных ($this->db)', 500);
else { else {
// Свойство ещё не было инициализировано // Свойство ещё не было инициализировано
if (is_object($value)) $this->db = $value; if (is_object($value)) $this->db = $value;
else throw new exception('Свойство $this->db должно хранить инстанцию соединения с базой данных', 500); else throw new exception('Свойство $this->db должно хранить инстанцию соединения с базой данных', 500);
} }
})(), })(),
'router' => (function () use ($value) { 'router' => (function () use ($value) {
if ($this->__isset('router')) throw new exception('Запрещено реинициализировать инстанцию маршрутизатора ($this->router)', 500); if ($this->__isset('router')) throw new exception('Запрещено реинициализировать инстанцию маршрутизатора ($this->router)', 500);
else { else {
// Свойство ещё не было инициализировано // Свойство ещё не было инициализировано
if ($value instanceof router) $this->router = $value; if ($value instanceof router) $this->router = $value;
else throw new exception('Свойство $this->router должно хранить инстанцию маршрутизатора (mirzaev\minimal\router)"', 500); else throw new exception('Свойство $this->router должно хранить инстанцию маршрутизатора (mirzaev\minimal\router)"', 500);
} }
})(), })(),
'controller' => (function () use ($value) { 'controller' => (function () use ($value) {
if ($this->__isset('controller')) throw new exception('Запрещено реинициализировать инстанцию ядра контроллеров ($this->controller)', 500); if ($this->__isset('controller')) throw new exception('Запрещено реинициализировать инстанцию ядра контроллеров ($this->controller)', 500);
else { else {
// Свойство не инициализировано // Свойство не инициализировано
if ($value instanceof controller) $this->controller = $value; if ($value instanceof controller) $this->controller = $value;
else throw new exception('Свойство $this->controller должно хранить инстанцию ядра контроллеров (mirzaev\minimal\controller)', 500); else throw new exception('Свойство $this->controller должно хранить инстанцию ядра контроллеров (mirzaev\minimal\controller)', 500);
} }
})(), })(),
'model' => (function () use ($value) { 'model' => (function () use ($value) {
if ($this->__isset('model')) throw new exception('Запрещено реинициализировать инстанцию ядра моделей ($this->model)', 500); if ($this->__isset('model')) throw new exception('Запрещено реинициализировать инстанцию ядра моделей ($this->model)', 500);
else { else {
// Свойство не инициализировано // Свойство не инициализировано
if ($value instanceof model) $this->model = $value; if ($value instanceof model) $this->model = $value;
else throw new exception('Свойство $this->model должно хранить инстанцию ядра моделей (mirzaev\minimal\model)', 500); else throw new exception('Свойство $this->model должно хранить инстанцию ядра моделей (mirzaev\minimal\model)', 500);
} }
})(), })(),
'namespace' => (function () use ($value) { 'namespace' => (function () use ($value) {
if ($this->__isset('namespace')) throw new exception('Запрещено реинициализировать путь пространства имён ($this->namespace)', 500); if ($this->__isset('namespace')) throw new exception('Запрещено реинициализировать путь пространства имён ($this->namespace)', 500);
else { else {
// Свойство не инициализировано // Свойство не инициализировано
if (is_string($value)) $this->namespace = $value; if (is_string($value)) $this->namespace = $value;
else throw new exception('Свойство $this->namespace должно хранить строку с путём пространства имён', 500); else throw new exception('Свойство $this->namespace должно хранить строку с путём пространства имён', 500);
} }
})(), })(),
default => throw new exception("Свойство \"\$$name\" не найдено", 404) default => throw new exception("Свойство \"\$$name\" не найдено", 404)
}; };
} }
/** /**
* Прочитать свойство * Прочитать свойство
* *
* Записывает значение по умолчанию, если свойство не инициализировано * Записывает значение по умолчанию, если свойство не инициализировано
* *
* @param string $name Название * @param string $name Название
* *
* @return mixed Содержимое * @return mixed Содержимое
*/ */
public function __get(string $name): mixed public function __get(string $name): mixed
{ {
return match ($name) { return match ($name) {
'db', 'database' => $this->db ?? throw new exception("Свойство \"\$$name\" не инициализировано", 500), 'db', 'database' => $this->db ?? throw new exception("Свойство \"\$$name\" не инициализировано", 500),
'router' => (function () { 'router' => (function () {
// Инициализация со значением по умолчанию // Инициализация со значением по умолчанию
if (!$this->__isset('router')) $this->__set('router', new router); if (!$this->__isset('router')) $this->__set('router', new router);
// Возврат (успех) // Возврат (успех)
return $this->router; return $this->router;
})(), })(),
'controller' => (function () { 'controller' => (function () {
// Инициализация со значением по умолчанию // Инициализация со значением по умолчанию
if (!$this->__isset('controller')) $this->__set('controller', new controller); if (!$this->__isset('controller')) $this->__set('controller', new controller);
// Возврат (успех) // Возврат (успех)
return $this->controller; return $this->controller;
})(), })(),
'model' => (function () { 'model' => (function () {
// Инициализация со значением по умолчанию // Инициализация со значением по умолчанию
if (!$this->__isset('model')) $this->__set('model', new model); if (!$this->__isset('model')) $this->__set('model', new model);
// Возврат (успех) // Возврат (успех)
return $this->model; return $this->model;
})(), })(),
'namespace' => $this->namespace ?? throw new exception("Свойство \"\$$name\" не инициализировано", 500), 'namespace' => $this->namespace ?? throw new exception("Свойство \"\$$name\" не инициализировано", 500),
default => throw new exception("Свойство \"\$$name\" не найдено", 404) default => throw new exception("Свойство \"\$$name\" не найдено", 404)
}; };
} }
/** /**
* Проверить свойство на инициализированность * Проверить свойство на инициализированность
* *
* @param string $name Название * @param string $name Название
* *
* @return bool Инициализировано свойство? * @return bool Инициализировано свойство?
*/ */
public function __isset(string $name): bool public function __isset(string $name): bool
{ {
return match ($name) { return match ($name) {
default => isset($this->{$name}) default => isset($this->{$name})
}; };
} }
/** /**
* Удалить свойство * Удалить свойство
* *
* @param string $name Название * @param string $name Название
* *
* @return void * @return void
*/ */
public function __unset(string $name): void public function __unset(string $name): void
{ {
match ($name) { match ($name) {
default => (function () use ($name) { default => (function () use ($name) {
// Удаление // Удаление
unset($this->{$name}); unset($this->{$name});
})() })()
}; };
} }
} }

89
mirzaev/minimal/system/model.php Normal file → Executable file
View File

@@ -4,88 +4,31 @@ declare(strict_types=1);
namespace mirzaev\minimal; namespace mirzaev\minimal;
// Встроенные библиотеки // Files of the project
use mirzaev\minimal\traits\magic;
// Built-in libraries
use exception; use exception;
/** /**
* Модель * Model (base)
* *
* @package mirzaev\minimal * @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> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
class model class model
{ {
/** use magic;
* Постфикс
*/
private const POSTFIX = '_model';
/** /**
* Конструктор * Postfix of file names
*/ */
public function __construct() public const string POSTFIX = '_model';
{
}
/** /**
* Записать свойство * Constructor
* */
* @param string $name Название public function __construct() {}
* @param mixed $value Содержимое
*
* @return void
*/
public function __set(string $name, mixed $value = null): void
{
match ($name) {
'POSTFIX' => throw new exception('Запрещено реинициализировать постфикс ($this::POSTFIX)', 500),
default => throw new exception("Свойство \"\$$name\" не найдено", 404)
};
}
/**
* Прочитать свойство
*
* @param string $name Название
*
* @return mixed Содержимое
*/
public function __get(string $name): mixed
{
return match ($name) {
'POSTFIX' => $this::POSTFIX ?? throw new exception("Свойство \"POSTFIX\" не инициализировано", 500),
default => throw new exception("Свойство \"\$$name\" не обнаружено", 404)
};
}
/**
* Проверить свойство на инициализированность
*
* @param string $name Название
*
* @return bool Инициализировано свойство?
*/
public function __isset(string $name): bool
{
return match ($name) {
default => isset($this->{$name})
};
}
/**
* Удалить свойство
*
* @param string $name Название
*
* @return void
*/
public function __unset(string $name): void
{
match ($name) {
default => (function () use ($name) {
// Удаление
unset($this->{$name});
})()
};
}
} }

431
mirzaev/minimal/system/router.php Normal file → Executable file
View File

@@ -11,163 +11,308 @@ use mirzaev\minimal\core;
* Маршрутизатор * Маршрутизатор
* *
* @package mirzaev\minimal * @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> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
final class router final class router
{ {
/** /**
* @var array $router Реестр маршрутов * @var array $router Реестр маршрутов
*/ */
protected array $routes = []; /* protected array $routes = []; */
public array $routes = [];
/** /**
* Записать маршрут * Записать маршрут
*
* @param string $route Маршрут
* @param string $handler Обработчик - инстанции контроллера и модели (не обязательно), без постфиксов
* @param ?string $method Вызываемый метод в инстанции контроллера обработчика
* @param ?string $request HTTP-метод запроса (GET, POST, PUT...)
* @param ?string $model Инстанция модели (переопределение инстанции модели в $target)
* @param array $variables
*
* @return static The instance from which the method was called (fluent interface)
*/
public function write(
string $route,
string $handler,
?string $method = 'index',
?string $request = 'GET',
?string $model = null,
array $variables = []
): static {
// Запись в реестр
$this->routes[$route][$request] = [
'controller' => $handler,
'model' => $model ?? $handler,
'method' => $method,
'variables' => $variables
];
// Exit (success) (fluent interface)
return $this;
}
/**
* Handle a request
*
* @param string|null $uri URI (protocol://domain/foo/bar)
* @param string|null $method Method (GET, POST, PUT...)
* @param core|null $core Instence of the system core
*
* @return string|int|null Response
*/
public function handle(
?string $uri = null,
?string $method = null,
?core $core = new core
): string|int|null {
// Declaration of the registry of routes directoies
$routes = [];
foreach ($this->routes as $route => $data) {
// Iteration over routes
// Search directories of route (explode() creates empty value in array)
preg_match_all('/(^\/$|[^\/]+)/', $route, $data['directories']);
$routes[$route] = $data['directories'][0];
}
if (count($routes) === count($this->routes)) {
// Initialized the registry of routes directoies
// Initializing default values
$uri ??= $_SERVER['REQUEST_URI'] ?? '/';
$method ??= $_SERVER["REQUEST_METHOD"];
// Initializing of URN (/foo/bar)
$urn = parse_url(urldecode($uri), PHP_URL_PATH);
// Universalization of URN
$urn = self::universalize($urn);
// Search directories of URN (explode() creates empty value in array)
preg_match_all('/(^\/$|[^\/]+)/', $urn, $directories);
$directories = $directories[0];
/**
* Initialization of the route
*/
// Initializing the buffer of matches of route directories with URN directories
$matches = [];
foreach ($directories as $i => $urn_directory) {
// Iteration over directories of URN
foreach ($this->routes as $route => $data) {
// Iteration over routes
if (isset($data[$method])) {
// The request method matches the route method
// Universalization of route
$route = self::universalize($route);
// Skipping unmatched routes based on results of previous iterations
if (isset($matches[$route]) && $matches[$route] === false) continue;
// Initializing of route directory
$route_directory = $routes[$route][$i] ?? null;
if (isset($route_directory)) {
// Initialized of route directory
if ($urn_directory === $route_directory) {
// The directory of URN is identical to the directory of route
// Writing: end of URN directories XNOR end of route directories
$matches[$route] = !(isset($directories[$_i = $i + 1]) xor isset($routes[$route][$_i]));
} else if (preg_match('/^\$([a-zA-Z_\x80-\xff]+)$/', $route_directory) === 1) {
// The directory of route is a variable ($variable)
// Writing: end of URN directories XNOR end of route directories
$matches[$route] = !(isset($directories[$_i = $i + 1]) xor isset($routes[$route][$_i]));
} else if (
!isset($routes[$route][$i + 1])
&& preg_match('/^\$([a-zA-Z_\x80-\xff]+\.\.\.)$/', $route_directory) === 1
) {
// The directory of route is a collector ($variable...)
// AND this is the end of route directories
// Writing
$matches[$route] = 'collector';
} else $matches[$route] = false;
} else if ($matches[$route] === 'collector') {
} else $matches[$route] = false;
}
}
}
// Finding a priority route from match results
foreach ($matches as $route => $match) if ($match !== false) break;
if ($route && !empty($data = $this->routes[$route])) {
// Route found
// Universalization of route
$route = self::universalize($route);
/**
* Initialization of route variables
*/
foreach ($routes[$route] as $i => $route_directory) {
// Iteration over directories of route
if (preg_match('/^\$([a-zA-Z_\x80-\xff]+)$/', $route_directory) === 1) {
// The directory is a variable ($variable)
// Запись в реестр переменных и перещапись директории в маршруте
$data[$method]['variables'][trim($route_directory, '$')] = $directories[$i];
} else if (preg_match('/^\$([a-zA-Z_\x80-\xff]+\.\.\.)$/', $route_directory) === 1) {
// The directory of route is a collector ($variable...)
// Инициализаия ссылки на массив сборщика
$collector = &$data[$method]['variables'][trim($route_directory, '$.')];
// Инициализаия массива сборщика
$collector ??= [];
// Запись в реестр переменных
$collector[] = $directories[$i];
foreach (array_slice($directories, ++$i, preserve_keys: true) as &$urn_directory) {
// Перебор директорий URN
// Запись в реестр переменных
$collector[] = $urn_directory;
}
break;
}
}
/**
* Initialization of route handlers
*/
if (array_key_exists($method, $data)) {
// Идентифицирован метод маршрута (GET, POST, PUT...)
// Инициализация обработчиков (controller, model, method)
$handlers = $data[$method];
if (class_exists($controller = $core->namespace . '\\controllers\\' . $handlers['controller'] . $core->controller::POSTFIX)) {
// Найден контроллер
// Инициализация инстанции ядра контроллера
$controller = new $controller;
// Вызов связанного с маршрутом метода и возврат (успех)
return $controller->{$handlers['method']}($handlers['variables'] + $_REQUEST, $_FILES, file_get_contents('php://input'));
}
}
}
}
// Возврат (провал)
return $this->error($core);
}
/**
* Sorting routes
*
* 1. Short routes
* 2. Long routes
* 3. Short routes with variables (position of variables from "right" to "left")
* 4. Long routes with variables (position of variables from "right" to "left")
* 5. Short routes with collector
* 6. Long routes with collector
* 7. Short routes with variables and collector (position of variables from "right" to "left" then by amount)
* 8. Long routes with variables and collector (position of variables from "right" to "left")
*
* Добавить чтобы сначала текст потом переменная затем после переменной первыми тексты и в конце перменные опять и так рекурсивно
*
* @return static The instance from which the method was called (fluent interface)
* *
* @param string $route Маршрут * @todo ПЕРЕДЕЛАТЬ ПОЛНОСТЬЮ
* @param string $handler Обработчик - инстанции контроллера и модели (не обязательно), без постфиксов */
* @param ?string $method Вызываемый метод в инстанции контроллера обработчика public function sort(): static
* @param ?string $request HTTP-метод запроса (GET, POST, PUT...) {
* @param ?string $model Инстанция модели (переопределение инстанции модели в $target) uksort($this->routes, function (string $a, string $b) {
* // Sorting routes
* @return void
*/
public function write(
string $route,
string $handler,
?string $method = 'index',
?string $request = 'GET',
?string $model = null
): void {
// Запись в реестр
$this->routes[$route][$request] = [
'controller' => $handler,
'model' => $model ?? $handler,
'method' => $method
];
}
/** // Initialization of string lengths (multibyte-safe)
* Обработать маршрут $length = [
* $a => mb_strlen($a),
* @param ?string $uri URI запроса (https://domain.com/foo/bar) $b => mb_strlen($b)
* @param ?string $method Метод запроса (GET, POST, PUT...) ];
* @param ?core $core Инстанция системного ядра
*/
public function handle(?string $uri = null, ?string $method = null, ?core $core = new core): ?string
{
// Инициализация значений по умолчанию
$uri ??= $_SERVER['REQUEST_URI'] ?? '/';
$method ??= $_SERVER["REQUEST_METHOD"] ?? 'GET';
// Инициализация URL запроса (/foo/bar) // Initialization of the presence of variables
$url = parse_url($uri, PHP_URL_PATH); $variables = [
$a => preg_match('/\$([a-zA-Z_\x80-\xff]+)(\/|$)/', $a) === 1,
$b => preg_match('/\$([a-zA-Z_\x80-\xff]+)(\/|$)/', $b) === 1
];
// Универсализация маршрута // Initialization of the presence of collectors
$url = self::universalize($url); $collectors = [
$a => preg_match('/\$([a-zA-Z_\x80-\xff]+)\.\.\.$/', $a) === 1,
$b => preg_match('/\$([a-zA-Z_\x80-\xff]+)\.\.\.$/', $b) === 1
];
// Сортировка реестра маршрутов от большего ключа к меньшему (кешируется)
krsort($this->routes);
// Поиск директорий в ссылке if ($variables[$a] && !$variables[$b]) return 1;
preg_match_all('/[^\/]+/', $url, $directories); else if (!$variables[$a] && $variables[$b]) return -1;
else if ($variables[$a] && $variables[$b]) {
} else if ($collectors[$a] && !$collectors[$b]) return 1;
else if (!$collectors[$a] && $collectors[$b]) return -1;
else {
// NOR variables and XAND collectors (both are not found or both are found)
// Инициализация директорий // The routes are the same length (no changes)
$directories = $directories[0]; if ($length[$a] === $length[$b]) return 0;
foreach ($this->routes as $route => $data) { // The longer route moves to the end
// Перебор маршрутов return $length[$a] > $length[$b] ? 1 : -1;
}
});
// Универсализация маршрута // Exit (success) (fluent interface)
$route = self::universalize($route); return $this;
}
// Поиск директорий /**
preg_match_all('/[^\/]+/', $route, $data['directories']); * Сгенерировать ответ с ошибкой
*
* Вызывает метод 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;
}
// Инициализация директорий /**
$data['directories'] = $data['directories'][0]; * Universalize URN
*
if (count($directories) === count($data['directories'])) { * Always "/" at the beginning and never "/" at the end
// Входит в диапазон маршрут (совпадает количество директорий у ссылки и маршрута) *
* @param string &$urn URN (/foo/bar)
// Инициализация реестра переменных *
$data['vars'] = []; * @return string Universalized URN
*/
foreach ($data['directories'] as $index => &$directory) { private function universalize(string $urn): string
// Перебор директорий {
// Universalization of URN and exit (success)
if (preg_match('/\$([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]+)/', $directory) === 1) { return (string) preg_replace('/(.+)\/$/', '$1', preg_replace('/^([^\/])/', '/$1', $urn));
// Директория является переменной (.../$variable/...) }
// Запись в реестр переменных
$directory = $data['vars'][trim($directory, '$')] = $directories[$index];
}
}
// Реиницилазция маршрута
$route = self::universalize(implode('/', $data['directories']));
// Проверка на пустой маршрут
if (empty($route)) $route = '/';
if (mb_stripos($route, $url, 0, "UTF-8") === 0 && mb_strlen($route, 'UTF-8') <= mb_strlen($url, 'UTF-8')) {
// Идентифицирован маршрут (длина не меньше длины запрошенного URL)
if (array_key_exists($method, $data)) {
// Идентифицирован метод маршрута (GET, POST, PUT...)
$route = $data[$method];
if (class_exists($controller = $core->namespace . '\\controllers\\' . $route['controller'] . $core->controller::POSTFIX)) {
// Найден контроллер
// Инициализация инстанции ядра контроллера
$controller = new $controller;
// Инициализация инстанции ядра модели
if (class_exists($model = $core->namespace . '\\models\\' . $route['model'] . $core->model::POSTFIX)) $controller->model = new $model;
// Вызов связанного с маршрутом методв и возврат (успех)
return $controller->{$route['method']}($data['vars'] + $_REQUEST, $_FILES);
}
}
// Выход из цикла (провал)
break;
}
}
}
// Возврат (провал)
return $this->error($core);
}
/**
* Сгенерировать ответ с ошибкой
*
* Вызывает метод error404 в инстанции контроллера ошибок
*
* @param ?core $core Инстанция системного ядра
*
* @return ?string HTML-документ
*/
private function error(core $core = new core): ?string
{
return class_exists($class = '\\' . $core->namespace . '\\controllers\\errors' . $core->controller::POSTFIX)
&& method_exists($class, $method = 'error404')
? (new $class)->$method()
: null;
}
/**
* Универсализировать маршрут
*
* @param string $route Маршрут
*
* @return string Универсализированный маршрут
*/
private function universalize(string $route): string
{
// Если не записан "/" в начале, то записать, затем, если записан "/" в конце, то удалить
return preg_replace('/(.+)\/$/', '$1', preg_replace('/^([^\/])/', '/$1', $route));
}
} }

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace mirzaev\minimal\traits;
// Built-in libraries
use exception;
/**
* Trait of magical methods
*
* @method void __set(string $name, mixed $value = null) 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
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
trait magic
{
/**
* Write property
*
* @param string $name Name of the property
* @param mixed $value Value of the property
*
* @return void
*/
public function __set(string $name, mixed $value = null): void
{
match ($name) {
default => throw new exception('Property "' . static::class . "::\$$name\" not found", 404)
};
}
/**
* Read property
*
* @param string $name Name of the property
*
* @return mixed Value of the property
*/
public function __get(string $name): mixed
{
return match ($name) {
default => throw new exception('Property "' . static::class . "::\$$name\" not found", 404)
};
}
/**
* Delete property
*
* @param string $name Name of the property
*
* @return void
*/
public function __unset(string $name): void
{
match ($name) {
default => (function () use ($name) {
unset($this->{$name});
})()
};
}
/**
* Check property for initialization
*
* @param string $name Name of the property
*
* @return bool Is the property initialized?
*/
public function __isset(string $name): bool
{
return match ($name) {
default => isset($this->{$name})
};
}
}