Translate

PHP Микрофреймворк HLEB

Скачать Скачать с GitHub
Предназначение Установить Настройка Структура проекта Маршрутизация Типы маршрутов Группы маршрутов Защита маршрутов Конструктор страниц Контроллеры Модели Получение данных Базы данных Регистрация API DI Дополнительно

Дополнительно

Установка и обновление: использование Composer

Настройка прав доступа к фреймворку в Linux

Установка и настройка: вариативное включение debug-режима

Настройка: Изменить папку public проекта на другую

Настройка: Изменить название директории vendor

Настройка: Внедрение собственного кода при загрузке фреймворка.

Маршрутизация: установка формата для адреса

Маршрутизация: использование переменных в файлах роутов

Маршрутизация: работа с поддоменами

Дополнительные константы для предварительной настройки

Модули: возможность модульной разработки

Памятка: защита проекта на фреймворке HLEB

Использование Content Security Policy

Использование отладочной панели Debug Panel

Маршрутизация: регулярное выражение для динамических значений

Объект Request: данные динамического маршрута

Объект Request: обработка внешних данных

Шаблоны: использование includeTemplate

Шаблоны: кэширование при помощи includeCachedTemplate

Консоль: использование собственных консольных команд

Консоль: пример запуска команд в определённое время

Реализация нескольких точек входа

Admin Panel: простая административная панель

Radjax: быстрый вспомогательный роутер

Шаблонизатор Twig: подключение

Шаблонизатор Twig: настройка

Мьютексы (Mutexes): установка и использование

Логирование: PSR-3 Logger

Контроллеры: перенос общих данных


Установка и обновление: использование Composer

Установить актуальную версию проекта при помощи консольной команды (требует установленный менеджер пакетов Composer):
$ composer create-project phphleb/hleb
Обновить только ядро фреймворка (выполнить из папки с установленным проектом):
$ composer require phphleb/framework
Если нужно обновить зависимости:
$ composer update

Настройка прав доступа к фреймворку в Linux

После установки фреймворка HLEB для Linux необходимо настроить права. Для этого нужно знать имя группы веб-сервера. Далее предложен вариант, как установить расширенные права на редактирование файлов в папке /storage/ проекта.
Веб-сервер может носить имя www-data, а его группа может быть одноимённая www-data. При запуске фреймворка, если права ещё не заданы, будет выведена ошибка с попыткой определить имя и группу активного веб-сервера.
Чтобы новые файлы, создаваемые веб-сервером, могли редактироваться через консоль текущим пользователем, необходимо добавить его в группу веб-сервера:
$ sudo usermod -aG www-data ${USER}
После этих изменений в составе группы, чтобы изменения применились, необходимо разлогиниться и снова залогиниться в системе под этим пользователем, или выполнить следующую команду:

$ su - ${USER}
Следующая проверка должна вывести 'www-data' в списке групп:
$ id -nG
Затем расширить права на папку /storage/ для группы (из корневой директории проекта).
$ sudo chmod -R 750 ./ ; sudo chmod -R 770 ./storage

Установка и настройка: вариативное включение debug-режима

Несмотря на то, что для тестовой версии проекта и итоговой устанавливаются разные настройки в конфигурационном файле start.hleb.php, иногда бывает необходимо опционально включить debug-режим или другие настройки в публичной версии. Это можно реализовать двумя способами - переключить опционально на тестовый конфигурационный файл в /public/index.php или установить условие в файле start.hleb.php, например по определенномуip-адресу. Естественно, с этого адреса доступ может быть только у необходимого круга лиц.
// start.hleb.php define( 'HLEB_PROJECT_DEBUG', $_SERVER['REMOTE_ADDR'] == '127.0.0.1' );

Настройка: Изменить папку public проекта на другую

Чтобы установить вместо папки "public" другую, например, "public_html" - необходимо в корневом файле "console" изменить строчку:
// `console` file define( 'HLEB_PUBLIC_DIR', 'public_html' );
В остальном, если адрес ресурса уже направлен в "public_html", никаких изменений делать не нужно.

Настройка: Изменить название директории vendor

Для изменения названия папки "vendor" с библиотеками проекта, например, на "lib", в корневом файле "console" изменить в строчке:
// `console` file require(__DIR__ . "/lib/phphleb/framework/console.php");
Также в файле index.php изменить папку в пути для загрузчика на "lib":
// public/index.php include __DIR__ . '/../lib/phphleb/framework/bootstrap.php';

Настройка: Внедрение собственного кода при загрузке фреймворка.

Бывает так, что при загрузке фреймворка необходимо задать собственные правила или выполнить определенный код. Добавлять его в специализированные файлы вроде "index.php", "aliases.php" или "shell.php" не совсем правильно, для предзагрузки существуют Middleware, что сделает эти правила более гибкими.
Route::before('PreloaderMiddlewareBefore')->getGroup('Preloader.Group'); // ... Маршрутизация проекта ... Route::endGroup('Preloader.Group');
// Файл /app/Middleware/Before/PreloaderMiddlewareBefore.php namespace App\Middleware\Before; class PreloaderMiddlewareBefore extends \MainMiddleware { function index() { /* Код и правила, выполняемые до всех маршрутов. */ } }

Маршрутизация: установка формата для адреса

Переданные с контроллером значения можно использовать для различных целей, например, для назначения локали и/или формата данных.
Route::get('/get_data/')->controller('AjaxTestController@data', ['json', 'en']);
// Файл /app/Controllers/AjaxTestController.php ... function data($format, $locale) { ... } ...
Реализация подобного назначения локали для группы маршрутов:
Route::before('SetLocaleBefore@index', ['ru'] ); Route::getGroup(); Route::get( ... ); Route::get( ... ); Route::endGroup();

Маршрутизация: использование переменных в файлах роутов

