17 Commits
0.1.0 ... 2.3.1

Author SHA1 Message Date
9011ccf557 beauty 2024-10-11 10:45:56 +03:00
e1a6483556 collectors, new router, errors handlers, refactoring 2024-10-11 10:14:33 +03:00
95ddffba30 fix postfix public 2024-01-04 04:08:44 +07:00
41bf8ab56a обновил чёто забыл уже 2023-12-21 23:14:34 +07:00
3d31c92628 a little fixes and transit into PHP 8.2 2023-03-20 21:46:41 +10:00
1f5685a20d Косметические изменения кода 2022-11-03 16:09:32 +10:00
22ad7304f9 Перенос с git.hood.su на git.mirzaev.sexy 2022-11-03 08:27:45 +10:00
Arsen Mirzaev Tatyano-Muradovich
7777d7af17 Баг при запросе маршрута с неправильным типом запроса 2022-03-04 07:15:52 +10:00
Arsen Mirzaev Tatyano-Muradovich
483814b6a5 Теперь понимает передачу файлов 2022-03-04 04:35:21 +10:00
Arsen Mirzaev Tatyano-Muradovich
e7a6b9cebe Исправления для маршрута на главную страницу 2022-03-04 02:15:42 +10:00
Arsen Mirzaev Tatyano-Muradovich
a4949ebc52 Мелкое исправление по переменным в контроллер 2022-02-28 04:40:03 +10:00
Arsen Mirzaev Tatyano-Muradovich
aeed2d21cc Доработка передачи переменных в контроллер 2022-02-28 04:30:29 +10:00
Arsen Mirzaev Tatyano-Muradovich
81990de191 Маршрутизатор теперь умеет в переменные 2022-02-28 04:22:21 +10:00
Arsen Mirzaev Tatyano-Muradovich
b6f90b7001 Исправление composer.json 2021-11-12 23:20:13 +10:00
Arsen Mirzaev Tatyano-Muradovich
db36a0a27e Исправления первой версии 2021-10-01 03:41:24 +10:00
Arsen Mirzaev Tatyano-Muradovich
ebfa14d8d6 Первая версия 2021-10-01 03:40:44 +10:00
Arsen Mirzaev Tatyano-Muradovich
2d06e386f5 Поверхностные исправления 2021-09-26 02:35:59 +10:00
14 changed files with 3176 additions and 2865 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**

21
composer.json Normal file → Executable file
View File

