6 Commits
2.0.0 ... 2.3.2

10 changed files with 3186 additions and 3179 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.

64
README.md Normal file
View File

@@ -0,0 +1,64 @@
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*
```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
**Repository:** https://git.mirzaev.sexy/mirzaev/ebala<br>
**Github mirror:** https://github.com/mature-woman/ebala<br>
*I earned more than a **million rubles** from this project*<br>
*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
**Repository:** https://git.mirzaev.sexy/mirzaev/notchat<br>
**Github mirror:** https://github.com/mature-woman/notchat<br>
*P2P chat project with different blockchains and smart stuff*<br>
### site-repression
**Link:** https://repression.mirzaev.sexy<br>
**Repository:** https://git.mirzaev.sexy/mirzaev/site-repression<br>
**Github mirror:** https://github.com/mature-woman/site-repression<br>
*A simple site for my article about **political repressions in Russia** and my **kidnapping by Wagner PMC operatives** from my house*<br>

8
composer.json Normal file → Executable file
View File

@@ -4,7 +4,8 @@
"description": "Lightweight MVC framework that manages only the basic mechanisms, leaving the development of the programmer and not overloading the project", "description": "Lightweight MVC framework that manages only the basic mechanisms, leaving the development of the programmer and not overloading the project",
"keywords": [ "keywords": [
"mvc", "mvc",
"framework" "framework",
"lightweight"
], ],
"license": "WTFPL", "license": "WTFPL",
"homepage": "https://git.mirzaev.sexy/mirzaev/minimal", "homepage": "https://git.mirzaev.sexy/mirzaev/minimal",
@@ -21,10 +22,7 @@
"issues": "https://git.mirzaev.sexy/mirzaev/minimal/issues" "issues": "https://git.mirzaev.sexy/mirzaev/minimal/issues"
}, },
"require": { "require": {
"php": "~8.1" "php": "~8.2"
},
"suggest": {
"ext-PDO": "To work with SQL-based databases (MySQL, PostreSQL...)"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

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

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

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

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

@@ -10,100 +10,86 @@ use mirzaev\minimal\router,
mirzaev\minimal\model; mirzaev\minimal\model;
// Встроенные библиотеки // Встроенные библиотеки
use exception; use exception,
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 $storage; private object $db;
/** /**
* Маршрутизатор * Инстанция маршрутизатора
*/ */
private router $router; private readonly router $router;
/** /**
* Контроллер * Инстанция ядра контроллера
*/ */
private controller $controller; private readonly controller $controller;
/** /**
* Модель * Инстанция ядра модели
*/ */
private model $model; private readonly model $model;
/** /**
* Пространство имён проекта * Путь пространства имён (системное)
* *
* Используется для поиска файлов по спецификации PSR-4 * Используется для поиска файлов по спецификации PSR-4
*/ */
private string $namespace; private readonly string $namespace;
/** /**
* Конструктор * Конструктор
* *
* @param object $storage Хранилище * @param ?object $db Инстанция соединения с базой данных
* @param router $router Маршрутизатор * @param ?router $router Маршрутизатор
* @param string $uri Маршрут * @param ?controller $controller Инстанция ядра контроллера
* @param ?model $model Инстанция ядра модели
* @param ?string $namespace Пространство имён системного ядра
*
* @return self Инстанция ядра
*/ */
public function __construct(object $storage = null, router $router = null, controller $controller = null, model $model = null, string $namespace = null) public function __construct(
{ ?object $db = null,
if (isset($storage)) { ?router $router = null,
// Переданы данные для хранилища ?controller $controller = null,
?model $model = null,
// Проверка и запись ?string $namespace = null
$this->__set('storage', $storage); ) {
} // Инициализация свойств
if (isset($db)) $this->__set('db', $db);
if (isset($router)) { 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());
$this->__set('router', $router);
}
if (isset($controller)) {
// Переданы данные для контроллера
// Проверка и запись
$this->__set('controller', $controller);
}
if (isset($model)) {
// Переданы данные для модели
// Проверка и запись
$this->__set('model', $model);
}
if (isset($namespace)) {
// Переданы данные для пространства имён
// Проверка и запись
$this->__set('namespace', $namespace);
}
} }
/** /**
* Деструктор * Деструктор
* *
*/ */
public function __destruct() public function __destruct() {}
{
}
public function start(string $uri = null): ?string /**
* Запуск
*
* @param ?string $uri Маршрут
*
* @return string|int|null Ответ
*/
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);
@@ -113,119 +99,56 @@ final class core
* Записать свойство * Записать свойство
* *
* @param string $name Название * @param string $name Название
* @param mixed $value Значение * @param mixed $value Содержимое
*
* @return void
*/ */
public function __set(string $name, mixed $value = null): void public function __set(string $name, mixed $value = null): void
{ {
match ($name) { match ($name) {
'storage', 'db', 'database' => (function () use ($value) { 'db', 'database' => (function () use ($value) {
if ($this->__isset('storage')) { if ($this->__isset('db')) throw new exception('Запрещено реинициализировать инстанцию соединения с базой данных ($this->db)', 500);
// Свойство уже было инициализировано else {
// Выброс исключения (неудача)
throw new exception('Запрещено реинициализировать хранилище ($this->storage)', 500);
} else {
// Свойство ещё не было инициализировано // Свойство ещё не было инициализировано
if (is_object($value)) { if (is_object($value)) $this->db = $value;
// Передано подходящее значение else throw new exception('Свойство $this->db должно хранить инстанцию соединения с базой данных', 500);
// Запись свойства (успех)
$this->storage = $value;
} else {
// Передано неподходящее значение
// Выброс исключения (неудача)
throw new exception('Хранилище ($this->storage) должно хранить объект', 500);
}
} }
})(), })(),
'router' => (function () use ($value) { 'router' => (function () use ($value) {
if ($this->__isset('router')) { if ($this->__isset('router')) throw new exception('Запрещено реинициализировать инстанцию маршрутизатора ($this->router)', 500);
// Свойство уже было инициализировано else {
// Выброс исключения (неудача)
throw new exception('Запрещено реинициализировать маршрутизатор ($this->router)', 500);
} else {
// Свойство ещё не было инициализировано // Свойство ещё не было инициализировано
if ($value instanceof router) { if ($value instanceof router) $this->router = $value;
// Передано подходящее значение else throw new exception('Свойство $this->router должно хранить инстанцию маршрутизатора (mirzaev\minimal\router)"', 500);
// Запись свойства (успех)
$this->router = $value;
} else {
// Передано неподходящее значение
// Выброс исключения (неудача)
throw new exception('Маршрутизатор ($this->router) должен хранить инстанцию "mirzaev\minimal\router"', 500);
}
} }
})(), })(),
'controller' => (function () use ($value) { 'controller' => (function () use ($value) {
if ($this->__isset('controller')) { if ($this->__isset('controller')) throw new exception('Запрещено реинициализировать инстанцию ядра контроллеров ($this->controller)', 500);
// Свойство уже было инициализировано else {
// Свойство не инициализировано
// Выброс исключения (неудача) if ($value instanceof controller) $this->controller = $value;
throw new exception('Запрещено реинициализировать контроллер ($this->controller)', 500); else throw new exception('Свойство $this->controller должно хранить инстанцию ядра контроллеров (mirzaev\minimal\controller)', 500);
} else {
// Свойство ещё не было инициализировано
if ($value instanceof controller) {
// Передано подходящее значение
// Запись свойства (успех)
$this->controller = $value;
} else {
// Передано неподходящее значение
// Выброс исключения (неудача)
throw new exception('Контроллер ($this->controller) должен хранить инстанцию "mirzaev\minimal\controller"', 500);
}
} }
})(), })(),
'model' => (function () use ($value) { 'model' => (function () use ($value) {
if ($this->__isset('model')) { if ($this->__isset('model')) throw new exception('Запрещено реинициализировать инстанцию ядра моделей ($this->model)', 500);
// Свойство уже было инициализировано else {
// Свойство не инициализировано
// Выброс исключения (неудача) if ($value instanceof model) $this->model = $value;
throw new exception('Запрещено реинициализировать модель ($this->model)', 500); else throw new exception('Свойство $this->model должно хранить инстанцию ядра моделей (mirzaev\minimal\model)', 500);
} else {
// Свойство ещё не было инициализировано
if ($value instanceof model) {
// Передано подходящее значение
// Запись свойства (успех)
$this->model = $value;
} else {
// Передано неподходящее значение
// Выброс исключения (неудача)
throw new exception('Модель ($this->model) должен хранить инстанцию "mirzaev\minimal\model"', 500);
}
} }
})(), })(),
'namespace' => (function () use ($value) { 'namespace' => (function () use ($value) {
if ($this->__isset('namespace')) { if ($this->__isset('namespace')) throw new exception('Запрещено реинициализировать путь пространства имён ($this->namespace)', 500);
// Свойство уже было инициализировано else {
// Свойство не инициализировано
// Выброс исключения (неудача) if (is_string($value)) $this->namespace = $value;
throw new exception('Запрещено реинициализировать пространство имён ($this->namespace)', 500); else throw new exception('Свойство $this->namespace должно хранить строку с путём пространства имён', 500);
} else {
// Свойство ещё не было инициализировано
if (is_string($value)) {
// Передано подходящее значение
// Запись свойства (успех)
$this->namespace = $value;
} else {
// Передано неподходящее значение
// Выброс исключения (неудача)
throw new exception('Пространство имён ($this->namespace) должно хранить строку', 500);
}
} }
})(), })(),
default => throw new exception("Свойство \"\$$name\" не найдено", 404) default => throw new exception("Свойство \"\$$name\" не найдено", 404)
@@ -244,42 +167,24 @@ final class core
public function __get(string $name): mixed public function __get(string $name): mixed
{ {
return match ($name) { return match ($name) {
'storage', 'db', 'database' => $this->storage ?? throw new exception("Свойство \"\$$name\" не инициализировано", 500), 'db', 'database' => $this->db ?? throw new exception("Свойство \"\$$name\" не инициализировано", 500),
'router' => (function () { 'router' => (function () {
if ($this->__isset('router')) {
// Свойство уже было инициализировано
} else {
// Свойство ещё не было инициализировано
// Инициализация со значением по умолчанию // Инициализация со значением по умолчанию
$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')) {
// Свойство уже было инициализировано
} else {
// Свойство ещё не было инициализировано
// Инициализация со значением по умолчанию // Инициализация со значением по умолчанию
$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')) {
// Свойство уже было инициализировано
} else {
// Свойство ещё не было инициализировано
// Инициализация со значением по умолчанию // Инициализация со значением по умолчанию
$this->__set('model', new model); if (!$this->__isset('model')) $this->__set('model', new model);
}
// Возврат (успех) // Возврат (успех)
return $this->model; return $this->model;
@@ -293,6 +198,8 @@ final class core
* Проверить свойство на инициализированность * Проверить свойство на инициализированность
* *
* @param string $name Название * @param string $name Название
*
* @return bool Инициализировано свойство?
*/ */
public function __isset(string $name): bool public function __isset(string $name): bool
{ {
@@ -305,6 +212,8 @@ final class core
* Удалить свойство * Удалить свойство
* *
* @param string $name Название * @param string $name Название
*
* @return void
*/ */
public function __unset(string $name): void public function __unset(string $name): void
{ {

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

@@ -4,110 +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 string $postfix = '_model';
/** /**
* Записать свойство * Postfix of file names
*
* @param string $name Название
* @param mixed $value Значение
*/ */
public function __set(string $name, mixed $value = null): void public const string POSTFIX = '_model';
{
match ($name) {
'postfix' => (function () use ($value) {
if (isset($this->postfix)) {
// Свойство уже было инициализировано
// Выброс исключения (неудача)
throw new exception('Запрещено реинициализировать постфикс ($this->postfix)', 500);
} else {
// Свойство ещё не было инициализировано
if ($value = filter_var($value, FILTER_SANITIZE_STRING)) {
// Передано подходящее значение
// Запись свойства (успех)
$this->postfix = $value;
} else {
// Передано неподходящее значение
// Выброс исключения (неудача)
throw new exception('Постфикс ($this->postfix) должен быть строкой', 500);
}
}
})(),
default => throw new exception("Свойство \"\$$name\" не найдено", 404)
};
}
/** /**
* Прочитать свойство * Constructor
*
* Записывает значение по умолчанию, если свойство не инициализировано
*
* @param string $name Название
*
* @return mixed Содержимое
*/ */
public function __get(string $name): mixed public function __construct() {}
{
return match ($name) {
'postfix' => (function() {
if ($this->__isset('postfix')) {
// Свойство уже было инициализировано
} else {
// Свойство ещё не было инициализировано
// Инициализация со значением по умолчанию
$this->__set('postfix', '_model');
}
// Возврат (успех)
return $this->postfix;
})(),
default => throw new exception("Свойство \"\$$name\" не обнаружено", 404)
};
}
/**
* Проверить свойство на инициализированность
*
* @param string $name Название
*/
public function __isset(string $name): bool
{
return match ($name) {
default => isset($this->{$name})
};
}
/**
* Удалить свойство
*
* @param string $name Название
*/
public function __unset(string $name): void
{
match ($name) {
default => (function () use ($name) {
// Удаление
unset($this->{$name});
})()
};
}
} }

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

@@ -7,202 +7,312 @@ namespace mirzaev\minimal;
// Файлы проекта // Файлы проекта
use mirzaev\minimal\core; use mirzaev\minimal\core;
// Встроенные библиотеки
use ReflectionClass;
/** /**
* Маршрутизатор * Маршрутизатор
* *
* @package mirzaev\shop * @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. Доработать обработку ошибок * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
* 2. Добавить __set(), __get(), __isset() и __unset()
*/ */
final class router final class router
{ {
/** /**
* @var array $router Маршруты * @var array $router Реестр маршрутов
*/ */
/* protected array $routes = []; */
public array $routes = []; public array $routes = [];
/** /**
* Записать маршрут * Записать маршрут
* *
* @param string $route Маршрут * @param string $route Маршрут
* @param string $target Обработчик (контроллер и модель, без постфиксов) * @param string $handler Обработчик - инстанции контроллера и модели (не обязательно), без постфиксов
* @param string|null $method Метод * @param ?string $method Вызываемый метод в инстанции контроллера обработчика
* @param string|null $type Тип * @param ?string $request HTTP-метод запроса (GET, POST, PUT...)
* @param string|null $model Модель * @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 $target, string $method = null, string $type = 'GET', string $model = null): void public function write(
{ string $route,
string $handler,
?string $method = 'index',
?string $request = 'GET',
?string $model = null,
array $variables = []
): static {
// Запись в реестр // Запись в реестр
$this->routes[$route][$type] = [ $this->routes[$route][$request] = [
'target' => $target, 'controller' => $handler,
'method' => $method ?? '__construct' 'model' => $model ?? $handler,
'method' => $method,
'variables' => $variables
]; ];
// Exit (success) (fluent interface)
return $this;
} }
/** /**
* Обработать маршрут * Handle a request
* *
* @param string $route Маршрут * @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, core $core = null): ?string public function handle(
{ ?string $uri = null,
// Запись полученного URI или из данных веб-сервера ?string $method = null,
$uri = $uri ?? $_SERVER['REQUEST_URI'] ?? ''; ?core $core = new core
): string|int|null {
// Инициализация URL // Declaration of the registry of routes directoies
$url = parse_url($uri, PHP_URL_PATH); $routes = [];
// Универсализация
$url = self::universalization($url);
// Сортировка массива маршрутов от большего ключа к меньшему (кешируется)
krsort($this->routes);
// Поиск директорий в ссылке
preg_match_all('/[^\/]+/', $url, $directories);
// Инициализация директорий
$directories = $directories[0];
foreach ($this->routes as $route => $data) { foreach ($this->routes as $route => $data) {
// Перебор маршрутов // Iteration over routes
// Универсализация // Search directories of route (explode() creates empty value in array)
$route = self::universalization($route); preg_match_all('/(^\/$|[^\/]+)/', $route, $data['directories']);
$routes[$route] = $data['directories'][0];
}
// Поиск директорий в маршруте if (count($routes) === count($this->routes)) {
preg_match_all('/[^\/]+/', $route, $data['directories']); // Initialized the registry of routes directoies
// Инициализация директорий // Initializing default values
$data['directories'] = $data['directories'][0]; $uri ??= $_SERVER['REQUEST_URI'] ?? '/';
$method ??= $_SERVER["REQUEST_METHOD"];
if (count($directories) === count($data['directories'])) { // Initializing of URN (/foo/bar)
// Совпадает количество директорий у ссылки и маршрута (вероятно эта ссылка на этот маршрут) $urn = parse_url(urldecode($uri), PHP_URL_PATH);
// Инициализация массива переменных // Universalization of URN
$data['vars'] = []; $urn = self::universalize($urn);
foreach ($data['directories'] as $index => &$directory) { // Search directories of URN (explode() creates empty value in array)
// Перебор найденных переменных preg_match_all('/(^\/$|[^\/]+)/', $urn, $directories);
$directories = $directories[0];
if (preg_match('/\$([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]+)/', $directory) === 1) { /**
// Переменная * Initialization of the route
*/
// Запись в массив переменных и перезапись переменной значением из ссылки // Initializing the buffer of matches of route directories with URN directories
$directory = $data['vars'][trim($directory, '$')] = $directories[$index]; $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
$route = self::universalization(implode('/', $data['directories'])); foreach ($matches as $route => $match) if ($match !== false) break;
// Маршрут оказался пустым if ($route && !empty($data = $this->routes[$route])) {
if (empty($route)) $route = '/'; // Route found
if (mb_stripos($route, $url, 0, "UTF-8") === 0 && mb_strlen($route, 'UTF-8') <= mb_strlen($url, 'UTF-8')) { // Universalization of route
// Найден маршрут, а так же его длина не меньше длины запрошенного URL $route = self::universalize($route);
// Инициализация маршрута /**
if (array_key_exists($_SERVER["REQUEST_METHOD"], $data)) { * Initialization of route variables
// Найдены настройки для полученного типа запроса */
// Запись маршрута foreach ($routes[$route] as $i => $route_directory) {
$route = $data[$_SERVER["REQUEST_METHOD"]]; // Iteration over directories of route
} else {
// Не найдены настройки для полученного типа запроса
// Деинициализация if (preg_match('/^\$([a-zA-Z_\x80-\xff]+)$/', $route_directory) === 1) {
unset($route); // 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; break;
} }
} }
// Деинициализация /**
unset($route); * Initialization of route handlers
} */
if (!empty($route)) { if (array_key_exists($method, $data)) {
// Найден маршрут // Идентифицирован метод маршрута (GET, POST, PUT...)
if (class_exists($controller = ($core->namespace ?? (new core)->namespace) . '\\controllers\\' . $route['target'] . $core->controller->postfix ?? (new core())->controller->postfix)) { // Инициализация обработчиков (controller, model, method)
$handlers = $data[$method];
if (class_exists($controller = $core->namespace . '\\controllers\\' . $handlers['controller'] . $core->controller::POSTFIX)) {
// Найден контроллер // Найден контроллер
// Инициализация контроллера // Инициализация инстанции ядра контроллера
$controller = new $controller; $controller = new $controller;
if (class_exists($model = ($core->namespace ?? (new core)->namespace) . '\\models\\' . $route['target'] . $core->model->postfix ?? (new core())->model->postfix)) { // Вызов связанного с маршрутом метода и возврат (успех)
// Найдена модель return $controller->{$handlers['method']}($handlers['variables'] + $_REQUEST, $_FILES, file_get_contents('php://input'));
// Инициализация модели
$controller->model = new $model;
} }
if (empty($response = $controller->{$route['method']}($data['vars'] + $_REQUEST, $_FILES))) {
// Не удалось получить ответ после обработки контроллера
// Возврат (неудача)
return $this->error($core);
} }
// Возврат (успех)
return $response;
} }
} }
// Возврат (неудача) // Возврат (провал)
return $this->error($core); return $this->error($core);
} }
/** /**
* Контроллер ошибок * Sorting routes
* *
* @param core $core Ядро фреймворка * 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 string|null HTML-документ с ошибкой * Добавить чтобы сначала текст потом переменная затем после переменной первыми тексты и в конце перменные опять и так рекурсивно
*
* @return static The instance from which the method was called (fluent interface)
*
* @todo ПЕРЕДЕЛАТЬ ПОЛНОСТЬЮ
*/ */
private function error(core $core = null): ?string public function sort(): static
{ {
if ( uksort($this->routes, function (string $a, string $b) {
class_exists($class = '\\' . ($core->namespace ?? (new ReflectionClass(core::class))->getNamespaceName()) . '\\controllers\\errors' . $core->controller->postfix ?? (new core())->controller->postfix) && // Sorting routes
method_exists($class, $method = 'error404')
) {
// Существует контроллер ошибок и метод для обработки ошибки
// Возврат (вызов метода для обработки ошибки) // Initialization of string lengths (multibyte-safe)
return (new $class(basename($class)))->$method(); $length = [
} else { $a => mb_strlen($a),
// Не существует контроллер ошибок или метод для обработки ошибки $b => mb_strlen($b)
];
// Никаких исключений не вызывать, отдать пустую страницу, // Initialization of the presence of variables
// либо вызвать, но отображать в зависимости от включенного дебаг режима !!!!!!!!!!!!!!!!!!!! см. @todo $variables = [
return null; $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
$collectors = [
$a => preg_match('/\$([a-zA-Z_\x80-\xff]+)\.\.\.$/', $a) === 1,
$b => preg_match('/\$([a-zA-Z_\x80-\xff]+)\.\.\.$/', $b) === 1
];
if ($variables[$a] && !$variables[$b]) return 1;
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)
if ($length[$a] === $length[$b]) return 0;
// The longer route moves to the end
return $length[$a] > $length[$b] ? 1 : -1;
} }
});
// Exit (success) (fluent interface)
return $this;
} }
/** /**
* Универсализация URL * Сгенерировать ответ с ошибкой
* *
* @param string $url Ссылка * Вызывает метод error404 в инстанции контроллера ошибок
* *
* @return string Универсализированная ссылка * @param ?core $core Инстанция системного ядра
*
* @return ?string HTML-документ
*/ */
private function universalization(string $url): string private function error(core $core = new core): ?string
{ {
// Если не записан "/" в начале, то записать return class_exists($class = '\\' . $core->namespace . '\\controllers\\errors' . $core->controller::POSTFIX)
$url = preg_replace('/^([^\/])/', '/$1', $url); && method_exists($class, $method = 'error404')
? (new $class)->$method()
: null;
}
// Если записан "/" в конце, то удалить /**
$url = preg_replace('/(.+)\/$/', '$1', $url); * Universalize URN
*
return $url; * Always "/" at the beginning and never "/" at the end
*
* @param string &$urn URN (/foo/bar)
*
* @return string Universalized URN
*/
private function universalize(string $urn): string
{
// Universalization of URN and exit (success)
return (string) preg_replace('/(.+)\/$/', '$1', preg_replace('/^([^\/])/', '/$1', $urn));
} }
} }

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