Так как карта маршрутизации содержит обычный код PHP, то допустимо использовать все его возможности с единственным ограничением: без использования внешних данных. Например, задание переменных:
$page = "test"; Route::get("/$page/first/", ... ); Route::get("/$page/second/", ... );
В данном примере на практике уместнее было бы использовать префикс группы. Однако, вынос значений в переменные может привести к созданию универсального шаблона маршрутов для использования в дальнейшем.

Маршрутизация: работа с поддоменами

Задать правила для поддоменов, а также, при надобности, для домена любого уровня, помогут два метода маршрутизации: domain() и domainTemplate().
Route::domain("dev")->getGroup(); Route::get("/", ... ); // dev.site.com или *.dev.site.com Route::endGroup(); Route::get("/", ... ); // все запросы (site.com или *.site.com)
Второй параметр в этих методах указывает на соответствие уровню домена, начиная от домена верхнего уровня (1), домена (2) и поддоменов (3 по умолчанию и далее), а первый аргумент может быть перечнем допустимых значений. Например:
Route::domain("sub4", 4)->domain("sub3")->get("/", ... ); // sub4.sub3.site.com или *.sub4.sub3.site.com Route::domain(["var1", "var2", null])->get("/", ... ); // var1.site.com, var2.site.com или site.com, а также *.var1.site.com или *.var2.site.com
Route::domain(null)->get("/", ... ); // без поддоменов, только site.com Route::domain("sub4", 4)->domain("*")->get("/", ... ); // sub4.*.site.com или *.sub4.*.site.com // метод domain("*") добавлен для наглядности маршрута, его можно не указывать
Так как в этих примерах адрес роута одинаковый ("/"), то следует обратить внимание на очередность роутов, будет выбран первый совпавший по URL, поэтому рекомендуется сначала располагать частные случаи, а затем общие.

Привязка маршрутов осуществляется без формального разделения на домен и поддомены, только по уровням иерархии доменного имени, начиная от домена верхнего уровня. В комментариях к этим примерам "site.com" аналогичен любым вариантам домена второго и первого уровня.

Метод domainTemplate() отличается только тем, что в первом аргументе передаются значения для проверки на совпадение в регулярном выражении.

Для кириллических поддоменов возможно придётся задавать условия только после конвертации названия поддоменов в Punycode.

Дополнительные константы для предварительной настройки

Приведённые здесь константы (HLEB_PROJECT_ONLY_HTTPS, HLEB_PROJECT_GLUE_WITH_WWW) необходимо разместить в файле настроек проекта hleb.start.php для того, чтобы быстро настроить и продемонстрировать соответствующие свойства проекта и/или проект часто переносится и нет возможности заменить на серверные настройки. Настройка на стороне сервера рекомендуется в большинстве случаев и может конфликтовать с данными установками, если применяется вместе с ними.
/*
|-----------------------------------------------------------------------------
| Demo redirection from "http" to "https".
|
| Демонстрационное перенаправление с "http" на "https".
|
*/
define('HLEB_PROJECT_ONLY_HTTPS', false);
/*
|-----------------------------------------------------------------------------
| Demo URL redirection from "www" to without "www" and back 0/1/2.
|
| Демонстрационное перенаправление URL с "www" на без "www" и обратно 0/1/2.
|
*/
define('HLEB_PROJECT_GLUE_WITH_WWW', 0);
В версии фреймворка 1.5.51 в настройки добавлена константа для глобального управления кешированием для всех шаблонов (стандартные и Twig).
/*
|-----------------------------------------------------------------------------
| Allows to enable (true) / disable (false) template caching.
|
| Позволяет включить (true) / выключить (false) кэширование шаблонов.
|
*/
define('HLEB_TEMPLATE_CACHE', true);
Запись запросов SQL из DB и XD в логи. Это может быть необходимо для логирования запросов вне режима отладки.
/*
|-----------------------------------------------------------------------------
| Enables writing SQL queries to a log file.
|
| Включает запись SQL-запросов в лог-файл.
|
*/
define('HLEB_DB_LOG_ENABLED', false);
Если страницы сайта не требуют работы с сессиями, можно отключить сессии через настройки. По умолчанию включено (true).
/*
|-----------------------------------------------------------------------------
| Using Sessions in a project.
|
| Глобальное включение Session в проекте.
|
*/
define('HLEB_DEFAULT_SESSION_INIT', true);
В случае, если карта маршрутов уже составлена и изменяется редко, то рекомендуется отключить автоматическое обновление и обновлять кеш маршрутов консольной командой php console --clear-routes-cache или удалением файла кеша `/storage/cache/routes/routes.txt`.
По умолчанию включено (true).
/*
|-----------------------------------------------------------------------------
| Automatic cache update on changes in route files.
|
| Автоматическое обновление кеша при изменениях в файлах маршрутов.
|
*/
define('HLEB_AUTOMATIC_ROUTE_UPDATES', true);
Установка временной зоны для проекта на основе стандартной PHP-функции. Переопределяет начение `date.timezone` в INI-файле.
/*
|-----------------------------------------------------------------------------
| Sets the default timezone used by all date/time functions in a script.
|
| Устанавливает часовой пояс по умолчанию для всех функций даты/времени в скрипте.
|
*/
date_default_timezone_set('Europe/Moscow');
Начиная с версии фреймворка 1.5.91 есть возможность использовать константу для вывода текущего домена в название лог-файла.
/*
|-----------------------------------------------------------------------------
| Adds the current domain to the name of the log file.
|
| Добавляет в название файла логов текущий домен.
|
*/
define('HLEB_PROJECT_LOG_SORT_BY_DOMAIN', true);
Начиная с версии фреймворка 1.6.74 есть возможность использовать константу для ограничения действия константы HLEB_PROJECT_ENDING_URL только к выбранным методам запроса.
/*
|-----------------------------------------------------------------------------
| Restricts the action of HLEB_PROJECT_ENDING_URL to only the specified HTTP methods.
|
| Ограничивает действие HLEB_PROJECT_ENDING_URL только на указанные HTTP-методы.
|
*/
define('HLEB_ENDING_URL_INCLUDING_METHODS', ['get']);