@@ -1,13 +1,14 @@
{ {
"name": "mirzaev/minimal", "name": "mirzaev/minimal",
"type": "framework", "type": "framework",
"description": "Легковесный MVC фреймворк который следует твоим правилам, а не диктует свои", "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"
], ],
"homepage": "https://git.hood.su/mirzaev/minimal",
"license": "WTFPL", "license": "WTFPL",
"homepage": "https://git.mirzaev.sexy/mirzaev/minimal",
"authors": [ "authors": [
{ {
"name": "Arsen Mirzaev Tatyano-Muradovich", "name": "Arsen Mirzaev Tatyano-Muradovich",
@@ -17,19 +18,11 @@
} }
], ],
"support": { "support": {
"docs": "https://git.hood.su/mirzaev/minimal/manual", "docs": "https://git.mirzaev.sexy/mirzaev/minimal/wiki",
"issues": "https://git.hood.su/mirzaev/minimal/issues" "issues": "https://git.mirzaev.sexy/mirzaev/minimal/issues"
}, },
"require": { "require": {
"php": "~8.0", "php": "~8.2"
"psr/log": "~3.0",
"twig/twig": "^3.3"
},
"require-dev": {
"phpunit/phpunit": "~9.5"
},
"suggest": {
"ext-PDO": "Для работы с базами данных на SQL (MySQL, PostreSQL...)"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

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

View File

@@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace mirzaev\minimal;
// Files of the project
use mirzaev\minimal\model,
mirzaev\minimal\traits\magic;
// Встроенные библиотеки
use exception;
/**
* Controller (base)
*
* @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>
*/
class controller
{
use magic;
/**
* Postfix of file names
*/
public const string POSTFIX = '_controller';
/**
* Instance of the model connected in the router
*/
protected model $model;
/**
* View template engine instance (twig)
*/
protected object $view;
/**
* Constructor
*/
public function __construct() {}
/**
* 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

@@ -1,148 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\minimal\controllers;
use mirzaev\minimal\core;
use mirzaev\minimal\models\model;
use Twig\Loader\FilesystemLoader;
use Twig\Environment as view;
use Exception;
/**
* Контроллер
*
* @package mirzaev\minimal\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
class controller
{
/**
* @var model $model Модель
*/
protected model $model;
/**
* @var view $view Шаблонизатор представления
*/
protected view $view;
/**
* Конструктор
*
* @return void
*/
public function __construct()
{
// Установка значения по умолчанию для модели (если будет найдена)
$this->__get('model');
// Установка значения по умолчанию для шаблонизатора представлений
$this->__get('view');
}
/**
* Отрисовка шаблона
*
* @param string $route Маршрут
*/
public function view(string $route)
{
// Чтение представления по шаблону пути: "/views/[controller]/index
// Никаких слоёв и шаблонизаторов
// Не стал в ядре записывать путь до шаблонов
if (file_exists($view = core::path() . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . $route . DIRECTORY_SEPARATOR . 'index.html')) {
include $view;
}
}
/**
* Записать свойство
*
* @param mixed $name Название
* @param mixed $value Значение
*
* @return void
*/
public function __set($name, $value): void
{
if ($name === 'model') {
if (!isset($this->model)) {
$this->model = $value;
return;
} else {
throw new Exception('Запрещено переопределять модель');
}
} else if ($name === 'view') {
if (!isset($this->view)) {
$this->view = $value;
return;
} else {
throw new Exception('Запрещено переопределять шаблонизатор представления');
}
}
throw new Exception('Свойство не найдено: ' . $name);
}
/**
* Прочитать свойство
*
* @param mixed $name Название
*
* @return mixed
*/
public function __get($name)
{
if ($name === 'model') {
if (isset($this->model)) {
// Если модель найдена
return $this->model;
} else {
// Инициализация класса модели
$model = preg_replace('/' . core::controllerPostfix() . '$/i', '', basename(get_class($this))) . core::modelPostfix();
// Иначе
if (class_exists($model_class = core::namespace() . '\\models\\' . $model)) {
// Если найдена одноимённая с контроллером модель (без постфикса)
return $this->model = new $model_class;
}
return;
}
} else if ($name === 'view') {
if (isset($this->view)) {
// Если модель найдена
return $this->view;
} else {
$path = core::path() . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'views';
$loader = new FilesystemLoader($path);
return $this->view = (new view($loader, [
// 'cache' => $path . DIRECTORY_SEPARATOR . 'cache',
]));
}
}
throw new Exception('Свойство не найдено: ' . $name);
}
/**
* Проверить свойство на инициализированность
*
* @param string $name Название
*
* @return mixed
*/
public function __isset(string $name)
{
if ($name === 'model') {
return isset($this->model);
} else if ($name === 'view') {
return isset($this->view);
}
throw new Exception('Свойство не найдено: ' . $name);
}
}

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

@@ -4,138 +4,224 @@ declare(strict_types=1);
namespace mirzaev\minimal; namespace mirzaev\minimal;
use mirzaev\minimal\router; // Файлы проекта
use mirzaev\minimal\router,
mirzaev\minimal\controller,
mirzaev\minimal\model;
use PDO; // Встроенные библиотеки
use PDOException; use exception,
ReflectionClass as reflection;
use Exception;
/** /**
* Ядро * 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 core final class core
{ {
/** /**
* @var PDO $db Соединение с базой данных * Инстанция соединения с базой данных
*/ */
private static PDO $db; private object $db;
/** /**
* @var router $router Маршрутизатор * Инстанция маршрутизатора
*/ */
private static router $router; private readonly router $router;
/** /**
* @var string $path Корневая директория * Инстанция ядра контроллера
*/ */
private static string $path; private readonly controller $controller;
/** /**
* @var string $namespace Пространство имён * Инстанция ядра модели
*/ */
private static string $namespace; private readonly model $model;
/** /**
* @var string $postfix_controller Постфикс контроллеров * Путь пространства имён (системное)
*/ *
private static string $postfix_controller = '_controller'; * Используется для поиска файлов по спецификации PSR-4
*/
private readonly string $namespace;
/** /**
* @var string $postfix_model Постфикс моделей * Конструктор
*/ *
private static string $postfix_model = '_model'; * @param ?object $db Инстанция соединения с базой данных
* @param ?router $router Маршрутизатор
* @param ?controller $controller Инстанция ядра контроллера
* @param ?model $model Инстанция ядра модели
* @param ?string $namespace Пространство имён системного ядра
*
* @return self Инстанция ядра
*/
public function __construct(
?object $db = null,
?router $router = null,
?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());
}
/** /**
* Конструктор * Деструктор
* *
* @param string $db */
* @param string $login public function __destruct() {}
* @param string $password
* @param router $router Маршрутизатор
*/
public function __construct(string $db = 'mysql:dbname=db;host=127.0.0.1', string $login = '', string $password = '', router $router = null)
{
// Инициализация маршрутизатора
self::$router = $router ?? new router;
// Инициализация корневого пространства имён /**
self::$namespace = __NAMESPACE__; * Запуск
*
* @param ?string $uri Маршрут
*
* @return string|int|null Ответ
*/
public function start(string $uri = null): string|int|null
{
// Обработка запроса
return $this->__get('router')->handle($uri, core: $this);
}
try { /**
// Инициализация PDO * Записать свойство
self::$db = new PDO($db, $login, $password); *
} catch (PDOException $e) { * @param string $name Название
throw new Exception('Проблемы при соединении с базой данных: ' . $e->getMessage(), $e->getCode()); * @param mixed $value Содержимое
} *
* @return void
*/
public function __set(string $name, mixed $value = null): void
{
match ($name) {
'db', 'database' => (function () use ($value) {
if ($this->__isset('db')) throw new exception('Запрещено реинициализировать инстанцию соединения с базой данных ($this->db)', 500);
else {
// Свойство ещё не было инициализировано
// Обработка запроса if (is_object($value)) $this->db = $value;
self::$router::handle(); else throw new exception('Свойство $this->db должно хранить инстанцию соединения с базой данных', 500);
} }
})(),
'router' => (function () use ($value) {
if ($this->__isset('router')) throw new exception('Запрещено реинициализировать инстанцию маршрутизатора ($this->router)', 500);
else {
// Свойство ещё не было инициализировано
/** if ($value instanceof router) $this->router = $value;
* Деструктор else throw new exception('Свойство $this->router должно хранить инстанцию маршрутизатора (mirzaev\minimal\router)"', 500);
* }
*/ })(),
public function __destruct() 'controller' => (function () use ($value) {
{ if ($this->__isset('controller')) throw new exception('Запрещено реинициализировать инстанцию ядра контроллеров ($this->controller)', 500);
// Закрытие соединения else {
} // Свойство не инициализировано
/** if ($value instanceof controller) $this->controller = $value;
* Прочитать/записать корневую директорию else throw new exception('Свойство $this->controller должно хранить инстанцию ядра контроллеров (mirzaev\minimal\controller)', 500);
* }
* @var string|null $path Путь })(),
* 'model' => (function () use ($value) {
* @return string if ($this->__isset('model')) throw new exception('Запрещено реинициализировать инстанцию ядра моделей ($this->model)', 500);
*/ else {
public static function path(string $path = null): string // Свойство не инициализировано
{
return self::$path = (string) ($path ?? self::$path);
}
/** if ($value instanceof model) $this->model = $value;
* Прочитать/записать соединение с базой данных else throw new exception('Свойство $this->model должно хранить инстанцию ядра моделей (mirzaev\minimal\model)', 500);
* }
* @var PDO|null $db Соединение с базой данных })(),
* 'namespace' => (function () use ($value) {
* @return PDO if ($this->__isset('namespace')) throw new exception('Запрещено реинициализировать путь пространства имён ($this->namespace)', 500);
*/ else {
public static function db(PDO $db = null): PDO // Свойство не инициализировано
{
return self::$db = $db ?? self::$db;
}
/** if (is_string($value)) $this->namespace = $value;
* Прочитать постфикс контроллеров else throw new exception('Свойство $this->namespace должно хранить строку с путём пространства имён', 500);
* }
* @return string|null })(),
*/ default => throw new exception("Свойство \"\$$name\" не найдено", 404)
public static function controllerPostfix(): ?string };
{ }
return self::$postfix_controller;
}
/** /**
* Прочитать постфикс моделей * Прочитать свойство
* *
* @return string|null * Записывает значение по умолчанию, если свойство не инициализировано
*/ *
public static function modelPostfix(): ?string * @param string $name Название
{ *
return self::$postfix_model; * @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);
/** // Возврат (успех)
* Прочитать пространство имён return $this->router;
* })(),
* @return string|null 'controller' => (function () {
*/ // Инициализация со значением по умолчанию
public static function namespace(): ?string if (!$this->__isset('controller')) $this->__set('controller', new controller);
{
return self::$namespace; // Возврат (успех)
} return $this->controller;
})(),
'model' => (function () {
// Инициализация со значением по умолчанию
if (!$this->__isset('model')) $this->__set('model', new model);
// Возврат (успех)
return $this->model;
})(),
'namespace' => $this->namespace ?? throw new exception("Свойство \"\$$name\" не инициализировано", 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});
})()
};
}
} }

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace mirzaev\minimal;
// Files of the project
use mirzaev\minimal\traits\magic;
// Built-in libraries
use exception;
/**
* Model (base)
*
* @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>
*/
class model
{
use magic;
/**
* Postfix of file names
*/
public const string POSTFIX = '_model';
/**
* Constructor
*/
public function __construct() {}
}

