16 Commits
1.0.0 ... 3.0.0

Author SHA1 Message Date
6a4ea3f351 executing requests from a controller + boosted by PHP 8.4 + the router rebuild. hell fucking yeah 2024-10-31 23:34:35 +03:00
f95e4082ef mega sex 2024-10-11 10:55:40 +03:00
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
12 changed files with 3487 additions and 3108 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>

76
composer.json Normal file → Executable file
View File

@@ -1,44 +1,32 @@
{ {
"name": "mirzaev/minimal", "name": "mirzaev/minimal",
"type": "framework", "type": "framework",
"description": "Легковесный MVC фреймворк который следует твоим правилам, а не диктует свои", "description": "My vision of a good framework",
"keywords": [ "keywords": [
"mvc", "mvc",
"framework" "framework",
], "lightweight"
"homepage": "https://git.hood.su/mirzaev/minimal", ],
"license": "WTFPL", "license": "WTFPL",
"authors": [ "homepage": "https://git.mirzaev.sexy/mirzaev/minimal",
{ "authors": [
"name": "Arsen Mirzaev Tatyano-Muradovich", {
"email": "arsen@mirzaev.sexy", "name": "Arsen Mirzaev Tatyano-Muradovich",
"homepage": "https://mirzaev.sexy", "email": "arsen@mirzaev.sexy",
"role": "Developer" "homepage": "https://mirzaev.sexy",
} "role": "Programmer"
], }
"support": { ],
"docs": "https://git.hood.su/mirzaev/minimal/manual", "support": {
"issues": "https://git.hood.su/mirzaev/minimal/issues" "docs": "https://git.mirzaev.sexy/mirzaev/minimal/wiki",
}, "issues": "https://git.mirzaev.sexy/mirzaev/minimal/issues"
"require": { },
"php": "~8.0", "require": {
"psr/log": "~3.0", "php": "~8.4"
"twig/twig": "^3.3" },
}, "autoload": {
"require-dev": { "psr-4": {
"phpunit/phpunit": "~9.5" "mirzaev\\minimal\\": "mirzaev/minimal/system"
}, }
"suggest": { }
"ext-PDO": "Для работы с базами данных на SQL (MySQL, PostreSQL...)" }
},
"autoload": {
"psr-4": {
"mirzaev\\minimal\\": "mirzaev/minimal/system"
}
},
"autoload-dev": {
"psr-4": {
"mirzaev\\minimal\\tests\\": "mirzaev/minimal/tests"
}
}
}

4640
composer.lock generated Normal file → Executable file

File diff suppressed because it is too large Load Diff

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