Модули: возможности модульной разработки

Модульная разработка не нарушает базовый принцип MVC фреймворка. Для работы с его компонентами, в рамках изолированного модуля, не всегда удобно переключаться по папкам разных частей проекта. Поэтому предусмотрен вариант, когда все файлы разрабатываемого модуля расположены рядом. Отличие состоит в том, чтобы вместо метода controller() в маршрутизаторе использовать метод module().

Дополнительно необходимо создать папку "/modules" в корневой папке проекта. В ней будут располагаться модули с возможностью различного уровня вложенности.

/modules
    /example
        /DefaultModuleController.php
        /content.php
        /templates
            /origin.php

Все составляющие элементы используют внутренние ссылки на папку "modules".
Route::get('/test/module/example/')->module('example', 'DefaultModuleController');
Если вторым параметром не передано название класса контроллера, будет использовано "Controller".
Функция view() в данном случае также указывает на папку с текущим модулем "/modules/example".
Пространство имён зависит от пути до файла контроллера, как в приведённом примере:
// Файл /modules/example/DefaultModuleController.php namespace Modules\Example; class DefaultModuleController extends \MainController { function index() { return view('content'); } }
Функции шаблонов при использовании модуля тоже направляют в папку "modules".
// Файл /modules/example/content.php includeTemplate('/example/templates/origin');
Есть возможность создавать вложенные модули, в таком случае, если изменить предыдущую файловую структуру:

/modules
    /example
        /attachment
            /DefaultModuleController.php
            /content.php
            /templates
                /origin.php

Роутинг будет выглядеть вот так:
Route::get('/test/module/v2/example/')->module('example', 'Attachment/DefaultModuleController');
Функция view(...) в контроллере при поиске файла будет указывать на текущую директорию `/modules/example/attachment/`, то есть останется без изменений, а для шаблона (в includeTemplate()) нужно будет указать путь `/example/attachment/templates/origin` от корневой папки модуля, так шаблоны останутся общими.
Для модулей также, как и для контроллеров, возможно использование произвольных значений в названии контроллера модуля и метода из url.

Памятка: защита проекта на фреймворке HLEB

1 Отправка форм и ajax-запросов
1.1 Добавить в форму и/или запрос CSRF-защиту, предусмотренную фреймворком. Об этом здесь. Проверить, запросив с неправильным токеном и валидным.
1.2 Отправлять данные POST-методом.

2 DEBUG-режим
2.1 Использовать DEBUG-режим только при разработке.
2.2 Проект с запущенным DEBUG-режимом НЕ должен быть в свободном доступе из сети Интернет. Даже если на сайте все страницы без контента, специфический вывод панели отладки может спровоцировать нежелательный интерес со стороны сканирующих ботов.

3 Уникальный идентификатор проекта и кеш
3.1 При копировании проекта для иных целей, путем прямого переноса из папки, рекомендуется в перенесённой(!) конечной копии очистить данные в папках "storage/cache/key", "storage/public" и "storage/logs". В общем, везде, где хранится кеш с подразумеваемым его удалением.

4 Очистка входящих данных
4.1 Ограничивайте внешние данные по принципу "что не разрешено, то запрещено".
4.2 Используйте специальные методы маршрутов с регулярным выражением(where([...])), чтобы лишние символы отбрасывались уже на этом уровне. В константе HLEB_PROJECT_VALIDITY_URL также можно ограничить допустимые символы в маршрутах для всего проекта.
4.3 Старайтесь не использовать GET-параметры для получения пользовательских данных, для этого должно хватить возможностей маршрутизации. Используйте методы объекта Request для получения очищенных параметров всех типов.
4.4 Основное внимание нужно обратить на то, чтобы пользовательские данные не попадали напрямую в базу данных (защита от SQL-инъекций), например, используя PDO. И также код JavaScript, а лучше любой код в тегах, был особым образом обработан при самом поступлении данных. Если производится запись в базу данных, то рекомендуется хранить пользовательские данные в необработанном виде, применяется лишь отделение данных от запроса (например, средствами PDO). А при выводе нежелательные теги преобразовывать в спецсимволы или иным образом обезопасить контент.
4.5 Указание кодировки каждой веб-страницы должно быть обязательным (в основном это кодировка UTF-8). Так данные будут интерпретированы в ожидаемом виде.
4.6 Использовать Content Security Policy (CSP). Это заголовок, который позволяет в явном виде объявить разрешённый список источников, с которых можно подгружать различные данные, например, JS, CSS, JPG и тд.
4.7 Запретите вставку страниц проекта в сторонние фреймы, если это не задумано заранее, минимальная защита представлена заголовком X-Frame-Options в файле index.php в публичной директории проекта.

5 Безопасность используемых данных
5.1 Настройте права доступа для веб-сервера и командной строки (в UNIX-системах), добавив владельца проекта в группу веб-сервера и разрешив этой группе полный доступ к папке /storage/.
5.2 Выставите значения для cookies в php.ini (session.use_cookies=On, session.use_only_cookies=On, session.cookie_httponly=On, session.cookie_secure=On) и убедитесь, что вам известно, какие условия будут выполняться при этом.

Использование Content Security Policy

Директива Content Security Policy устанавливает различные степени защиты в браузере, при использовании максимального уровня защиты, при котором не разрешается использовать встроенные на странице стили и скрипты, может неправильно отображаться отладочная панель в режиме DEBUG. Обойти это можно следующим способом, добавив политику CSP в виде исключения в конце файла конфигурации:
// Файл /start.hleb.php if ( ! HLEB_PROJECT_DEBUG) { header("Content-Security-Policy: default-src ‘self’"); }
Библиотека hlogin будет работать только с разрешением выполнения встроенных стилей и скриптов.

Использование отладочной панели Debug Panel