View File

@@ -1,90 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\minimal\models;
use mirzaev\minimal\core;
use PDO;
use Exception;
/**
* Модель
*
* @package mirzaev\minimal\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
class model
{
/**
* @var PDO $db Соединение с базой данных
*/
protected PDO $db;
/**
* Конструктор
*
* @param PDO|null $db Соединение с базой данных
*/
public function __construct(PDO $db = null)
{
$this->db = $db ?? core::db();
}
/**
* Записать свойство
*
* @param mixed $name Название
* @param mixed $value Значение
*
* @return void
*/
public function __set($name, $value): void
{
if ($name === 'db') {
if (!isset($this->db)) {
$this->db = $value;
return;
} else {
throw new Exception('Запрещено переопределять соединение с базой данных');
}
}
throw new Exception('Свойство не найдено: ' . $name);
}
/**
* Прочитать свойство
*
* @param mixed $name Название
*
* @return mixed
*/
public function __get($name)
{
if ($name === 'db') {
return $this->db;
}
throw new Exception('Свойство не найдено: ' . $name);
}
/**
* Проверить свойство на инициализированность
*
* @param string $name Название
*
* @return mixed
*/
public function __isset(string $name)
{
if ($name === 'db') {
return isset($this->db);
}
throw new Exception('Свойство не найдено: ' . $name);
}
}

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