@@ -1,179 +1,86 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\minimal; namespace mirzaev\minimal;
use mirzaev\minimal\model; // Files of the project
use mirzaev\minimal\model,
use Exception; mirzaev\minimal\core,
mirzaev\minimal\traits\magic;
/**
* Контроллер // Build-in libraries
* use exception;
* @package mirzaev\minimal
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> /**
*/ * Controller
class controller *
{ * @var core $core An instance of the core
/** * @var model $model An instance of the model connected in the core
* Постфикс * @var view $view View template engine instance (twig)
*/ * @var core $core An instance of the core
private string $postfix = '_controller'; * @var core $core An instance of the core
*
/** * @method self __construct(core $core) Constructor
* Модель *
*/ * @package mirzaev\minimal
protected model $model; *
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
/** * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
* Шаблонизатор представления */
*/ class controller
protected object $view; {
use magic;
/**
* Конструктор /**
* * Core
* @return void *
*/ * @var core $core An instance of the core
public function __construct() */
{ public core $core {
} // Read
get => $this->core;
/** }
* Записать свойство
* /**
* @param string $name Название * Model
* @param mixed $value Значение *
*/ * @var model $model An instance of the model connected in the core
public function __set(string $name, mixed $value = null): void */
{ public model $model {
match ($name) { // Write
'model' => (function () use ($value) { set (model $model) {
if (isset($this->model)) { $this->model ??= $model;
// Свойство уже было инициализировано }
// Выброс исключения (неудача) // Read
throw new exception('Запрещено реинициализировать модель ($this->model)', 500); get => $this->model;
} else { }
// Свойство ещё не было инициализировано
/**
if ($value instanceof model) { * View
// Передано подходящее значение *
* @var view $view View template engine instance (twig)
// Запись свойства (успех) */
$this->model = $value; public object $view {
} else { // Write
// Передано неподходящее значение set (object $view) {
$this->view ??= $view;
// Выброс исключения (неудача) }
throw new exception('Модель ($this->model) должна хранить инстанцию "mirzaev\minimal\model"', 500);
} // Read
} get => $this->view;
})(), }
'view' => (function () use ($value) {
if (isset($this->view)) { /**
// Свойство уже было инициализировано * Constructor
*
// Выброс исключения (неудача) * @param core $core The instance of the core
throw new exception('Запрещено реинициализировать шаблонизатор представления ($this->view)', 500); *
} else { * @return self
// Свойство ещё не было инициализировано */
public function __construct(core $core) {
if (is_object($value)) { // Writing the core into the property
// Передано подходящее значение $this->core = $core;
}
// Запись свойства (успех) }
$this->view = $value;
} else {
// Передано неподходящее значение
// Выброс исключения (неудача)
throw new exception('Шаблонизатор представлений ($this->view) должен хранить объект', 500);
}
}
})(),
'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)
};
}
/**
* Прочитать свойство
*
* Записывает значение по умолчанию, если свойство не инициализировано
*
* @param string $name Название
*
* @return mixed Содержимое
*/
public function __get(string $name): mixed
{
return match ($name) {
'postfix' => (function () {
if (isset($this->postfix)) {
// Свойство уже было инициализировано
} 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});
})()
};
}
}

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