В микрофреймворк HLEB добавлена отладочная панель, активная только в DEBUG-режиме. Она подключается при помощи строки:
// Файл /app/Optional/MainConnect.php ... [ "Phphleb\Debugpan\DPanel" => "vendor/phphleb/debugpan/DPanel.php" ] ...
После подключения можно настроить эту панель, добавив собственные данные для вывода, используя методы классов \Hleb\Main\MyDebug и \Hleb\Main\WorkDebug.

MyDebug

Создав класс FirstBefore или любым подобным способом:
// Файл /app/Middleware/Before/FirstBefore.php namespace App\Middleware\Before; use Hleb\Main\MyDebug; class FirstBefore extends \MainMiddleware { function index() { MyDebug::add("SERVER", $_SERVER ?? []); MyDebug::add("SESSION", $_SESSION ?? []); MyDebug::add("REQUEST", $_REQUEST ?? []); // Пример последовательного добавления if (isset($_COOKIE)) { foreach ($_COOKIE as $key => $value){ MyDebug::insertToListing("COOKIE", "<b>$key</b>: " . $value); } } } }
Первым аргументом здесь указывается желаемое название вкладки, при этом для MyDebug::add() вторым параметром обязателен массив, а для MyDebug::insertToListing() второй аргумент - любое значение, добавляемое в массив по имени. Также можно использовать метод MyDebug::insertToString(), в котором вторым аргументом добавляется строковое значение.

Теперь в отладочной панели появятся четыре дополнительные вкладки с параметрами, при условии, что этот класс подключён в карте маршрутов перед проверяемыми маршрутами.
Route::before('FirstBefore')->getGroup(); Route::get('/', 'Данные выведены в панель отладки.'); ... // Распространить правило на подключаемый файл include 'other_routes.php'; Route::endGroup();
Применение класса WorkDebug аналогично MyDebug, только служит для временного вывода данных при помощи var_dump, которые размещаются поверх страницы в отдельной панели. Отладочные данные этих классов доступны для добавления везде в проекте, кроме After-классов, которые выполняются после вывода основного контента.
\Hleb\Main\WorkDebug::add($data);
// Равносильно print_r2($data);
// С описанием \Hleb\Main\WorkDebug::add($data2, 'Описание data2');
Более простым и запоминающимся аналогом для вызова WorkDebug::add() является функция print_r2() c такими же аргументами.

Маршрутизация: регулярное выражение для динамических значений

Регулярное выражение в методе where() может принимать различные условия, в рамках правил составления регулярных выражений, например:
Route::get('/{page}/', 'Все вхождения по условию, кроме /map/.')->where(['page' => '(?!(map))[a-z0-9\-_]+']); Route::get('/map/', 'Отдельная обработка /map/.');
Данный пример не совсем отвечает интуитивно понятному способу составления карты маршрутов, в реальном проекте лучше определить эту закономерность при помощи очередности поиска подходящего маршрута:
Route::get('/map/', 'Отдельная обработка /map/.'); Route::get('/{page}/', 'Все вхождения по условию, кроме /map/.')->where(['page' => '[a-z0-9\-_]+']);

Объект Request: данные динамического маршрута

Объект Request содержит значения из URL по совпадению с динамическим маршрутом. Например, при запросе "site.com/first/" и наличии маршрута:
Route::before('TestMiddlewareBefore')->get('/{test}/')->controller('TestController')->after('TestMiddlewareAfter');
В каждом из контроллеров будет доступен объект Request c методом обращения get():
... use Hleb\Constructor\Handlers\Request; ... var_dump( Request::get()); // array(1) { ["test"]=> string(5) "first" } var_dump( Request::get('test')); // string(5) "first"

Объект Request: обработка данных запроса

Через методы объекта Request (Hleb\Constructor\Handlers\Request) также можно получить различные данные пользовательского запроса.

Request::getMethod() - получение метода запроса из $_SERVER['REQUEST_METHOD'], к примеру 'GET', 'POST', 'PUT'
Request::getUri() - URI, который был предоставлен для доступа к этой странице. Например, '/index.html'
Request::getReferer() - определяет адрес предыдущей страницы, при его существовании, с которого пользователь осуществил переход.
Request::getHost() - возвращает интернет-хост с номером порта(если он был в запросе) запрашиваемого ресурса.
Request::getFullHost() - возвращает интернет-хост с номером порта(если он был в запросе) и текущим протоколом 'http://' или 'https://'.
Request::getFullUrl() - возвращает полный url-адрес ресурса, без GET-параметров.
Request::getMainUrl() - возвращает url-адрес ресурса, c GET-параметрами.
Request::getHttpHeader(<значение>) - обращается к значениям $_SERVER.
Request::getCookie(<значение>) - возвращает значение $_COOKIE.
Request::getSession(<значение>) - возвращает значение $_SESSION.
Request::getRemoteAddress() - возвращает IP-адрес, с которого пользователь просматривает текущую страницу.
Request::getJsonBodyList() - возвращает массивом данные, пришедшие из Body запроса в формате JSON.
Request::getGet(<значение>) - возвращает данные GET-запроса из $_GET по значению.
Request::getPost(<значение>) - возвращает данные POST-запроса из $_POST по значению.
Request::getRequest(<значение>) - возвращает данные POST-запроса или GET-запроса из $_REQUEST по значению.

Request::getHead()->addStyles(<url>) - добавляет ссылку на CSS-файл с подключаемыми стилями.
Request::getHead()->addScript(<url>) - добавляет ссылку на JS-файл с подключаемыми скриптами.
Request::getHead()->setTitle(<текст>) - вывод в <head><title>...</title></head>.
Request::getHead()->setDescription(<текст>) - установка META-тега описания страницы.
Request::getHead()->addMeta(<название>, <контент>) - задание произвольного META-тега.
<?php getRequestHead()->output(); ?> - помещенное в <head>...</head> страницы, отображает все предыдущие установленные данные getHead().

