diff --git a/mirzaev/minimal/system/controller.php b/mirzaev/minimal/system/controller.php new file mode 100644 index 0000000..15017c7 --- /dev/null +++ b/mirzaev/minimal/system/controller.php @@ -0,0 +1,194 @@ + + */ +class controller +{ + /** + * Постфикс + */ + private string $postfix = '_controller'; + + /** + * Модель + */ + protected model $model; + + /** + * Шаблонизатор представления + */ + protected object $view; + + /** + * Конструктор + * + * @return void + */ + public function __construct() + { + } + + /** + * Отрисовка шаблона + * + * @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 string $name Название + * @param mixed $value Значение + */ + public function __set(string $name, mixed $value = null): void + { + match ($name) { + 'model' => (function () use ($value) { + if (isset($this->model)) { + // Свойство уже было инициализировано + + // Выброс исключения (неудача) + throw new exception('Запрещено реинициализировать модель ($this->model)', 500); + } else { + // Свойство ещё не было инициализировано + + if ($value instanceof model) { + // Передано подходящее значение + + // Запись свойства (успех) + $this->model = $value; + } else { + // Передано неподходящее значение + + // Выброс исключения (неудача) + throw new exception('Модель ($this->model) должна хранить инстанцию "mirzaev\minimal\model"', 500); + } + } + })(), + 'view' => (function () use ($value) { + if (isset($this->view)) { + // Свойство уже было инициализировано + + // Выброс исключения (неудача) + throw new exception('Запрещено реинициализировать шаблонизатор представления ($this->view)', 500); + } else { + // Свойство ещё не было инициализировано + + if (is_object($value)) { + // Передано подходящее значение + + // Запись свойства (успех) + $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}); + })() + }; + } +} diff --git a/mirzaev/minimal/system/controllers/controller.php b/mirzaev/minimal/system/controllers/controller.php deleted file mode 100644 index 6f5e838..0000000 --- a/mirzaev/minimal/system/controllers/controller.php +++ /dev/null @@ -1,148 +0,0 @@ - - */ -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); - } -} diff --git a/mirzaev/minimal/system/core.php b/mirzaev/minimal/system/core.php index 2ae3b6f..c29b165 100644 --- a/mirzaev/minimal/system/core.php +++ b/mirzaev/minimal/system/core.php @@ -5,75 +5,92 @@ declare(strict_types=1); namespace mirzaev\minimal; use mirzaev\minimal\router; +use mirzaev\minimal\controller; +use mirzaev\minimal\model; -use PDO; -use PDOException; - -use Exception; +use exception; /** * Ядро * * @package mirzaev\minimal * @author Arsen Mirzaev Tatyano-Muradovich + * + * @todo + * 1. Добавить __isset() и __unset() */ final class core { /** - * @var PDO $db Соединение с базой данных + * Соединение с базой данных */ - private static PDO $db; + private object $storage; /** - * @var router $router Маршрутизатор + * Маршрутизатор */ - private static router $router; + private router $router; /** - * @var string $path Корневая директория + * Контроллер */ - private static string $path; + private controller $controller; /** - * @var string $namespace Пространство имён + * Модель */ - private static string $namespace; + private model $model; /** - * @var string $postfix_controller Постфикс контроллеров + * Пространство имён проекта + * + * Используется для поиска файлов по спецификации PSR-4 */ - private static string $postfix_controller = '_controller'; - - /** - * @var string $postfix_model Постфикс моделей - */ - private static string $postfix_model = '_model'; + private string $namespace; /** * Конструктор * - * @param string $db - * @param string $login - * @param string $password + * @param object $storage Хранилище * @param router $router Маршрутизатор + * @param string $uri Маршрут */ - public function __construct(string $db = 'mysql:dbname=db;host=127.0.0.1', string $login = '', string $password = '', router $router = null) + public function __construct(object $storage = null, router $router = null, controller $controller = null, model $model = null, string $namespace = null) { - // Инициализация маршрутизатора - self::$router = $router ?? new router; + if (isset($storage)) { + // Переданы данные для хранилища - // Инициализация корневого пространства имён - self::$namespace = __NAMESPACE__; - - try { - // Инициализация PDO - self::$db = new PDO($db, $login, $password); - } catch (PDOException $e) { - throw new Exception('Проблемы при соединении с базой данных: ' . $e->getMessage(), $e->getCode()); + // Проверка и запись + $this->__set('storage', $storage); } - // Обработка запроса - self::$router::handle(); + if (isset($router)) { + // Переданы данные для маршрутизатора + + // Проверка и запись + $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); + } } /** @@ -82,60 +99,218 @@ final class core */ public function __destruct() { - // Закрытие соединения + } + + public function start(string $uri = null): ?string + { + // Обработка запроса + return $this->__get('router')->handle($uri, core: $this); } /** - * Прочитать/записать корневую директорию + * Записать свойство * - * @var string|null $path Путь - * - * @return string + * @param string $name Название + * @param mixed $value Значение */ - public static function path(string $path = null): string + public function __set(string $name, mixed $value = null): void { - return self::$path = (string) ($path ?? self::$path); + match ($name) { + 'storage', 'db', 'database' => (function () use ($value) { + if (isset($this->storage)) { + // Свойство уже было инициализировано + + // Выброс исключения (неудача) + throw new exception('Запрещено реинициализировать хранилище ($this->storage)', 500); + } else { + // Свойство ещё не было инициализировано + + if (is_object($value)) { + // Передано подходящее значение + + // Запись свойства (успех) + $this->storage = $value; + } else { + // Передано неподходящее значение + + // Выброс исключения (неудача) + throw new exception('Хранилище ($this->storage) должно хранить объект', 500); + } + } + })(), + 'router' => (function () use ($value) { + if (isset($this->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); + } + } + })(), + 'controller' => (function () use ($value) { + if (isset($this->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); + } + } + })(), + 'model' => (function () use ($value) { + if (isset($this->model)) { + // Свойство уже было инициализировано + + // Выброс исключения (неудача) + throw new exception('Запрещено реинициализировать модель ($this->model)', 500); + } else { + // Свойство ещё не было инициализировано + + if ($value instanceof model) { + // Передано подходящее значение + + // Запись свойства (успех) + $this->model = $value; + } else { + // Передано неподходящее значение + + // Выброс исключения (неудача) + throw new exception('Модель ($this->model) должен хранить инстанцию "mirzaev\minimal\model"', 500); + } + } + })(), + 'namespace' => (function () use ($value) { + if (isset($this->namespace)) { + // Свойство уже было инициализировано + + // Выброс исключения (неудача) + 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) + }; } /** - * Прочитать/записать соединение с базой данных + * Прочитать свойство * - * @var PDO|null $db Соединение с базой данных + * Записывает значение по умолчанию, если свойство не инициализировано * - * @return PDO + * @param string $name Название + * + * @return mixed Содержимое */ - public static function db(PDO $db = null): PDO + public function __get(string $name): mixed { - return self::$db = $db ?? self::$db; + return match ($name) { + 'storage', 'db', 'database' => $this->storage ?? throw new exception("Свойство \"\$$name\" не инициализировано", 500), + 'router' => (function () { + if (isset($this->router)) { + // Свойство уже было инициализировано + } else { + // Свойство ещё не было инициализировано + + // Инициализация со значением по умолчанию + $this->__set('router', new router); + } + + // Возврат (успех) + return $this->router; + })(), + 'controller' => (function () { + if (isset($this->controller)) { + // Свойство уже было инициализировано + } else { + // Свойство ещё не было инициализировано + + // Инициализация со значением по умолчанию + $this->__set('controller', new controller); + } + + // Возврат (успех) + return $this->controller; + })(), + 'model' => (function () { + if (isset($this->model)) { + // Свойство уже было инициализировано + } else { + // Свойство ещё не было инициализировано + + // Инициализация со значением по умолчанию + $this->__set('model', new model); + } + + // Возврат (успех) + return $this->model; + })(), + 'namespace' => $this->namespace ?? throw new exception("Свойство \"\$$name\" не инициализировано", 500), + default => throw new exception("Свойство \"\$$name\" не найдено", 404) + }; } /** - * Прочитать постфикс контроллеров + * Проверить свойство на инициализированность * - * @return string|null + * @param string $name Название */ - public static function controllerPostfix(): ?string + public function __isset(string $name): bool { - return self::$postfix_controller; + return match ($name) { + default => isset($this->{$name}) + }; } /** - * Прочитать постфикс моделей + * Удалить свойство * - * @return string|null + * @param string $name Название */ - public static function modelPostfix(): ?string + public function __unset(string $name): void { - return self::$postfix_model; - } - - /** - * Прочитать пространство имён - * - * @return string|null - */ - public static function namespace(): ?string - { - return self::$namespace; + match ($name) { + default => (function () use ($name) { + // Удаление + unset($this->{$name}); + })() + }; } } diff --git a/mirzaev/minimal/system/model.php b/mirzaev/minimal/system/model.php new file mode 100644 index 0000000..1a521b0 --- /dev/null +++ b/mirzaev/minimal/system/model.php @@ -0,0 +1,112 @@ + + */ +class model +{ + /** + * Постфикс + */ + private string $postfix = '_model'; + + /** + * Записать свойство + * + * @param string $name Название + * @param mixed $value Значение + */ + public function __set(string $name, mixed $value = null): void + { + 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}); + })() + }; + } +} diff --git a/mirzaev/minimal/system/models/model.php b/mirzaev/minimal/system/models/model.php deleted file mode 100644 index 0f6ec09..0000000 --- a/mirzaev/minimal/system/models/model.php +++ /dev/null @@ -1,84 +0,0 @@ - - */ -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 Значение - */ - public function __set(string $name, mixed $value): void - { - if ($name === 'db') { - if (!isset($this->db)) { - $this->db = $value; - return; - } else { - throw new Exception('Запрещено переопределять соединение с базой данных'); - } - } - - throw new Exception('Свойство не найдено: ' . $name); - } - - /** - * Прочитать свойство - * - * @param string $name Название - */ - public function __get(string $name): mixed - { - if ($name === 'db') { - return $this->db; - } - - throw new Exception('Свойство не найдено: ' . $name); - } - - - /** - * Проверить свойство на инициализированность - * - * @param string $name Название - */ - public function __isset(string $name): bool - { - if ($name === 'db') { - return isset($this->db); - } - - throw new Exception('Свойство не найдено: ' . $name); - } -} diff --git a/mirzaev/minimal/system/public/.gitkeep b/mirzaev/minimal/system/public/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/mirzaev/minimal/system/router.php b/mirzaev/minimal/system/router.php index 4cae941..01a7d08 100644 --- a/mirzaev/minimal/system/router.php +++ b/mirzaev/minimal/system/router.php @@ -4,64 +4,64 @@ declare(strict_types=1); namespace mirzaev\minimal; -use mirzaev\shop\core; +use mirzaev\minimal\core; + +use ReflectionClass; /** * Маршрутизатор * * @package mirzaev\shop * @author Arsen Mirzaev Tatyano-Muradovich + * + * @todo + * 1. Доработать обработку ошибок + * 2. Добавить __set(), __get(), __isset() и __unset() */ final class router { /** * @var array $router Маршруты */ - public static array $routes = []; + public array $routes = []; /** - * Новый маршрут + * Записать маршрут * * @param string $route Маршрут - * @param string $controller Контроллер + * @param string $target Обработчик (контроллер и модель, без постфиксов) * @param string|null $method Метод * @param string|null $type Тип * @param string|null $model Модель - * - * @return void */ - public static function create(string $route, string $controller, string $method = null, string $type = 'GET', string $model = null): void + public function write(string $route, string $target, string $method = null, string $type = 'GET', string $model = null): void { - if (is_null($model)) { - $model = $controller; - } - - self::$routes[$route][$type] = [ - // Инициализация контроллера с постфиксом - 'controller' => preg_match('/' . core::controllerPostfix() . '$/i', $controller) ? $controller : $controller . core::controllerPostfix(), - 'model' => preg_match('/' . core::modelPostfix() . '$/i', $model) ? $model : $model . core::modelPostfix(), + // Запись в реестр + $this->routes[$route][$type] = [ + 'target' => $target, 'method' => $method ?? '__construct' ]; } /** - * Обработка маршрута + * Обработать маршрут * * @param string $route Маршрут - * @param string $controller Контроллер */ - public static function handle(string $uri = null): void + public function handle(string $uri = null, core $core = null): ?string { - // Если не передан URI, то взять из данных веб-сервера + // Запись полученного URI или из данных веб-сервера $uri = $uri ?? $_SERVER['REQUEST_URI'] ?? ''; // Инициализация URL $url = parse_url($uri, PHP_URL_PATH); // Сортировка массива маршрутов от большего ключа к меньшему - krsort(self::$routes); + krsort($this->routes); + + foreach ($this->routes as $key => $value) { + // Перебор маршрутов - foreach (self::$routes as $key => $value) { // Если не записан "/" в начале, то записать $route_name = preg_replace('/^([^\/])/', '/$1', $key); $url = preg_replace('/^([^\/])/', '/$1', $url); @@ -71,52 +71,66 @@ final class router $url = preg_replace('/([^\/])$/', '$1/', $url); if (mb_stripos($route_name, $url, 0, "UTF-8") === 0 && mb_strlen($route_name, 'UTF-8') <= mb_strlen($url, 'UTF-8')) { - // Если найден маршрут, а так же его длина не меньше длины запрошенного URL + // Найден маршрут, а так же его длина не меньше длины запрошенного URL + + // Инициализация маршрута $route = $value[$_SERVER["REQUEST_METHOD"] ?? 'GET']; + + // Выход из цикла (успех) break; } } if (!empty($route)) { - // Если маршрут найден - if (class_exists($controller_class = core::namespace() . '\\controllers\\' . $route['controller'])) { - // Если найден класс-контроллер маршрута + // Найден маршрут - $controller = new $controller_class; + if (class_exists($controller = ($core->namespace ?? (new core)->namespace) . '\\controllers\\' . $route['target'] . $core->controller->postfix ?? (new core())->controller->postfix)) { + // Найден контроллер - if (empty($response = $controller->{$route['method']}($_REQUEST))) { - // Если не получен ответ после обработки контроллера + // Инициализация контроллера + $controller = new $controller; - // Удаление постфикса для поиска директории - $dir = preg_replace('/' . core::controllerPostfix() . '$/i', '', $route['controller']); + if (class_exists($model = ($core->namespace ?? (new core)->namespace) . '\\models\\' . $route['target'] . $core->model->postfix ?? (new core())->model->postfix)) { + // Найдена модель - // Отрисовка шаблона по умолчанию - $response = $controller->view($dir); + // Инициализация модели + $controller->model = new $model; } - echo $response; - return; + if (empty($response = $controller->{$route['method']}($_REQUEST))) { + // Не удалось получить ответ после обработки контроллера + + // Возврат (неудача) + return $this->error($core); + } + + // Возврат (успех) + return $response; } } - echo self::error(); + // Возврат (неудача) + return $this->error($core); } /** * Контроллер ошибок */ - private static function error(): ?string + private function error(core $core = null): ?string { if ( - class_exists($class = core::namespace() . '\\controllers\\errors' . core::controllerPostfix()) && + class_exists($class = (new ReflectionClass(core::class))->getNamespaceName() . '\\controllers\\errors' . $core->controller->postfix ?? (new core())->controller->postfix) && method_exists($class, $method = 'error404') ) { - // Если существует контроллер ошибок и метод-обработчик ответа 404, - // то вызвать обработку ответа 404 + // Существует контроллер ошибок и метод для обработки ошибки + + // Возврат (вызов метода для обработки ошибки) return (new $class(basename($class)))->$method(); } else { - // Никаких исключений не вызывать, отдать пустую страницу - // Либо можно, но отображать в зависимости от включенного дебаг режима + // Не существует контроллер ошибок или метод для обработки ошибки + + // Никаких исключений не вызывать, отдать пустую страницу, + // либо вызвать, но отображать в зависимости от включенного дебаг режима !!!!!!!!!!!!!!!!!!!! см. @todo return null; } } diff --git a/mirzaev/minimal/system/views/.gitkeep b/mirzaev/minimal/system/views/.gitkeep deleted file mode 100644 index e69de29..0000000