@@ -1,316 +1,364 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\minimal; namespace mirzaev\minimal;
use mirzaev\minimal\router; // Files of the project
use mirzaev\minimal\controller; use mirzaev\minimal\router,
use mirzaev\minimal\model; mirzaev\minimal\route,
mirzaev\minimal\controller,
use exception; mirzaev\minimal\model;
/** // Built-in libraries
* Ядро use exception,
* BadMethodCallException as exception_method,
* @package mirzaev\minimal DomainException as exception_domain,
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> InvalidArgumentException as exception_argument,
* UnexpectedValueException as exception_value,
* @todo ReflectionClass as reflection;
* 1. Добавить __isset() и __unset()
*/ /**
final class core * Core
{ *
/** * @param string $namespace Namespace where the core was initialized from
* Соединение с базой данных * @param controller $controller An instance of the controller
*/ * @param model $model An instance of the model
private object $storage; * @param router $router An instance of the router
*
/** * @mathod self __construct(?string $namespace) Constructor
* Маршрутизатор * @method void __destruct() Destructor
*/ * @method string|null request(?string $uri, ?string $method, array $variabls) Handle the request
private router $router; * @method string|null route(route $route, string $method) Handle the route
*
/** * @package mirzaev\minimal
* Контроллер *
*/ * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
private controller $controller; * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
/** final class core
* Модель {
*/ /**
private model $model; * Namespace
*
/** * @var string $namespace Namespace where the core was initialized from
* Пространство имён проекта *
* * @see https://www.php-fig.org/psr/psr-4/
* Используется для поиска файлов по спецификации PSR-4 */
*/ public string $namespace {
private string $namespace; // Read
get => $this->namespace;
/** }
* Конструктор
* /**
* @param object $storage Хранилище * Controller
* @param router $router Маршрутизатор *
* @param string $uri Маршрут * @var controller $controller An instance of the controller
*/ */
public function __construct(object $storage = null, router $router = null, controller $controller = null, model $model = null, string $namespace = null) private controller $controller {
{ // Read
if (isset($storage)) { get => $this->controller ??= new controller;
// Переданы данные для хранилища }
// Проверка и запись /**
$this->__set('storage', $storage); * Model
} *
* @var model $model An instance of the model
if (isset($router)) { */
// Переданы данные для маршрутизатора private model $model {
// Read
// Проверка и запись get => $this->model ??= new model;
$this->__set('router', $router); }
}
/**
if (isset($controller)) { * Router
// Переданы данные для контроллера *
* @var router $router An instance of the router
// Проверка и запись */
$this->__set('controller', $controller); public router $router {
} get => $this->router ??= router::initialize();
}
if (isset($model)) {
// Переданы данные для модели /**
* Constrictor
// Проверка и запись *
$this->__set('model', $model); * @param ?string $namespace Пространство имён системного ядра
} *
* @return self The instance of the core
if (isset($namespace)) { */
// Переданы данные для пространства имён public function __construct(
?string $namespace = null
// Проверка и запись ) {
$this->__set('namespace', $namespace); // Writing a namespace to the property
} $this->namespace = $namespace ?? (new reflection(self::class))->getNamespaceName();
} }
/**
* Деструктор /**
* * Destructor
*/ */
public function __destruct() public function __destruct() {}
{
} /**
* Request
public function start(string $uri = null): ?string *
{ * Handle the request
// Обработка запроса *
return $this->__get('router')->handle($uri, core: $this); * @param string|null $uri URI of the request (value by default: $_SERVER['REQUEST_URI'])
} * @param string|null $method Method of the request (GET, POST, PUT...) (value by default: $_SERVER["REQUEST_METHOD"])
* @paam array $parameters parameters for merging with route parameters
/** *
* Записать свойство * @return string|null Response
* */
* @param string $name Название public function request(?string $uri = null, ?string $method = null, array $parameters = []): ?string
* @param mixed $value Значение {
*/ // Matching a route
public function __set(string $name, mixed $value = null): void $route = $this->router->match($uri ??= $_SERVER['REQUEST_URI'] ?? '/', $method ??= $_SERVER["REQUEST_METHOD"]);
{
match ($name) { if ($route) {
'storage', 'db', 'database' => (function () use ($value) { // Initialized the route
if (isset($this->storage)) {
// Свойство уже было инициализировано if (!empty($parameters)) {
// Recaived parameters
// Выброс исключения (неудача)
throw new exception('Запрещено реинициализировать хранилище ($this->storage)', 500); // Merging parameters with route parameters
} else { $route->parameters = $parameters + $route->parameters;
// Свойство ещё не было инициализировано }
if (is_object($value)) { // Handling the route and exit (success)
// Передано подходящее значение return $this->route($route, $method);
}
// Запись свойства (успех)
$this->storage = $value; // Exit (fail)
} else { return null;
// Передано неподходящее значение }
// Выброс исключения (неудача) /**
throw new exception('Хранилище ($this->storage) должно хранить объект', 500); * Route
} *
} * Handle the route
})(), *
'router' => (function () use ($value) { * @param route $route The route
if (isset($this->router)) { * @param string $method Method of requests (GET, POST, PUT, DELETE, COOKIE...)
// Свойство уже было инициализировано *
* @return string|null Response, if generated
// Выброс исключения (неудача) */
throw new exception('Запрещено реинициализировать маршрутизатор ($this->router)', 500); public function route(route $route, string $method = 'GET'): ?string
} else { {
// Свойство ещё не было инициализировано // Initializing name of the controller class
$controller = $route->controller;
if ($value instanceof router) {
// Передано подходящее значение if ($route->controller instanceof controller) {
// Initialized the controller
// Запись свойства (успех) } else if (class_exists($controller = "$this->namespace\\controllers\\$controller")) {
$this->router = $value; // Found the controller by its name
} else {
// Передано неподходящее значение // Initializing the controller
$route->controller = new $controller(core: $this);
// Выброс исключения (неудача) } else if (!empty($route->controller)) {
throw new exception('Маршрутизатор ($this->router) должен хранить инстанцию "mirzaev\minimal\router"', 500); // Not found the controller and $route->controller has a value
}
} // Exit (fail)
})(), throw new exception_domain('Failed to found the controller: ' . $route->controller);
'controller' => (function () use ($value) { } else {
if (isset($this->controller)) { // Not found the controller and $route->controller is empty
// Свойство уже было инициализировано
// Exit (fail)
// Выброс исключения (неудача) throw new exception_argument('Failed to initialize the controller: ' . $route->controller);
throw new exception('Запрещено реинициализировать контроллер ($this->controller)', 500); }
} else {
// Свойство ещё не было инициализировано // Deinitializing name of the controller class
unset($controller);
if ($value instanceof controller) {
// Передано подходящее значение // Initializing name if the model class
$model = $route->model;
// Запись свойства (успех)
$this->controller = $value; if ($route->model instanceof model) {
} else { // Initialized the model
// Передано неподходящее значение } else if (class_exists($model = "$this->namespace\\models\\$model")) {
// Found the model by its name
// Выброс исключения (неудача)
throw new exception('Контроллер ($this->controller) должен хранить инстанцию "mirzaev\minimal\controller"', 500); // Initializing the model
} $route->model = new $model;
} } else if (!empty($route->model)) {
})(), // Not found the model and $route->model has a value
'model' => (function () use ($value) {
if (isset($this->model)) { // Exit (fail)
// Свойство уже было инициализировано throw new exception_domain('Failed to initialize the model: ' . ($route->model ?? $route->controller));
}
// Выброс исключения (неудача)
throw new exception('Запрещено реинициализировать модель ($this->model)', 500); // Deinitializing name of the model class
} else { unset($model);
// Свойство ещё не было инициализировано
if ($route->model instanceof model) {
if ($value instanceof model) { // Initialized the model
// Передано подходящее значение
// Writing the model to the controller
// Запись свойства (успех) $route->controller->model = $route->model;
$this->model = $value; }
} else {
// Передано неподходящее значение if ($method === 'POST') {
// POST
// Выброс исключения (неудача)
throw new exception('Модель ($this->model) должен хранить инстанцию "mirzaev\minimal\model"', 500); if (method_exists($route->controller, $route->method)) {
} // Found the method of the controller
}
})(), // Executing method of the controller that handles the route and exit (success)
'namespace' => (function () use ($value) { return $route->controller->{$route->method}($route->parameters + $_POST, $_FILES);
if (isset($this->namespace)) { } else {
// Свойство уже было инициализировано // Not found the method of the controller
// Выброс исключения (неудача) // Exit (fail)
throw new exception('Запрещено реинициализировать пространство имён ($this->namespace)', 500); throw new exception('Failed to find the method of the controller: ' . $route->method);
} else { }
// Свойство ещё не было инициализировано } else if ($method === 'GET') {
// GET
if (is_string($value)) {
// Передано подходящее значение if (method_exists($route->controller, $route->method)) {
// Found the method of the controller
// Запись свойства (успех)
$this->namespace = $value; if ($_SERVER["CONTENT_TYPE"] === 'multipart/form-data' || $_SERVER["CONTENT_TYPE"] === 'application/x-www-form-urlencoded') {
} else { // The requeset content type is the "multipart/form-data" or "application/x-www-form-urlencoded"
// Передано неподходящее значение
// Analysis of the request
// Выброс исключения (неудача) [$_GET, $_FILES] = request_parse_body($route->options);
throw new exception('Пространство имён ($this->namespace) должно хранить строку', 500); }
}
} // Executing method of the controller that handles the route and exit (success)
})(), return $route->controller->{$route->method}($route->parameters + $_GET, $_FILES);
default => throw new exception("Свойство \"\$$name\" не найдено", 404) } else {
}; // Not found the method of the controller
}
// Exit (fail)
/** throw new exception('Failed to find the method of the controller: ' . $route->method);
* Прочитать свойство }
* } else if ($method === 'PUT') {
* Записывает значение по умолчанию, если свойство не инициализировано // PUT
*
* @param string $name Название if (method_exists($route->controller, $route->method)) {
* // Found the method of the controller
* @return mixed Содержимое
*/ if ($_SERVER["CONTENT_TYPE"] === 'multipart/form-data' || $_SERVER["CONTENT_TYPE"] === 'application/x-www-form-urlencoded') {
public function __get(string $name): mixed // The requeset content type is the "multipart/form-data" or "application/x-www-form-urlencoded"
{
return match ($name) { // Analysis of the request
'storage', 'db', 'database' => $this->storage ?? throw new exception("Свойство \"\$$name\" не инициализировано", 500), [$_PUT, $_FILES] = request_parse_body($route->options);
'router' => (function () { }
if (isset($this->router)) {
// Свойство уже было инициализировано // Executing method of the controller that handles the route and exit (success)
} else { return $route->controller->{$route->method}($route->parameters + $_PUT, $_FILES);
// Свойство ещё не было инициализировано } else {
// Not found the method of the controller
// Инициализация со значением по умолчанию
$this->__set('router', new router); // Exit (fail)
} throw new exception('Failed to find the method of the controller: ' . $route->method);
}
// Возврат (успех) } else if ($method === 'DELETE') {
return $this->router; // DELETE
})(),
'controller' => (function () { if (method_exists($route->controller, $route->method)) {
if (isset($this->controller)) { // Found the method of the controller
// Свойство уже было инициализировано
} else { if ($_SERVER["CONTENT_TYPE"] === 'multipart/form-data' || $_SERVER["CONTENT_TYPE"] === 'application/x-www-form-urlencoded') {
// Свойство ещё не было инициализировано // The requeset content type is the "multipart/form-data" or "application/x-www-form-urlencoded"
// Инициализация со значением по умолчанию // Analysis of the request
$this->__set('controller', new controller); [$_DELETE, $_FILES] = request_parse_body($route->options);
} }
// Возврат (успех) // Executing method of the controller that handles the route and exit (success)
return $this->controller; return $route->controller->{$route->method}($route->parameters + $_DELETE, $_FILES);
})(), } else {
'model' => (function () { // Not found the method of the controller
if (isset($this->model)) {
// Свойство уже было инициализировано // Exit (fail)
} else { throw new exception('Failed to find the method of the controller: ' . $route->method);
// Свойство ещё не было инициализировано }
} else if ($method === 'PATCH') {
// Инициализация со значением по умолчанию // PATCH
$this->__set('model', new model);
} if (method_exists($route->controller, $route->method)) {
// Found the method of the controller
// Возврат (успех)
return $this->model; if ($_SERVER["CONTENT_TYPE"] === 'multipart/form-data' || $_SERVER["CONTENT_TYPE"] === 'application/x-www-form-urlencoded') {
})(), // The requeset content type is the "multipart/form-data" or "application/x-www-form-urlencoded"
'namespace' => $this->namespace ?? throw new exception("Свойство \"\$$name\" не инициализировано", 500),
default => throw new exception("Свойство \"\$$name\" не найдено", 404) // Analysis of the request
}; [$_PATCH, $_FILES] = request_parse_body($route->options);
} }
/** // Executing method of the controller that handles the route and exit (success)
* Проверить свойство на инициализированность return $route->controller->{$route->method}($route->parameters + $_PATCH, $_FILES);
* } else {
* @param string $name Название // Not found the method of the controller
*/
public function __isset(string $name): bool // Exit (fail)
{ throw new exception('Failed to find the method of the controller: ' . $route->method);
return match ($name) { }
default => isset($this->{$name}) } else if ($method === 'HEAD') {
}; // HEAD
}
if (method_exists($route->controller, $route->method)) {
/** // Found the method of the controller
* Удалить свойство
* // Executing method of the controller that handles the route and exit (success)
* @param string $name Название return $route->controller->{$route->method}($route->parameters);
*/ } else {
public function __unset(string $name): void // Not found the method of the controller
{
match ($name) { // Exit (fail)
default => (function () use ($name) { throw new exception('Failed to find the method of the controller: ' . $route->method);
// Удаление }
unset($this->{$name}); } else if ($method === 'OPTIONS') {
})() // OPTIONS
};
} if (method_exists($route->controller, $route->method)) {
} // Found the method of the controller
// Executing method of the controller that handles the route and exit (success)
return $route->controller->{$route->method}($route->parameters);
} else {
// Not found the method of the controller
// Exit (fail)
throw new exception('Failed to find the method of the controller: ' . $route->method);
}
} else if ($method === 'CONNECT') {
// CONNECT
if (method_exists($route->controller, $route->method)) {
// Found the method of the controller
// Executing method of the controller that handles the route and exit (success)
return $route->controller->{$route->method}($route->parameters);
} else {
// Not found the method of the controller
// Exit (fail)
throw new exception('Failed to find the method of the controller: ' . $route->method);
}
} else if ($method === 'TRACE') {
// TRACE
if (method_exists($route->controller, $route->method)) {
// Found the method of the controller
// Executing method of the controller that handles the route and exit (success)
return $route->controller->{$route->method}($route->parameters);
} else {
// Not found the method of the controller
// Exit (fail)
throw new exception('Failed to find the method of the controller: ' . $route->method);
}
} else {
// Not recognized method of the request
// Exit (fail)
throw new exception_value('Failed to recognize the method: ' . $method);
}
// Exit (fail)
return null;
}
}

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