Request::getResources()->addBottomStyles(<url>) - добавляет ссылку на CSS-файл в нижней части страницы.
Request::getResources()->addBottomScript(<url>) - добавляет ссылку на JS-файл в нижней части страницы.
<?php print getRequestResources()->getBottomStyles(); ?> - получение данных стилей для вывода на странице (в нижней её части).
<?php print getRequestResources()->getBottomScripts(); ?> - получение данных JS-файлов для вывода на странице (в нижней её части).


Шаблоны: использование includeTemplate

Части кода в подключаемых файлах папки views могут повторятся. Чтобы вынести их в отдельный шаблон, независимый от окружающего контента, используется функция includeTemplate(), первым аргументом которой указывается название шаблона из папки resources/views, а вторым - массив переменных, которые будут доступны в шаблоне по ключам массива. Для отличия шаблонов от других файлов их рекомендуется разместить в отдельной папке templates.

Данные шаблоны могут обращаться только к ".php" файлам, поэтому они не используются при работе с шаблонизатором (Twig), который включает собственные способы управления контентом.
// Файл /resources/views/content.php includeTemplate('templates/name', ['variable1'=>'value1', 'variable2'=>'value2']);
// Файл /resources/views/templates/name.php echo $variable1; // data1 echo $variable2; // data2

Шаблоны: кэширование при помощи includeCachedTemplate и includeOwnCachedTemplate

В отдельных случаях, если содержимое шаблона требует существенных расчётов, но данные неизменны некоторое время, есть возможность задать кэширование. Чем больше производительности требуют эти вычисления, тем больший прирост в скорости обработки блока можно получить при последующих обращениях, так как в кэше он сохранен как обычный html-текст.
Кэширование будет привязано к заданному времени в методе setCacheTime(), который необходимо расположить в шаблоне для функции includeCachedTemplate(). Аргументы последней идентичны функции includeTemplate(), единственное отличие, что заданное в шаблоне время кэширования приведет к кэшированию содержимого. Все изменения содержимого шаблона будут недоступны до истечения указанного срока. Также следует учесть, если шаблон задан через аналогичную функцию includeOwnCachedTemplate() и в нём установлено время кэширования, то где бы он не был использован в проекте, кэшированное значение его будет для текущего пользователя одинаковым. Таким образом функция includeOwnCachedTemplate отличается от includeCachedTemplate привязкой к пользовательской сессии, что ведёт к значительному увеличению размера кэша, а также более низкой его эффективности, но зато позволяет включать в кэш пользовательские данные.
// Файл /resources/views/content.php includeCachedTemplate('templates/cached/name');
// Файл /resources/views/templates/cached/name.php $this->setCacheTime(60); // задание времени кеширования в секундах echo rand(); // рандомное число будет неизменно в течение минуты

Кэширование должно быть рациональным и обдуманно применено в каждом случае, иначе результат может слишком явно не соответствовать вычисляемым данным. Например, рекомендуется кэшировать блоки, содержащие информацию, которая рассчитывается из текущего названия месяца или ежедневно обновляемого курса валют. Но и в таком случае значение желательно устанавливать не более 10 минут (600 секунд).
Если это определение без особых затрат производительности, вроде прямого получения даты из PHP-функции date(), то эффективности от кэширования не будет.

Также, при использовании дополнительно передаваемых параметров во втором аргументе данных функций, возможно отсутствие пользы от кеширования, если эти параметры часто изменяются. Для каждого варианта из этих параметров будет создан собственный соответствующий кэш.
В случае возникновения каких-либо сомнений при использовании этой функции, лучше заменить её на функцию includeTemplate().
Для удаления всего кэша шаблонов - удалить содержимое папки /storage/cache/templates/ или специальной консольной командой.

Консоль: использование собственных консольных команд

Перечень встроенных консольных команд отображается командой php console --help, выполненной в корневой директории проекта.
$ php console --help
--version or -v (displays the version of the framework) (версия фреймворка)
--clear-cache or -cc (clears the templates) (очистка кэша шаблонов и маршрутов)
--forced-cc (forcefully clears the templates)
--clear-routes-cache or -routes-cc (clear route cache)
--update-routes-cache or -routes-upd (update route cache)
--info or -i (displays the values of the main settings) (вывод настроек)
--help or -h (displays a list of default console actions)
--routes or -r (forms a list of routes)
--list or -l (forms a list of commands) (вывод списка пользовательских команд)
--list <command> [--help] (command info)
--logs or -lg (prints multiple trailing lines from a log file) (отображение последних ошибок в логах)
--find-route <url> [method] [domain] (route search by url)
--new-task (сreates a new command)
--new-task example-task "Short description" (Создание новой команды)

Создать собственную консольную команду при помощи добавления соответствующего класса в app/Commands/:
// Файл /app/Commands/DefaultTask.php namespace App\Commands; class DefaultTask extends \Hleb\Scheme\App\Commands\MainTask { // php console default-task [arg] const DESCRIPTION = "Default task"; // Короткое, но информативное описание команды. protected function execute($arg = null) // Переменная $arg будет содержать необязательный дополнительный аргумент. { // Содержимое класса } }
Теперь можно вызвать эту команду по принципу соответствия DefaultTask > default-task.
$ php console default-task
Если, например, класс расположен в папке app/Commands/Folder/, то вызвать его можно таким образом:
$ php console folder/default-task
Стоит учесть, что при расположении класса в папке app/Commands/Folder/, его namespace должно быть App\Commands\Folder.

Добавление произвольного значения "test", которое будет передано в переменной $arg :
$ php console default-task test

Если необходимо передать два или более аргументов с командой, то их необходимо указать в методе execute, например так: execute($arg1, $arg2, $arg3).