@@ -4,119 +4,315 @@ declare(strict_types=1);
namespace mirzaev\minimal; namespace mirzaev\minimal;
use mirzaev\shop\core; // Файлы проекта
use mirzaev\minimal\core;
/** /**
* Маршрутизатор * Маршрутизатор
* *
* @package mirzaev\shop * @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 Реестр маршрутов
*/ */
public static array $routes = []; /* protected array $routes = []; */
public array $routes = [];
/** /**
* Новый маршрут * Записать маршрут
* *
* @param string $route Маршрут * @param string $route Маршрут
* @param string $controller Контроллер * @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 void *
*/ * @return static The instance from which the method was called (fluent interface)
public static function create(string $route, string $controller, string $method = null, string $type = 'GET', string $model = null): void */
{ public function write(
if (is_null($model)) { string $route,
$model = $controller; 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
];
self::$routes[$route][$type] = [ // Exit (success) (fluent interface)
// Инициализация контроллера с постфиксом return $this;
'controller' => preg_match('/' . core::controllerPostfix() . '$/i', $controller) ? $controller : $controller . core::controllerPostfix(), }
'model' => preg_match('/' . core::modelPostfix() . '$/i', $model) ? $model : $model . core::modelPostfix(),
'method' => $method ?? '__construct'
];
}
/** /**
* Обработка маршрута * Handle a request
* *
* @param string $route Маршрут * @param string|null $uri URI (protocol://domain/foo/bar)
* @param string $controller Контроллер * @param string|null $method Method (GET, POST, PUT...)
* * @param core|null $core Instence of the system core
* @return void *
*/ * @return string|int|null Response
public static function handle(string $uri = null): void */
{ public function handle(
// Если не передан URI, то взять из данных веб-сервера ?string $uri = null,
$uri = $uri ?? $_SERVER['REQUEST_URI'] ?? ''; ?string $method = null,
?core $core = new core
): string|int|null {
// Declaration of the registry of routes directoies
$routes = [];
// Инициализация URL foreach ($this->routes as $route => $data) {
$url = parse_url($uri, PHP_URL_PATH); // Iteration over routes
// Сортировка массива маршрутов от большего ключа к меньшему // Search directories of route (explode() creates empty value in array)
krsort(self::$routes); preg_match_all('/(^\/$|[^\/]+)/', $route, $data['directories']);
$routes[$route] = $data['directories'][0];
}
foreach (self::$routes as $key => $value) { if (count($routes) === count($this->routes)) {
// Если не записан "/" в начале, то записать // Initialized the registry of routes directoies
$route_name = preg_replace('/^([^\/])/', '/$1', $key);
$url = preg_replace('/^([^\/])/', '/$1', $url);
// Если не записан "/" в конце, то записать // Initializing default values
$route_name = preg_replace('/([^\/])$/', '$1/', $route_name); $uri ??= $_SERVER['REQUEST_URI'] ?? '/';
$url = preg_replace('/([^\/])$/', '$1/', $url); $method ??= $_SERVER["REQUEST_METHOD"];
if (mb_stripos($route_name, $url, 0, "UTF-8") === 0 && mb_strlen($route_name, 'UTF-8') <= mb_strlen($url, 'UTF-8')) { // Initializing of URN (/foo/bar)
// Если найден маршрут, а так же его длина не меньше длины запрошенного URL $urn = parse_url(urldecode($uri), PHP_URL_PATH);
$route = $value[$_SERVER["REQUEST_METHOD"] ?? 'GET'];
break;
}
}
if (!empty($route)) { // Universalization of URN
// Если маршрут найден $urn = self::universalize($urn);
if (class_exists($controller_class = core::namespace() . '\\controllers\\' . $route['controller'])) {
// Если найден класс-контроллер маршрута
$controller = new $controller_class; // Search directories of URN (explode() creates empty value in array)
preg_match_all('/(^\/$|[^\/]+)/', $urn, $directories);
$directories = $directories[0];
if (empty($response = $controller->{$route['method']}($_REQUEST))) { /**
// Если не получен ответ после обработки контроллера * Initialization of the route
*/
// Удаление постфикса для поиска директории // Initializing the buffer of matches of route directories with URN directories
$dir = preg_replace('/' . core::controllerPostfix() . '$/i', '', $route['controller']); $matches = [];
// Отрисовка шаблона по умолчанию foreach ($directories as $i => $urn_directory) {
$response = $controller->view($dir); // Iteration over directories of URN
}
echo $response; foreach ($this->routes as $route => $data) {
return; // Iteration over routes
}
}
echo self::error(); if (isset($data[$method])) {
} // The request method matches the route method
private static function error(): ?string // Universalization of route
{ $route = self::universalize($route);
if (
class_exists($class = core::namespace() . '\\controllers\\errors' . core::controllerPostfix()) && // Skipping unmatched routes based on results of previous iterations
method_exists($class, $method = 'error404') if (isset($matches[$route]) && $matches[$route] === false) continue;
) {
// Если существует контроллер ошибок и метод-обработчик ответа 404, // Initializing of route directory
// то вызвать обработку ответа 404 $route_directory = $routes[$route][$i] ?? null;
return (new $class(basename($class)))->$method();
} else { if (isset($route_directory)) {
// Никаких исключений не вызывать, отдать пустую страницу // Initialized of route directory
// Либо можно, но отображать в зависимости от включенного дебаг режима
return null; 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)
*
* @todo ПЕРЕДЕЛАТЬ ПОЛНОСТЬЮ
*/
public function sort(): static
{
uksort($this->routes, function (string $a, string $b) {
// Sorting routes
// Initialization of string lengths (multibyte-safe)
$length = [
$a => mb_strlen($a),
$b => mb_strlen($b)
];
// Initialization of the presence of variables
$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
$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;
}
/**
* Сгенерировать ответ с ошибкой
*
* Вызывает метод 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
*
* 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})
};
}
}