@@ -1,112 +1,30 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\minimal; namespace mirzaev\minimal;
use Exception; // Files of the project
use mirzaev\minimal\traits\magic;
/**
* Модель /**
* * Model
* @package mirzaev\minimal *
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @method self __construct() Constructor
*/ *
class model * @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>
*/ */
private string $postfix = '_model'; class model
{
/** use magic;
* Записать свойство
* /**
* @param string $name Название * Constructor
* @param mixed $value Значение *
*/ * @return self
public function __set(string $name, mixed $value = null): void */
{ public function __construct() {}
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)
};
}
/**
* Прочитать свойство
*
* Записывает значение по умолчанию, если свойство не инициализировано
*
* @param string $name Название
*
* @return mixed Содержимое
*/
public function __get(string $name): mixed
{
return match ($name) {
'postfix' => (function() {
if (isset($this->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});
})()
};
}
}

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

@@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
namespace mirzaev\minimal;
/**
* Route
*
* @param string|controller $controller Name of the controller
* @param string $method Name of the method of the method of $this->controller
* @param string|model $model Name of the model
* @param array $parameters Arguments for the $this->method (will be concatenated together with generated request parameters)
* @param array $options Options for `request_parse_body($options)`
*
* @method self __construct(string $controller,?string $method, ?string $model, array $variables, array $options) Constructor
*
* @package mirzaev\minimal
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class route
{
/**
* Controller
*
* @var string|model $controller Name of the controller or an instance of the controller
*/
public string|controller $controller {
// Read
get => $this->controller;
}
/**
* Method
*
* @var string $method Name of the method of the method of $this->controller
*/
public string $method{
// Read
get => $this->method;
}
/**
* Model
*
* @var string|model|null $model Name of the model of an instance of the model
*/
public string|model|null $model {
// Read
get => $this->model;
}
/**
* Parameters
*
* @var array $parameters Arguments for the $this->method (will be concatenated together with generated request parameters)
*/
public array $parameters {
// Read
get => $this->parameters;
}
/**
* Options
*
* Used only for GET, PUT, PATCH and DELETE
*
* @var string $options Options for `request_parse_body($options)`
*/
public array $options {
// Write
set (array $value) => array_filter(
$value,
fn(string $key) => match ($key) {
'post_max_size',
'max_input_vars',
'max_multipart_body_parts',
'max_file_uploads',
'upload_max_filesize' => true,
default => false
},
ARRAY_FILTER_USE_KEY
);
// Read
get => $this->options;
}
/**
* Constructor
*
* @param string|controller $controller Name of the controller
* @param string|null $method Name of the method of the method of $controller
* @param string|model|null $model Name of the model
* @param array $parameters Arguments for the $method (will be concatenated together with generated request parameters)
* @param array $options Options for `request_parse_body` (Only for POST method)
*
* @return self
*/
public function __construct(
string|controller $controller,
?string $method = 'index',
string|model|null $model = null,
array $parameters = [],
array $options = []
) {
// Writing name of the controller
$this->controller = $controller;
// Writing name of the model
$this->model = $model;
// Writing name of the method of the controller
$this->method = $method;
// Writing parameters
$this->parameters = $parameters;
// Writing options
if (match ($method) {
'GET', 'PUT', 'PATCH', 'DELETE' => true,
default => false
}) $this->options = $options;
}
}

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