Передача именованных параметров, например:
$ php console default-task --arg1=value1 --arg2=value2
Параметры будут разбиты по названиям и далее их можно получить в коде через метод $this->getOption('arg1') или $this->getOption('arg2', 0), в последнем случае также указано значение по умолчанию. Не допускается смешивать обычные аргументы с именованными параметрами.

При добавлении аргумента --quiet после пользовательской консольной команды отключается вывод данных.

Чтобы выполнить конкретную команду внутри другой или в коде приложения, нужно вызвать метод call(). Это может быть как (new DefaultTask())->call(['arg1' => 'value1', 'arg2' => 'value2']) или (new DefaultTask())->call(['test']) для приведённых выше примеров.

Консоль: пример запуска команд в определённое время

Если выполнение команд происходит при помощи cron, с 1.2.3 версии фреймворка добавлена возможность собрать несколько команд в одной основной и запускать в зависимости от назначенного времени. B cron указывается только выполнение основной команды, каждую минуту. Таким образом, при переносе проекта, достаточно задать одну команду на выполнение. Например, в основной команде systematic-launch-task заданы две исполняемые команды.
// Файл /app/Commands/SystematicLaunchTask.php namespace App\Commands; use Hleb\Main\Commands\MainLaunchTask class SystematicLaunchTask extends MainLaunchTask { // php console systematic-launch-task const DESCRIPTION = "Main launch all selected Tasks"; protected function execute() { // В начале каждого часа $this->everyHour("/usr/local/bin/php7.3 ~/hleb/console default-task test"); // Каждый день в 04:05 if ($this->givenHour(4)) { $this->givenMinutes(5, [ "/usr/local/bin/php7.3 ~/hleb/console rotate-logs-task &" ]); } } }
C версии фреймворка v1.6.87 добавлен более простой способ указания команд.
Первоначальный вариант:
$this->everyHour('/usr/local/bin/php7.3 ~/hleb/console default-task test');
Добавлен вариант c непосредственным выполнением:
$this->everyHour([DefaultTask::class, ['test']]);
Перечисленные в обработчике команды выполняются независимо друг от друга, если произошла ошибка при выполнении задачи, то она будет записана в лог и начнется выполнение следующей.

Подробный перечень методов, задающих время выполнения, можно найти в классе MainLaunchTask, от которого унаследован основной класс. Методы, определяющие конкретные минуты, могут принимать список этих команд в виде массива, остальные могут служить условием запуска.

everyMinute(<commands>) - каждую минуту.
everyHour(<commands>) - каждое начало часа.
everyDay(<commands>) - каждый день в 00:00.
givenMinutes(<minute>, <commands>) - первым аргументом минуты часа (0-60) в массиве или числовое значение.
givenHour(<hour>) - часы (0-12) в массиве или числовое значение.
givenMonth(<month>) - месяцы (1-12) в массиве или числовое значение.
givenWeeklyDay(<day>) - значения дня недели (1-7) в массиве или числовое значение.
givenMonthlyDay(<day>) - значения дня месяца (1-31) в массиве или числовое значение.
givenYearDay(<day>) - значения дня года (1-365) в массиве или числовое значение.


Реализация нескольких точек входа

По умолчанию точкой входа в приложение является /public/index.php. Так как загрузочный файл запускается из этого индексного файла, то изменить название публичной папки проекта легко - просто переименовав её и направив на неё веб-сервер, подробнее.

Например, необходимо создать отдельную папку для независимой front-разработки на поддомене основного сайта (точки входа). Скопируйте папку /public/ и, для наглядности сохраните в новую папку /dev/ в корневой директории проекта, дополнительно переименовав в /public_html/.
В эту директорию /dev/public_html/ нужно направить необходимый поддомен. Если это директория разработки, то прежде необходимо поставить на нее пароль.
Теперь, открыв файл /dev/public_html/index.php нужно добавить следующие изменения для того, чтобы фреймворк обнаружил необходимые ресурсы.
// Файл /dev/public_html/index.php // ... define('HLEB_GLOBAL_DIRECTORY', realpath(__DIR__ . '/../../')); require realpath(HLEB_GLOBAL_DIRECTORY . '/vendor/phphleb/framework/bootstrap.php');
Здесь ресурсы для фронтенда уже берутся для поддомена из папки /dev/public_html/. При этом бекенд вне этой папки остался общим.

Далее, возможно назначить собственные конфигурационные файлы для этой папки и также дополнительно отдельную папку /storage/ (скопированную из оригинальной) с выводом логов и кешированием. Чтобы продемонстрировать полностью возможности раздельных точек входа, скопируем в эту же папку файл ./console из корневой директории. Получим следующую структуру:
/dev/
    /public_html/
        /css/
        /images/
        /js/
        /svg/
        /.htaccess
        /favicon.ico
        /index.php
        /robots.txt
    /storage/
    /console
    /dbase.config.php
    /start.hleb.php


Для работы этой структуры нужно внести некоторые изменения в /dev/public_html/index.php и /dev/console:
// Файл /dev/public_html/index.php // ... define('HLEB_GLOBAL_DIRECTORY', realpath(__DIR__ . '/../../')); define('HLEB_SEARCH_START_CONFIG_FILE', realpath(__DIR__ . '/../')); define('HLEB_SEARCH_DBASE_CONFIG_FILE', realpath(__DIR__ . '/../')); define('HLEB_STORAGE_DIRECTORY', realpath(__DIR__ . '/../')); require realpath(HLEB_GLOBAL_DIRECTORY . '/vendor/phphleb/framework/bootstrap.php');
// Файл /dev/console define('HLEB_GLOBAL_DIRECTORY', realpath(__DIR__ . '/../')); define('HLEB_PUBLIC_DIR', realpath(HLEB_GLOBAL_DIRECTORY . '/dev/public_html')); define('HLEB_SEARCH_START_CONFIG_FILE', __DIR__); define('HLEB_SEARCH_DBASE_CONFIG_FILE', __DIR__); define('HLEB_STORAGE_DIRECTORY', realpath(__DIR__ . '/storage')); require  realpath(__DIR__ . '/../vendor/phphleb/framework/console.php');
После этих операций получается отдельная точка входа, со своими конфигурационными файлами (например, для dev-разработки можно оставить отладочный режим), отдельное логирование ошибок при подключении к поддомену, отдельные настройки базы данных и все это на одном общем проекте.

