Мы используем файлы cookie для улучшения работы сайта. Вы можете принять все cookies или настроить предпочтения. Подробнее в нашей Политике использования cookie-файлов.
Разработка расширений
Что такое расширение?
Расширение (Extension) - это модуль функциональности, который можно привязать к разделу сайта через таблицу s_Navigator. Расширения находятся в packages/WeppsExtensions/.
Типы расширений
- Frontend расширения - для разделов сайта (
WeppsExtensions/) - Кастомные аддоны - специфичные для проекта (
WeppsExtensions/Addons/) - Системные расширения - для админки (
WeppsAdmin/ConfigExtensions/)
Создание расширения
Шаг 1: Регистрация в админке (рекомендуемый способ)
-
Перейдите в админку → список "Расширения" (
s_Extensions) -
Создайте новую запись с параметрами:
- Name:
MyExtension- имя расширения (имя класса) - FileExt:
jpg,png,pdf- разрешенные расширения файлов для загрузки - Lists:
["MyTable"]- связанные таблицы данных - Создать файлы расширения - выбрать шаблон структуры:
1.0- простая структура (копируется из_Example10/)1.1- расширенная структура для работы со списками (копируется из_Example11/)
- Name:
-
При сохранении записи автоматически срабатывает триггер, который:
- Создает папку
WeppsExtensions/MyExtension/ - Копирует файлы из выбранного шаблона (
_Example10или_Example11) - Переименовывает классы и файлы под указанное название
- Создает папку
-
Привяжите расширение к разделу через
s_Navigator(полеExtension)
Шаблоны структуры:
_Example10- минималистичная структура (основной класс + шаблон)_Example11- расширенная структура для работы со списками (Data, CRUD, фильтры)
Шаг 2: Структура файлов расширения
WeppsExtensions/MyExtension/
├── MyExtension.php # Основной класс с логикой
├── Request.php # AJAX-обработчик (опционально, для асинхронных запросов)
├── MyExtension.tpl # Smarty шаблон для списка
├── MyExtensionItem.tpl # Smarty шаблон для детальной страницы (опционально)
├── MyExtension.css # Стили
├── MyExtension.js # JavaScript
├── RequestFilters.tpl # Шаблон для AJAX-ответа (если есть Request.php)
├── RequestFilters.tpl.css # Стили для AJAX-шаблона
├── RequestFilters.tpl.js # JS для AJAX-шаблона
└── files/ # Статические файлы (изображения, документы)
Соглашения по именованию:
- Основные файлы - совпадают с именем класса:
MyExtension.php,MyExtension.tpl,MyExtension.css - AJAX-шаблоны - префикс
Request*:RequestFilters.tplдля асинхронной подгрузки
Шаг 3: Основной класс
MyExtension.php:
<?php
namespace WeppsExtensions\MyExtension;
use WeppsCore\Extension;
use WeppsCore\Data;
use WeppsCore\Smarty;
class MyExtension extends Extension {
/**
* Основной метод обработки запроса
*/
public function request() {
// Получение данных из БД
$data = new Data('MyTable');
$items = $data->fetch('t.IsActive=1', 10, $this->page, 't.Priority DESC');
// Получение Smarty и передача данных
$smarty = Smarty::getSmarty();
$smarty->assign('items', $items);
$smarty->assign('title', 'Заголовок страницы');
$smarty->assign('paginator', $data->paginator);
// Подключение стилей и скриптов (короткий путь /ext/ + версионирование)
$this->headers->css("/ext/MyExtension/MyExtension.{$this->rand}.css");
$this->headers->js("/ext/MyExtension/MyExtension.{$this->rand}.js");
// Установка шаблона (полный путь от корня)
$this->tpl = 'packages/WeppsExtensions/MyExtension/MyExtension.tpl';
}
}
Шаг 4: Шаблон
MyExtension.tpl:
<div class="my-extension">
<h1>{$title}</h1>
<div class="items-list">
{foreach $items as $item}
<div class="item">
<h2>{$item.Name}</h2>
<p>{$item.Description}</p>
{* Вывод изображений *}
{if $item.Images}
<img src="{$item.Images[0].FileUrl}" alt="{$item.Name}">
{/if}
</div>
{foreachelse}
<p>Нет данных для отображения</p>
{/foreach}
</div>
</div>
Шаг 6: Привязка к разделу
- В админке откройте Навигация → s_Navigator
- Создайте или отредактируйте раздел:
- Url:
/my-section/ - Extension: выберите
MyExtension
- Url:
- Теперь раздел доступен по адресу:
/?weppsurl=/my-section/
Доступные свойства Extension
$this->headers
Управление CSS/JS файлами:
// Подключить CSS (короткий путь /ext/ + версионирование)
$this->headers->css("/ext/MyExtension/MyExtension.{$this->rand}.css");
$this->headers->css('https://cdn.example.com/style.css'); // Внешний
// Подключить JS (короткий путь /ext/ + версионирование)
$this->headers->js("/ext/MyExtension/MyExtension.{$this->rand}.js");
$this->headers->js("/ext/MyExtension/MyExtension.{$this->rand}.js", 'defer'); // С атрибутом defer
Примечание: $this->rand — это строка для версионирования файлов CSS/JS, предотвращающая кэширование браузером. Устанавливается в configloader.php на основе настройки debug в config.php. Если debug=1, добавляется случайное число; если debug=0, используется фиксированная строка "dev-1". Разработчик может менять строку версионирования на свое усмотрение.
Подключение файлов из других расширений:
// Можно подключать CSS/JS из других расширений при необходимости
$this->headers->css("/ext/Template/Template.{$this->rand}.css");
$this->headers->js("/ext/Cart/Cart.{$this->rand}.js");
// Внешние библиотеки
$this->headers->css('https://cdn.example.com/library.css');
$this->headers->js('https://cdn.jsdelivr.net/npm/library@1.0.0/dist/library.min.js');
Минификация и версионирование:
Минификация CSS/JS файлов настраивается в конфигурации:
// В config.php
'Services' => [
'minify' => [
'active' => true, // Включить минификацию (true/false)
'lifetime' => 300, // Время жизни минифицированных файлов в секундах
],
]
Как работает минификация:
-
minify.active=> true:- Все CSS/JS файлы собираются в один файл
- Минифицируются через библиотеку MatthiasMullie\Minify
- Сохраняются в
/files/tpl/minify/{hash}где hash = MD5 от всех подключенных файлов - Встраиваются inline в HTML через
<style>и<script>теги - Пересоздаются при истечении
lifetimeили изменении набора файлов
-
minify.active=> false:- Файлы подключаются отдельными ссылками
- Версионирование через
{$this->rand}для предотвращения кэширования браузером
Режим отладки (debug):
debug=> 1:{$this->rand}меняется при каждом запросе (удобно для разработки)debug=> 0:{$this->rand}стабильный (для production)
Пример результата в HTML:
<!-- minify.active = true: -->
<style>/* минифицированный CSS всех файлов */</style>
<script type="text/javascript">/* минифицированный JS всех файлов */</script>
<!-- minify.active = false: -->
<link rel="stylesheet" href="/ext/MyExtension/MyExtension.1234567890.css">
<script src="/ext/MyExtension/MyExtension.1234567890.js"></script>
$this->navigator
Объект текущего раздела навигации (Navigator):
// Текущий раздел
$current = $this->navigator->path;
echo $current['Name']; // Название
echo $current['Url']; // URL
// Родительские разделы
$parent = $this->navigator->parent;
// Дочерние разделы текущего раздела
$child = $this->navigator->child;
// Путь до текущего раздела (хлебные крошки)
$way = $this->navigator->way;
// Контент раздела (дополнительные поля из s_Navigator)
$content = $this->navigator->content;
Текущий пользователь
Доступ к данным пользователя через глобальный объект Connect:
use WeppsCore\Users;
use WeppsCore\Connect;
// Проверка авторизации (выполняется один раз при инициализации)
// На платформе этот вызов уже выполнен в configloader.php:
// $users = new Users();
// $users->getAuth(); // Проверяет JWT токен и устанавливает Connect::$projectData['user']
// Доступ к данным пользователя (если авторизован)
if (!empty(Connect::$projectData['user'])) {
$user = Connect::$projectData['user'];
echo $user['Login'];
echo $user['Email'];
// Проверка прав (1=Администратор, 2=Редактор, 3=Посетитель)
if ($user['UserPermissions'] == 1) {
// Администратор
}
}
Важно:
- Платформа не использует PHP сессии (SESSION) для аутентификации
- Авторизация реализована через JWT токены, которые хранятся в cookie (
wepps_token) или передаются в Bearer заголовке getAuth()уже вызывается автоматически при загрузке платформы вconfigloader.php- В расширениях просто проверяйте наличие
Connect::$projectData['user']
Smarty
Для работы с шаблонами получайте объект через статический метод:
use WeppsCore\Smarty;
$smarty = Smarty::getSmarty();
// Передать одну переменную
$smarty->assign('var', $value);
// Передать несколько переменных (массив)
$smarty->assign([
'products' => $products,
'paginator' => $paginator,
'totalCount' => $totalCount
]);
// Получить HTML без вывода (для вложенных шаблонов)
$html = $smarty->fetch('path/to/template.tpl');
// Можно использовать шаблоны из других расширений
$html = $smarty->fetch('packages/WeppsExtensions/Template/Paginator/Paginator.tpl');
Работа с данными
Класс Data
Класс Data - это высокоуровневая обёртка над PDO для работы с базой данных. Это не ORM, а удобный, безопасный и очевидный способ выполнения типовых операций с данными: выборка с автоматическими JOIN по схеме, управление файлами, пагинация, CRUD-операции.
use WeppsCore\Data;
use WeppsCore\Smarty;
$data = new Data('Products');
// Простая выборка (без автоматических JOIN)
$products = $data->fetchmini('IsActive=1 AND Price>1000', 20, $this->page, 'Priority DESC');
// Выборка с JOIN по схеме полей (автоматически подтягивает связи и файлы)
$products = $data->fetch('t.IsActive=1 AND t.Price>1000', 20, $this->page, 't.Priority DESC');
// Настройка запроса перед fetch()
$data->setFields('Id,Name,Price'); // Только нужные поля
$data->setJoin('LEFT JOIN Brands b ON b.Id = t.BrandId');
$products = $data->fetch('t.IsActive=1');
// Пагинация доступна после fetch/fetchmini
$paginator = $data->paginator; // ['current' => 1, 'pages' => [1,2,3], ...]
$totalCount = $data->count; // Общее количество
// Создание записи
$id = $data->add([
'Name' => 'Новый товар',
'Price' => 1500,
'IsActive' => 1
]);
// Обновление записи
$data->set($id, [
'Name' => 'Обновленное название',
'Price' => 2000
]);
// Удаление записи (и связанных файлов)
$data->remove($id);
// Передача в шаблон
$smarty = Smarty::getSmarty();
$smarty->assign('products', $products);
$smarty->assign('paginator', $data->paginator);
Прямые SQL-запросы
Для сложных запросов, которые нельзя реализовать через класс Data:
use WeppsCore\Connect;
// 1. Через Connect::$instance->fetch() - высокоуровневая обёртка с кэшированием
// Рекомендуется для SELECT запросов с JOIN - автоматически кэширует в memcached (если активно)
$products = Connect::$instance->fetch("
SELECT p.Id, p.Name, COUNT(o.Id) as OrdersCount
FROM Products p
LEFT JOIN Orders o ON p.Id = o.ProductId
WHERE p.IsActive = ? AND p.Price > ?
GROUP BY p.Id
HAVING OrdersCount > ?
", [1, 1000, 10]);
// 2. Через Connect::$db - прямой PDO (prepared statements)
// Используйте для INSERT/UPDATE/DELETE и когда не нужно кэширование
// INSERT
$sth = Connect::$db->prepare("
INSERT INTO Products (Name, Price, IsActive)
VALUES (?, ?, ?)
");
$sth->execute(['Товар', 1500, 1]);
$newId = Connect::$db->lastInsertId();
// UPDATE
$sth = Connect::$db->prepare("
UPDATE Products SET Price = ? WHERE Id = ?
");
$sth->execute([2000, 15]);
// DELETE
$sth = Connect::$db->prepare("DELETE FROM Products WHERE Id = ?");
$sth->execute([15]);
// SELECT через PDO (если не нужно кэширование)
$sth = Connect::$db->prepare("
SELECT * FROM Products WHERE Category = ? ORDER BY Priority DESC
");
$sth->execute(['Electronics']);
$products = $sth->fetchAll(\PDO::FETCH_ASSOC);
Важно:
- Класс
Data- для стандартных SELECT/INSERT/UPDATE/DELETE с автоматической работой с файлами Connect::$instance->fetch()- для сложных SELECT (GROUP BY, HAVING) с автоматическим кэшированием в memcachedConnect::$db- прямой PDO для prepared statements, когда не нужно кэширование или для модификации данных
Безопасность:
- Всегда используйте prepared statements (подготовленные запросы) с плейсхолдерами
?или именованными параметрами - Никогда не подставляйте пользовательские данные напрямую в SQL через конкатенацию строк
- Prepared statements автоматически защищают от SQL-инъекций, экранируя параметры на уровне драйвера БД
- Все три метода (
Data,Connect::$instance,Connect::$db) используют prepared statements и безопасны
// ❌ ОПАСНО - SQL-инъекция!
$id = $_GET['id'];
Connect::$db->query("SELECT * FROM Products WHERE Id = $id");
// ✅ БЕЗОПАСНО - prepared statement с параметрами
$id = $_GET['id'];
$sth = Connect::$db->prepare("SELECT * FROM Products WHERE Id = ?");
$sth->execute([$id]);
Frontend часть (JS)
MyExtension.js:
/**
* Инициализация расширения MyExtension
*
* Этот паттерн удобен тем, что:
* - Изолирует логику в отдельной функции
* - Легко тестировать и дебажить
* - Можно переиспользовать функцию в других местах
* - Избегает глобального загрязнения пространства имён
*
* Использование .off().on():
* - .off('event') снимает все старые обработчики события перед назначением новых
* - Предотвращает дублирование обработчиков при повторной инициализации
* - Обеспечивает безопасную переинициализацию расширения
*/
var readyMyExtensionInit = function () {
// jQuery доступен глобально в проекте через $
// Загрузка элементов через layoutWepps.request()
$('.load-items').off('click').on('click', function() {
const page = $(this).data('page') || 1;
layoutWepps.request({
url: '/ext/MyExtension/Request.php',
data: 'action=load-items&page=' + page,
obj: $('.items-container') // Результат вставляется в этот элемент
});
});
// Добавление элемента с обработкой результата
$('.add-item-form').off('submit').on('submit', function(e) {
e.preventDefault();
const name = $(this).find('[name="name"]').val();
layoutWepps.request({
url: '/ext/MyExtension/Request.php',
data: 'action=add-item&name=' + name
// Без параметра obj - результат обрабатывается автоматически
});
});
// Удаление элемента
$('.delete-btn').off('click').on('click', function() {
const $btn = $(this);
const id = $btn.data('id');
layoutWepps.request({
url: '/ext/MyExtension/Request.php',
data: 'action=delete-item&id=' + id
});
});
// Сохранение с указанием контейнера для результата
$('.save-item').off('click').on('click', function() {
const id = $(this).data('id');
const title = $('#item-title').val();
layoutWepps.request({
url: '/ext/MyExtension/Request.php',
data: 'action=save-item&id=' + id + '&title=' + title,
obj: $('#result-container') // Результат вставится в этот элемент
});
});
// Открытие модального окна с AJAX-содержимым
$('.open-modal-btn').off('click').on('click', function(e) {
e.preventDefault();
const id = $(this).data('id');
layoutWepps.modal({
size: 'medium', // small, medium, large
url: '/ext/MyExtension/Request.php',
data: 'action=item-details&id=' + id
});
});
// Модальное окно с существующим контентом
$('.show-info').off('click').on('click', function(e) {
e.preventDefault();
layoutWepps.modal({
size: 'small',
content: $('#info-block') // DOM-элемент для отображения
});
});
};
// Вызов инициализации при загрузке DOM
$(document).ready(readyMyExtensionInit);
Рекомендуемый паттерн структуры:
- Именование функции:
ready+ название расширения +Init(например,readyMyExtensionInit) - Изоляция логики: вся логика инициализации помещается в отдельную функцию
- Вызов через
$(document).ready(): обеспечивает выполнение после загрузки DOM - Переиспользование: функцию можно вызвать повторно при необходимости (например, после динамической подгрузки контента)
- Использование
.off().on(): предотвращает дублирование обработчиков событий при повторной инициализации
Важно:
- Всегда используйте
.off('event').on('event', handler)- это снимает старые обработчики перед назначением новых и предотвращает дублирование при повторной инициализации - Для динамически создаваемых элементов (которых нет в DOM на момент инициализации) используйте делегирование через
$(document).on('event', '.selector', handler)
API платформы layoutWepps:
// layoutWepps.request() - AJAX-запрос с автоматической обработкой
// Loader отображается автоматически во время выполнения запроса
layoutWepps.request({
url: '/ext/MyExtension/Request.php', // URL для запроса
data: 'action=load&id=1', // Данные (строка или объект)
obj: $('#target-element') // Опционально: элемент для вставки результата
});
// layoutWepps.modal() - открытие модального окна
layoutWepps.modal({
size: 'medium', // small, medium, large
url: '/ext/MyExtension/Request.php', // URL для загрузки содержимого
data: 'action=details&id=1', // Данные запроса
content: $('#existing-element') // Или готовый DOM-элемент
});
// layoutWepps.remove() - закрытие текущего модального окна
layoutWepps.remove();
Параметры layoutWepps.request():
url(обязательно) - адрес Request.php расширенияdata- данные запроса (строка форматаkey=value&key2=value2или объект)obj- jQuery-элемент для вставки результата HTML-ответа- Loader - отображается автоматически на время выполнения запроса
Параметры layoutWepps.modal():
size- размер модального окна:'small','medium','large'url+data- для загрузки содержимого через AJAXcontent- jQuery-элемент с готовым содержимым
Guard Clause паттерн
Используйте ранний return для валидации:
use WeppsCore\Users;
use WeppsCore\Connect;
use WeppsCore\Smarty;
public function request() {
// Проверка авторизации (getAuth вызывается один раз при инициализации приложения)
// Здесь только проверяем наличие данных пользователя
if (empty(Connect::$projectData['user'])) {
$smarty = Smarty::getSmarty();
$smarty->assign('message', 'Требуется авторизация');
$this->tpl = 'packages/WeppsExtensions/MyExtension/Unauthorized.tpl';
return;
}
$user = Connect::$projectData['user'];
// Проверка прав (1=Администратор, 2=Редактор, 3=Посетитель)
if ($user['UserPermissions'] > 1) {
$smarty = Smarty::getSmarty();
$smarty->assign('message', 'Недостаточно прав');
$this->tpl = 'packages/WeppsExtensions/MyExtension/Forbidden.tpl';
return;
}
// Основной код выполняется только если все проверки пройдены
$this->tpl = 'packages/WeppsExtensions/MyExtension/MyExtension.tpl';
}
Обработка AJAX запросов
Для обработки асинхронных HTTP/AJAX запросов создается файл Request.php в папке расширения.
Структура Request.php
Request.php - это отдельная точка входа для HTTP/AJAX запросов, которая:
- Загружает конфигурацию платформы через
configloader.php - Наследуется от класса
WeppsCore\Request - Обрабатывает различные действия (actions) через
switch - Может подключать CSS/JS и использовать шаблоны
- Возвращает HTML или JSON ответы
Пример Request.php
packages/WeppsExtensions/MyExtension/Request.php:
<?php
require_once '../../../configloader.php';
use WeppsCore\Smarty;
use WeppsCore\Request;
use WeppsCore\Exception;
class RequestMyExtension extends Request
{
public function request($action = "")
{
switch ($action) {
case 'load-items':
$this->loadItems();
break;
case 'add-item':
$this->addItem();
break;
case 'delete-item':
$this->deleteItem();
break;
default:
Exception::error404();
break;
}
}
/**
* Загрузка элементов
*/
private function loadItems()
{
// Установка шаблона для ответа
$this->tpl = 'RequestItems.tpl';
// Получение данных
$data = new Data('MyTable');
$items = $data->fetch('t.IsActive=1', 10, $this->get['page'] ?? 1);
// Передача данных в шаблон через метод assign()
$this->assign('items', $items);
$this->assign('paginator', $data->paginator);
}
/**
* Добавление элемента
*/
private function addItem()
{
if (empty($this->get['name'])) {
Exception::error(400);
}
$data = new Data('MyTable');
$id = $data->add([
'Name' => $this->get['name'],
'IsActive' => 1
]);
// JSON ответ
echo json_encode([
'status' => 200,
'id' => $id,
'message' => 'Элемент добавлен'
]);
exit();
}
/**
* Удаление элемента
*/
private function deleteItem()
{
if (empty($this->get['id'])) {
Exception::error(400);
}
$data = new Data('MyTable');
$data->remove($this->get['id']);
// JSON ответ
echo json_encode([
'status' => 200,
'message' => 'Элемент удален'
]);
exit();
}
}
// Инициализация и выполнение запроса
$request = new RequestMyExtension($_REQUEST);
$smarty->assign('get', $request->get);
$smarty->display($request->tpl);
Шаблон для AJAX-ответа
packages/WeppsExtensions/MyExtension/RequestItems.tpl:
{foreach $items as $item}
<div class="item" data-id="{$item.Id}">
<h3>{$item.Name}</h3>
<button class="delete-btn" data-id="{$item.Id}">Удалить</button>
</div>
{/foreach}
{if $paginator}
<div class="pagination">
{foreach $paginator.pages as $page}
<a href="#" data-page="{$page}">{$page}</a>
{/foreach}
</div>
{/if}
{* Вставка CSS/JS - обычно размещается в конце *}
{$get.cssjs}
Системные переменные в AJAX-шаблонах:
-
{$get}- объект класса Request с доступом к параметрам запроса и системным свойствам:{$get.action}- текущее действие (action из параметра запроса){$get.id},{$get.page}- параметры из GET/POST запроса{$get.cssjs}- переменная для вставки автоматически подключенных CSS/JS
-
{$get.cssjs}- вставляет теги<style>и<script>с содержимым файловRequestItems.tpl.cssиRequestItems.tpl.js. Рекомендуется размещать в конце шаблона для удобства.
{* Пример с использованием параметров запроса *}
<div class="items-page-{$get.page}">
{* Ваш контент *}
</div>
{* Вставка стилей и скриптов (обычно в конце) *}
{$get.cssjs}
Автоматическое подключение стилей и скриптов:
Если в папке расширения создать файлы с тем же именем, что и шаблон, но с суффиксами .css и .js:
RequestItems.tpl.css- стили для шаблонаRequestItems.tpl.js- скрипты для шаблона
То платформа автоматически подхватит и подключит эти файлы при рендере шаблона RequestItems.tpl. Явное подключение через $this->headers не требуется.
WeppsExtensions/MyExtension/
├── Request.php
├── RequestItems.tpl # Шаблон
├── RequestItems.tpl.css # Стили (подключаются автоматически)
└── RequestItems.tpl.js # Скрипты (подключаются автоматически)
Явное подключение стилей и скриптов в Request.php
Если автоматическое подключение через .tpl.css и .tpl.js не подходит, можно явно подключить CSS/JS файлы через $this->headers:
class RequestMyExtension extends Request
{
public function request($action = "")
{
// Явное подключение стилей и скриптов
$this->headers->css("/ext/MyExtension/RequestItems.{$this->rand}.css");
$this->headers->js("/ext/MyExtension/RequestItems.{$this->rand}.js");
switch ($action) {
case 'load-items':
$this->loadItems();
break;
// ...
}
}
}
Типы ответов
HTML ответ (через шаблон):
private function loadItems()
{
$this->tpl = 'RequestItems.tpl';
$this->assign('items', $items);
// Ответ отдается автоматически через $smarty->display($request->tpl)
}
JSON ответ:
private function addItem()
{
// Ваша логика
header('Content-Type: application/json');
echo json_encode([
'status' => 200,
'data' => $result
]);
exit(); // Важно! Прерываем выполнение для JSON
}
Прямой HTML ответ:
private function loadItems()
{
$html = '<div>Мой контент</div>';
echo $html;
exit();
}
Примеры расширений
Реальные примеры реализации расширений смотрите в папке packages/WeppsExtensions/:
- Products - каталог товаров с детальными страницами
- News - новости и статьи
- Gallery - галерея изображений
- Cart - корзина покупок
- Profile - личный кабинет пользователя
- Services - услуги
- Contacts - контактная информация
- Brands - бренды
Отладка
Режим debug
В config.php установите 'debug' => 1:
'Dev' => [
'debug' => 1,
]
Логирование
Для отладки используйте метод Utils::debug():
use WeppsCore\Utils;
// Вывод переменной в HTML-блоке (по умолчанию)
Utils::debug($data);
// Режимы вывода:
Utils::debug($data, 0); // HTML-блок без закрытия соединения (по умолчанию)
Utils::debug($data, 1); // HTML-блок с закрытием соединения
Utils::debug($data, 3); // Сырой текст на экран
Utils::debug($data, 31); // Сырой текст и закрытие соединения
// Запись в файл:
Utils::debug($data, 2); // Перезапись файла debug.conf
Utils::debug($data, 21); // Перезапись и закрытие соединения
Utils::debug($data, 22); // Дописать в конец файла
// Запись в кастомный файл (по умолчанию пишется в /debug.conf):
Utils::debug($data, 2, __DIR__ . '/my-debug.log');
// Альтернативные способы:
error_log("Debug info: " . print_r($data, true));
echo '<script>console.log(' . json_encode($data) . ');</script>';
Возможности Utils::debug():
- Автоматическое добавление времени и места вызова (файл и строка) при
'debug' => 1в конфиге - Форматированный вывод в зелёном блоке с прокруткой
- Запись в файл с возможностью перезаписи или дополнения
- Опциональное закрытие соединения после вывода
Smarty debug
// В шаблоне
{debug}
Лучшие практики
- Используйте prepared statements для всех SQL-запросов
- Валидируйте входные данные перед использованием
- Применяйте Guard Clause для ранних выходов
- Кэшируйте дорогие операции (DB запросы, API вызовы)
- Используйте транзакции для связанных операций с БД
- Создавайте отдельные методы для логики вместо большого
request() - Документируйте код через PHPDoc комментарии