@@ -1,137 +1,285 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
namespace mirzaev\minimal; namespace mirzaev\minimal;
use mirzaev\minimal\core; // Files of the project
use mirzaev\minimal\route,
use ReflectionClass; mirzaev\minimal\traits\singleton;
/** // Build-ing libraries
* Маршрутизатор use InvalidArgumentException as exception_argument;
*
* @package mirzaev\shop /**
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * Router
* *
* @todo * @param array $routes The registry of routes
* 1. Доработать обработку ошибок *
* 2. Добавить __set(), __get(), __isset() и __unset() * @method self write(string $uri, string $method)
*/ * @method self sort() Sort routes (DEV)
final class router * @method string universalize(string $urn) Universalize URN
{ *
/** * @package mirzaev\minimal
* @var array $router Маршруты *
*/ * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
public array $routes = []; * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
/** final class router
* Записать маршрут {
* use singleton;
* @param string $route Маршрут
* @param string $target Обработчик (контроллер и модель, без постфиксов) /**
* @param string|null $method Метод * Routes
* @param string|null $type Тип *
* @param string|null $model Модель * @var array $routes The registry of routes
*/ */
public function write(string $route, string $target, string $method = null, string $type = 'GET', string $model = null): void protected array $routes = [];
{
// Запись в реестр /**
$this->routes[$route][$type] = [ * Write a route
'target' => $target, *
'method' => $method ?? '__construct' * @param string $urn URN of the route ('/', '/page', '/page/$variable', '/page/$collector...'...)
]; * @param route $route The route
} * @param string|array $method Method of requests (GET, POST, PUT, DELETE, COOKIE...)
*
/** * @return self The instance from which the method was called (fluent interface)
* Обработать маршрут */
* public function write(
* @param string $route Маршрут string $urn,
*/ route $route,
public function handle(string $uri = null, core $core = null): ?string null|string|array $method = 'GET'
{ ): self {
// Запись полученного URI или из данных веб-сервера foreach (is_array($method) ? $method : [$method] as $method) {
$uri = $uri ?? $_SERVER['REQUEST_URI'] ?? ''; // Iterate over methods of requests
// Инициализация URL // Validating method
$url = parse_url($uri, PHP_URL_PATH); $method = match ($method) {
'POST', 'GET', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'CONNECT', 'TRACE' => $method,
// Сортировка массива маршрутов от большего ключа к меньшему default => throw new exception_argument("Failed to initialize method: \"$method\"")
krsort($this->routes); };
foreach ($this->routes as $key => $value) { // Writing to the registry of routes
// Перебор маршрутов $this->routes[$urn][$method] = $route;
}
// Если не записан "/" в начале, то записать
$route_name = preg_replace('/^([^\/])/', '/$1', $key); // Exit (success) (fluent interface)
$url = preg_replace('/^([^\/])/', '/$1', $url); return $this;
}
// Если не записан "/" в конце, то записать
$route_name = preg_replace('/([^\/])$/', '$1/', $route_name); /**
$url = preg_replace('/([^\/])$/', '$1/', $url); * Match
*
if (mb_stripos($route_name, $url, 0, "UTF-8") === 0 && mb_strlen($route_name, 'UTF-8') <= mb_strlen($url, 'UTF-8')) { * Match request URI with registry of routes
// Найден маршрут, а так же его длина не меньше длины запрошенного URL *
* @param string $uri URI (protocol://domain/foo/bar)
// Инициализация маршрута * @param string $method Method of the request (GET, POST, PUT...)
$route = $value[$_SERVER["REQUEST_METHOD"] ?? 'GET']; *
* @return route|null Route, if found
// Выход из цикла (успех) */
break; public function match(string $uri, string $method): ?route {
} // Declaration of the registry of routes directoies
} $routes = [];
if (!empty($route)) { foreach ($this->routes as $route => $data) {
// Найден маршрут // Iteration over routes
if (class_exists($controller = ($core->namespace ?? (new core)->namespace) . '\\controllers\\' . $route['target'] . $core->controller->postfix ?? (new core())->controller->postfix)) { // Search directories of route (explode() creates empty value in array)
// Найден контроллер preg_match_all('/(^\/$|[^\/]+)/', $route, $data['directories']);
$routes[$route] = $data['directories'][0];
// Инициализация контроллера }
$controller = new $controller;
if (count($routes) === count($this->routes)) {
if (class_exists($model = ($core->namespace ?? (new core)->namespace) . '\\models\\' . $route['target'] . $core->model->postfix ?? (new core())->model->postfix)) { // Initialized the registry of routes directoies
// Найдена модель
// Universalization of URN (/foo/bar)
// Инициализация модели $urn = self::universalize(parse_url(urldecode($uri), PHP_URL_PATH));
$controller->model = new $model;
} // Search directories of URN (explode() creates empty value in array)
preg_match_all('/(^\/$|[^\/]+)/', $urn, $directories);
if (empty($response = $controller->{$route['method']}($_REQUEST))) { $directories = $directories[0];
// Не удалось получить ответ после обработки контроллера
/**
// Возврат (неудача) * Initialization of the route
return $this->error($core); */
}
// Initializing the buffer of matches of route directories with URN directories
// Возврат (успех) $matches = [];
return $response;
} foreach ($directories as $i => $urn_directory) {
} // Iteration over directories of URN
// Возврат (неудача) foreach ($this->routes as $route => $data) {
return $this->error($core); // Iteration over routes
}
if (isset($data[$method])) {
/** // The request method matches the route method
* Контроллер ошибок
*/ // Universalization of route
private function error(core $core = null): ?string $route = self::universalize($route);
{
if ( // Skipping unmatched routes based on results of previous iterations
class_exists($class = (new ReflectionClass(core::class))->getNamespaceName() . '\\controllers\\errors' . $core->controller->postfix ?? (new core())->controller->postfix) && if (isset($matches[$route]) && $matches[$route] === false) continue;
method_exists($class, $method = 'error404')
) { // Initializing of route directory
// Существует контроллер ошибок и метод для обработки ошибки $route_directory = $routes[$route][$i] ?? null;
// Возврат (вызов метода для обработки ошибки) if (isset($route_directory)) {
return (new $class(basename($class)))->$method(); // Initialized of route directory
} else {
// Не существует контроллер ошибок или метод для обработки ошибки if ($urn_directory === $route_directory) {
// The directory of URN is identical to the directory of route
// Никаких исключений не вызывать, отдать пустую страницу,
// либо вызвать, но отображать в зависимости от включенного дебаг режима !!!!!!!!!!!!!!!!!!!! см. @todo // Writing: end of URN directories XNOR end of route directories
return null; $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;
}
}
// Exit (success or fail)
return $data[$method] ?? null;
}
}
// Exit (fail)
return null;
}
/**
* 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 self The instance from which the method was called (fluent interface)
*
* @todo ПЕРЕДЕЛАТЬ ПОЛНОСТЬЮ
*/
public function sort(): self
{
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;
}
/**
* 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) '/' . mb_trim($urn, '/');
}
}

View File

@@ -0,0 +1,83 @@
<?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) 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
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @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})
};
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace mirzaev\minimal\traits;
// Built-in libraries
use exception;
/**
* Trait of singleton
*
* @method static initialize() Initialize an instance
*
* @package mirzaev\minimal\traits
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
trait singleton
{
/**
* Constructor (blocked)
*
* @return void
*/
final private function __construct()
{
// Initializing the indicator that an instance of static::class has already been initialized
static $instance = false;
if ($instance) {
// An instance of static has already been initialized
// Exit (fail)
throw new exception('Has already been initialized an instance of the ' . static::class);
}
// Writing the indicator that the instance of static been initialized
$instance = true;
}
/**
* Initialize an instance
*
* Create an instance of static::class, or return an already created static::class instance
*
* @return static
*/
public static function initialize(): static
{
// Initialize the buffer of the instance of static::class
static $instance;
// Exit (success)
return $instance ??= new static;
}
/**
* Clone (blocked)
*/
private function __clone()
{
// Exit (fail)
throw new exception('Cloning is inadmissible');
}
/**
* Sleep (blocked)
*/
public function __sleep()
{
// Exit (fail)
throw new exception('Serialization is inadmissible');
}
/**
* Wake up (blocked)
*/
public function __wakeup()
{
// Exit (fail)
throw new exception('Deserialisation is inadmissible');
}
}