Запрос консольной команды к текущей точке проекта будет таким из корневой папки:
$ php ./dev/console --info


Admin Panel: простая административная панель

В версии 1.1.5 фреймворка добавлен новый метод маршрутизации adminPanController(). Он необходим для использования библиотеки phphleb/adminpan, которая подключается отдельно.
$ composer require phphleb/adminpan
Метод adminPanController() аналогичен методу controller(), с той разницей, что отображаемый им контент будет встроен в оболочку административной панели.
Route::get('/admin/panel/main/')->adminPanController('AdminController@main', 'Page Name');
Следует использовать только прямые указания пути для маршрутов данного контроллера, чтобы в его меню правильно отображались ссылки на другие роуты adminPanController() и названия ссылок (в примере выше это "Page Name").
К странице в дальнейшем допустимо подключить какой-нибудь СSS- и/или JS-фреймворк в коде подключаемого контента.

Кроме того, можно воспользоваться встроенными инструментами формирования таблиц и списков.
// Файл /app/Controllers/AdminController.php namespace App\Controllers; use Phphleb\Adminpan\MainAdminPanel; class AdminController extends \MainController { function main() { $panel = new MainAdminPanel(); // Вывод HTML $content = $panel->getDataHTML("<b>HTML</b>");
// Вывод нумерованного списка $content .= $panel->getDataList(["Text 1", "Text 2", "Text 3"]);
// Вывод массива с данными в виде таблицы $data = UserModel::getData(); $content .= $panel->getDataTable($data);
return $content ; } }
Таким же образом методы класса MainAdminPanel доступны в view-файлах, возвращаемых из контроллера функцией view().


Radjax: быстрый вспомогательный роутер

Данный маршрутизатор не включен в основной пакет фреймворка, его можно установить через Composer:
$ composer require phphleb/radjax
После установки остается только назначить роуты в файле /routes/rajax.php проекта. Эти роуты просты и отличаются от роутов фреймворка HLEB. В них присутствует один метод get(), через который и настраивается конфигурация, все эти методы первоочередны по отношению к стандартным маршрутам фреймворка. Следует отметить, что, кроме этих отличий, есть ещё несколько:

1) Если тип маршрута (GET, POST и тд.) из конфигурации не подходит, нo адрес маршрута совпал, Radjax-роутер не пропустит поиск далее, как это реализовано во фреймворке, а выдаст ошибку 405 (метод не поддерживается) c перечислением корректных для этого маршрута типов в заголовке.

2) Константы для настроек фреймворка (/start.hleb.php) также влияют на код Radjax-роутера, хотя он функционирует независимо и запускается отдельно от фреймворка, из-за чего его производительность выше. В большинстве, от роутера такого типа, требуется только получить или сохранить данные по запросу и отобразить ответ в виде json или xml.

3) Radjax может проверять токен, генерируемый фреймворком для защиты от CSRF-атак, если включен параметр protected данного роутера, но не устанавливает токен самостоятельно. Поэтому, используемый для ajax-запросов роутер проверяет существующий токен, как минимум уже установленный в сессию на странице, с которой был выполнен запрос. При использовании роутера как API, параметр protected следует отключить, чтобы сторонние ресурсы могли запрашивать данные. Дубликат токена для проверки добавляется со страницы GET- или POST-параметром "_token".

4) Файл /routes/radjax.php может включать только специальные роуты Radjax, этот файл не нужно инклюдировать в файл /routes/main.php, как в случае с другими файлами роутинга фреймворка.

5) Класс контроллера указывается полностью, вместе с его namespace, например, "App\Controllers\Api\MainController", а при не обнаружении метода класса, указанного после знака "@", поиск продолжается по указанному методу с добавлением приставки "Http{Type}", где Type это текущий тип HTTP запроса. Например, для "App\Controllers\Api\MainController@test" и указанием поддерживаемого типа ["post"], в соответствующем классе сначала будет произведен поиск метода test(), а если он не будет найден, то testHttpPost().

6) По умолчанию реализовано чтение файлов сессий, но не их сохранение, это улучшает обработку конкурентных ajax-запросов. Однако, возможность сохранения изменений в сессию можно включить в конфигурации роута, указав "save_session" => true.

Radjax\Route::get('/customers/{number}/orders?/', ["get","post"], "App\Controllers\CustomersController@orders", ["protected" => true, "where"=>["number" => "[0-9]+"], "save_session" => false]);
Как видно из примера, первым параметром идёт адрес роута, в нём значение number динамическое и доступно из Request::get("number"), а знак "?" после orders обозначает, что эта часть адреса не обязательна для совпадения. Вторым параметром указан массив с типами HTTP-запроса, на третьей позиции класс контроллера и метод класса, четвертым аргументом включён массив необязательных именованных параметров.

Чтобы использовать только Radjax-роутер в проекте необходимо назначить константу HLEB_ONLY_RADJAX_ROUTES и присвоить ей значение true. После этого стандартный роутинг будет отключен, несуществующие страницы будут выдавать 404-ошибку чуть быстрее и можно будет продолжить пользоваться некоторыми другими возможностями фреймворка: объектом Request, DB и консольными командами.

Шаблонизатор Twig: установка

При желании можно подключить к фреймворку шаблонизатор Twig. Он будет работать через автозагрузку классов, генерируемую Composer.
$ composer require "twig/twig:^3.0"

Шаблонизатор Twig: настройка

После установки, если указать расширение файла в функции view() маршрута или контроллера, файл будет передан в обработку Twig. Иначе он будет выполнен как обычный PHP-файл.
Route::get('/handler/twig/', view('template/main.twig', ['p1'=>'data1', 'p2'=>'data2']));
При добавлении в файл hleb.start.php следующих констант, значения их будут управлять действиями шаблонизатора:

HLEB_TEMPLATE_CACHE (bool), включение кеширования для всех шаблонов. По умолчанию включено.
HL_TWIG_AUTO_RELOAD (bool), автоматическая перекомпиляция шаблонов. Изначально ориентировано на режим отладки.
HL_TWIG_STRICT_VARIABLES (bool), включение строгого режима, при котором будут выдаваться ошибки, если существующая переменная не передана в шаблон. По умолчанию отключено.


Мьютексы (Mutexes): установка и использование

Необходима версия PHP >= 7.4

Использование мьютекса оправдано в случае, если необходимо заблокировать доступ к какому либо коду, пока он не будет выполнен в текущем процессе или по истечении заданного времени блокировки. Например, дублирующиеся одновременные запросы по API, могут вызвать параллельную запись в базу данных одного и того же значения. Чтобы этого не произошло, участок кода, ответственный за запись, необходимо обернуть в методы мьютекса. Их всего три - acquire, release и unlock.

Установка и назначение консольных команд (php console mutex/mutex-db-stat-task, php console mutex/mutex-predis-stat-task и php console mutex/mutex-file-stat-task):
$ composer require phphleb/conductor

$ php console phphleb/conductor --add

FileMutex
use \Phphleb\Conductor\FileMutex; $mutex = new FileMutex(); if ($mutex->acquire('mutex-name', 20)) { // Start blocking try { // Custom code. } catch (\Throwable $e) { $mutex->unlock('mutex-name'); // Force unlock throw $e; } } else { throw new \Exception('Custom error text.'); } if ( !$mutex->release('mutex-name')) { // End of blocking // Rolling back transactions }
При установке времени на блокировку (второй аргумент acquire в секундах), стоит учесть, что если активный процесс не сможет самостоятельно разблокировать мьютекс, остальные процессы из непоследовательной очереди, обратившиеся к этому коду, продолжат работу только после истечения этого времени. Поэтому при большой задержке они завершатся на уровне вебсервера по таймауту ожидания ответа от скрипта.

Собственная конфигурация мьютекса(устанавливается один раз):
use \Phphleb\Conductor\FileMutex; $config = new MainConfig(); // implements FileConfigInterface, BaseConfigInterface $mutex = new FileMutex($config);
Файловый тип мьютексов применяется обычно только для одного backend-сервера, иначе можно попробовать синхронизацию папки с файлами-метками мьютексов. Но, если есть такая вероятность, лучше использовать мьютексы основанные на хранении меток в базе данных.

DbMutex

Блокировки с сохранением состояния в базе данных - аналогичная реализация мьютексов. Используются такие-же методы acquire, release и unlock, а также подключение собственной конфигурации. Отличие заключается в классе, который используется при инициализации мьютекса.
Подключение к базе данных по умолчанию используется стандартное для фреймворка, если не указано иное в константе HLEB_MUTEX_TYPE_DB конфигурации.
use \Phphleb\Conductor\DbMutex; $mutex = new DbMutex();


PredisMutex

Аналогичная реализация мьютексов при помощи Redis (библиотеки predis/predis). По умолчанию параметры конфигурации берутся из константы HLEB_MUTEX_TYPE_REDIS или HLEB_TYPE_DB (database/dbase.config.php).
use \Phphleb\Conductor\PredisMutex; $mutex = new PredisMutex();


Логирование: PSR-3 Logger

Функция Logger() возвращает объект с различными уровнями логирования, например Logger()->error('Error message').
Текущий логгер сохраняет логи в файлы, в том числе исключения и предупреждения PHP. Можно создать собственный логер на основе интерфейса LoggerInterface и разместить в папке app/Optional/MainLogger.php (класс App\Optional\MainLogger).
Задать максимальный принимаемый логгером уровень можно при помощи константы HLEB_MAX_LOG_LEVEL в конфигурации фреймворка, например, define('HLEB_MAX_LOG_LEVEL', 'info'). Не рекомендуется устанавливать это значение ниже "warning", иначе могут быть не учтены в логах ошибки выполнения и компиляции PHP кода.

Контроллеры: перенос общих данных

Реализована передача данных из контроллера в контроллер (для этого он должен быть унаследован от класса MainController или MainMiddleware). Для манипуляции данными в контроллерах и посредниках используются методы:

$this->getControllerData(<attribute>) - получение общих данных для контроллеров по ключу или целиком массив без указания ключа.
$this->setControllerData(<attribute>, <value>) - сохранение данных по ключу.
$this->clearControllerData( ) - удаляет все общие данные.
$this->getControllerDataKeys( ) - возвращает список существующих ключей.

В контроллере-посреднике устанавливается значение:
class ExampleMiddleware extends \MainMiddleware { function index() { $this->setControllerData('UserId', 1001); } }
После посредника через роутинг вызывается контроллер:
class ExampleController extends \MainController { function index() { return $this->getControllerData('UserId'); // 1001 } }
Для тестирования контроллера (и только для этого) можно передать в контроллер тестовый массив, который заменит оригинальные данные и изменения будут применены к нему:
use App\Controllers\DefaultController; $test = (new DefaultController($testData))->index();




Предназначение Установить Настройка Структура проекта Маршрутизация Типы маршрутов Группы маршрутов Защита маршрутов Конструктор страниц Контроллеры Модели Получение данных Базы данных Регистрация API DI Дополнительно



Группа поддержки в Телеграм - @phphleb

Задать вопрос в блоге, который сделан с использованием фреймворка HLEB.

Этот сайт-инструкция к фреймворку HLEB сделан с использованием фреймворка HLEB.

HLEB - PHP Микрофреймворк Свободная лицензия. Без гарантий. © Foma Tuturov 2019-2023