Расширение конфигурации таблиц в Wepps Platform: Action-обработчики

Система управления списками данных в Wepps Platform предоставляет мощный механизм расширения стандартного функционала через Action-обработчики. Это специальные классы, которые автоматически выполняются при различных операциях со списками (просмотр, создание, изменение, удаление), позволяя добавить кастомную логику без изменения ядра системы.

31.01.2026

Расширение конфигурации таблиц в Wepps Platform: Action-обработчики

Введение

Система управления списками данных в Wepps Platform предоставляет мощный механизм расширения стандартного функционала через Action-обработчики. Это специальные классы, которые автоматически выполняются при различных операциях со списками (просмотр, создание, изменение, удаление), позволяя добавить кастомную логику без изменения ядра системы.

Зачем использовать Action-обработчики?

Проблемы, которые решают Action-обработчики

  1. Ограниченность стандартного функционала - базовые операции CRUD не всегда достаточны для сложной бизнес-логики
  2. Необходимость дополнительной обработки данных - автоматическое заполнение полей, валидация, связанные операции
  3. Производительность списков - оптимизация выборки данных через ограничение отображаемых полей
  4. Каскадные операции - удаление связанных записей, создание дополнительных сущностей

Когда использовать

  • Создание связанных файлов или записей при сохранении элемента
  • Фильтрация списка по сложным условиям
  • Добавление дополнительных вкладок или полей в форму редактирования
  • Каскадное удаление связанных данных
  • Оптимизация загрузки больших списков

Основные концепции

Таблица s_Config

Конфигурация таблиц данных хранится в системной таблице s_Config. Помимо базовых настроек отображения, она содержит специальные поля для подключения Action-обработчиков:

Поле Тип Назначение
ItemsFields VARCHAR(128) Перечисление полей для отображения в списке (через запятую)
ActionShow VARCHAR(255) Класс-обработчик для списка элементов
ActionShowId VARCHAR(255) Класс-обработчик для просмотра/редактирования элемента
ActionModify VARCHAR(255) Класс-обработчик после сохранения элемента
ActionDrop VARCHAR(255) Класс-обработчик перед удалением элемента

Структура Action-класса

Все Action-обработчики должны соответствовать единому паттерну:

<?php
namespace WeppsAdmin\Lists\Actions;

use WeppsCore\Request;
use WeppsCore\Connect;

class MyAction extends Request {
    public $noclose = 1;
    public $listSettings = [];
    public $listScheme = [];
    public $element = [];

    public function request($action = "") {
        // Получение переданных данных
        $this->listSettings = $this->get['listSettings'];
        $this->listScheme = $this->get['listScheme'];
        $this->element = $this->get['element'];

        // Guard clause - ранний выход при несоответствии условий
        if ($this->listSettings['TableName'] != 'Products') {
            return;
        }

        // Основная логика обработчика
    }
}

Важные особенности:

  • Класс находится в packages/WeppsAdmin/Lists/Actions/
  • Наследуется от WeppsCore\Request
  • Свойство $noclose = 1 предотвращает автоматическое завершение запроса
  • Использование Guard Clause паттерна для ранних проверок

Оптимизация производительности: ItemsFields

Ограничение выборки полей

Поле ItemsFields позволяет значительно ускорить загрузку списков, ограничивая SELECT-запрос только необходимыми столбцами.

Пример в админке:

Откройте список s_Config → найдите нужную таблицу → в поле ItemsFields укажите:

Name,Alias,Priority,IsHidden

Что происходит в коде:

// WeppsAdmin/Lists/Lists.php
$listObj = new Data($tableName);
if (!empty($listSettings['ItemsFields'])) {
    $listObj->setFields($listSettings['ItemsFields']);
}
$listScheme = $listObj->getScheme();

Результат:

  • Вместо SELECT * будет SELECT Name,Alias,Priority,IsHidden
  • Уменьшается объем передаваемых данных
  • Ускоряется рендеринг таблицы в админке
  • Снижается нагрузка на MySQL

Когда использовать:

  • Таблица содержит более 10 полей
  • Есть тяжелые поля типа TEXT, LONGTEXT
  • Список загружается медленно (более 1 секунды)
  • Список имеет несколько полей типа file

ActionShow: Кастомизация списка элементов

Назначение

Класс ActionShow выполняется при загрузке списка элементов и позволяет:

  • Добавить условия фильтрации (WHERE)
  • Изменить схему отображаемых полей
  • Добавить вычисляемые поля

Базовый пример

Настройка в админке:

s_ConfigProductsActionShow = ViewList.php

Класс-обработчик:

<?php
namespace WeppsAdmin\Lists\Actions;

use WeppsCore\Request;

class ViewList extends Request {
    public $noclose = 1;
    public $condition = "";
    public $scheme = [];

    public function request($action = "") {
        $this->scheme = $this->get['listScheme'];

        // Добавляем условие фильтрации
        $this->condition = "t.IsActive = 1 AND t.Price > 0";
    }
}

Результат: В списке будут показаны только активные товары с указанной ценой.

Продвинутый пример: Фильтрация по категориям

<?php
namespace WeppsAdmin\Lists\Actions;

use WeppsCore\Request;
use WeppsCore\Connect;

class ViewListProducts extends Request {
    public $noclose = 1;
    public $condition = "";
    public $fields = "";
    public $scheme = [];

    public function request($action = "") {
        $this->scheme = $this->get['listScheme'];

        // Добавляем JOIN для вывода названия категории
        $this->fields = "(SELECT c.Name FROM Categories c WHERE c.Id = t.CategoryId) as CategoryName";

        // Фильтр по GET-параметру
        if (isset($_GET['category']) && (int)$_GET['category'] > 0) {
            $categoryId = (int)$_GET['category'];
            $this->condition = "t.CategoryId = {$categoryId}";
        }

        // Фильтр по складским остаткам
        if (isset($_GET['instock'])) {
            $this->condition .= ($this->condition ? " AND " : "") . "t.Stock > 0";
        }

        // Добавляем виртуальное поле в схему
        $this->scheme['CategoryName'] = [
            'Field' => 'CategoryName',
            'Name' => 'Категория',
            'Type' => 'text'
        ];
    }
}

Возможности:

  • $this->condition - WHERE условия (без префикса WHERE)
  • $this->fields - дополнительные поля для SELECT
  • $this->scheme - изменение схемы полей для отображения

ActionShowId: Кастомизация формы элемента

Назначение

Класс ActionShowId выполняется при открытии формы редактирования элемента и позволяет:

  • Изменить данные элемента перед отображением
  • Добавить дополнительные вкладки
  • Подключить кастомные CSS/JS файлы
  • Изменить схему полей

Базовый пример: Изменение данных элемента

Настройка в админке:

s_Configs_NavigatorActionShowId = ViewItemDirectories.php

Класс-обработчик:

<?php
namespace WeppsAdmin\Lists\Actions;

use WeppsCore\Request;

class ViewItemDirectories extends Request {
    public $noclose = 1;
    public $scheme = [];
    public $element = [];

    public function request($action = "") {
        $element = $this->get['element'];
        $this->element = &$this->get['element'];
        $this->scheme = $this->get['listScheme'];

        // Обработка режима "Добавить подраздел"
        if (strstr($_GET['weppsurl'], '/addNavigator/')) {
            foreach ($this->scheme as $key => $value) {
                $this->element[$key] = "";
            }
            $this->element['Id'] = 'add'; 
            $this->element['Name'] = 'Новый раздел'; 
            $this->element['ParentDir_SelectChecked'] = $element['Id']; 
            $this->element['Template_SelectChecked'] = ""; 
            $this->element['Extension_SelectChecked'] = "";
        }
    }
}

Результат: При добавлении подраздела автоматически подставляется родительский раздел.

Продвинутый пример: Добавление вкладок

<?php
namespace WeppsAdmin\Lists\Actions;

use WeppsCore\Request;
use WeppsCore\Connect;
use WeppsCore\Smarty;

class ViewItemProducts extends Request {
    public $noclose = 1;
    public $scheme = [];
    public $element = [];
    public $settings = [];
    public $headers;

    public function request($action = "") {
        $this->element = &$this->get['element'];
        $this->scheme = &$this->get['listScheme'];
        $this->settings = &$this->get['listSettings'];
        $this->headers = &$this->get['headers'];

        // Подключаем кастомный JS
        $this->headers->js("/packages/WeppsAdmin/Lists/Actions/ViewItemProducts.{$this->headers::$rand}.js");

        // Получаем дополнительные данные
        $sql = "SELECT * FROM ProductsVariations WHERE ProductId = ?";
        $variations = Connect::$instance->fetch($sql, [$this->element['Id']]);

        // Рендерим шаблон вкладки
        $smarty = Smarty::getSmarty();
        $smarty->assign('variations', $variations);
        $smarty->assign('productId', $this->element['Id']);
        $smarty->assign('itemGroup', 'Variations');

        $tplVariations = $smarty->fetch(
            Connect::$projectDev['root'] . '/packages/WeppsAdmin/Lists/Actions/ViewItemProductsVariations.tpl'
        );

        // Добавляем новую вкладку
        $this->settings['ActionShowIdAddons'] = [
            [
                "title" => "Вариации товара",
                "group" => "Variations",
                "tpl" => $tplVariations
            ]
        ];
    }
}

Возможности:

  • $this->element - данные текущего элемента (по ссылке)
  • $this->scheme - схема полей (по ссылке)
  • $this->settings['ActionShowIdAddons'] - массив дополнительных вкладок
  • $this->headers - объект для подключения CSS/JS

Структура вкладки:

  • title - название вкладки в интерфейсе
  • group - уникальный идентификатор группы
  • tpl - HTML-контент вкладки

ActionModify: Постобработка после сохранения

Назначение

Класс ActionModify выполняется сразу после сохранения элемента в БД и позволяет:

  • Выполнить дополнительные операции с данными
  • Создать связанные записи
  • Обновить кэш или индексы
  • Сгенерировать файлы или структуры

Базовый пример: Автогенерация URL

Настройка в админке:

s_Configs_NavigatorActionModify = SaveItemDirectories.php

Класс-обработчик:

<?php
namespace WeppsAdmin\Lists\Actions;

use WeppsCore\Request;
use WeppsCore\Connect;
use WeppsCore\TextTransforms;

class SaveItemDirectories extends Request {
    public $noclose = 1;
    public $scheme = [];
    public $listSettings = [];
    public $element = [];

    public function request($action = "") {
        $this->scheme = $this->get['listScheme'];
        $this->listSettings = $this->get['listSettings'];
        $this->element = $this->get['element'];

        // Guard clause
        if ($this->listSettings['TableName'] != 's_Navigator') {
            return;
        }

        // Генерация URL из названия, если он не указан
        if ($this->element['Url'] == '') {
            $url = "/" . TextTransforms::translit($this->element['Name'], 2) . "/";
            $sql = "UPDATE s_Navigator SET Url = ? WHERE Id = ?";
            Connect::$instance->query($sql, [$url, $this->element['Id']]);
            $this->element['Url'] = $url;
        }
    }
}

Результат: При создании нового раздела навигации без URL, он автоматически генерируется из транслитерации названия.

Продвинутый пример: Создание таблицы

<?php
namespace WeppsAdmin\Lists\Actions;

use WeppsCore\Request;
use WeppsCore\Connect;
use WeppsAdmin\Lists\Lists;
use WeppsAdmin\Admin\Admin;

class SaveItemConfig extends Request {
    public $noclose = 1;
    public $scheme = [];
    public $listSettings = [];
    public $element = [];

    public function request($action = "") {
        $this->scheme = $this->get['listScheme'];
        $this->listSettings = $this->get['listSettings'];
        $this->element = $this->get['element'];

        if ($this->listSettings['TableName'] != 's_Config') {
            return;
        }

        // Создаем новую таблицу со стандартной структурой
        $str = Lists::addList($this->element['TableName']);
        if ($str != "") {
            Connect::$db->exec($str);

            // Добавляем права доступа для администратора
            $perm = Admin::getPermissions(1, ['list' => 's_Config']);
            if ($perm['status'] == 1) {
                $sql = "UPDATE s_Permissions 
                        SET TableName = CONCAT(TableName, ',', ?) 
                        WHERE Id = 1";
                Connect::$instance->query($sql, [$this->element['TableName']]);
            }
        }
    }
}

Результат: При добавлении новой таблицы в s_Config автоматически создается физическая таблица в БД с базовыми полями.

Кейс: Создание файлов шаблонов

<?php
namespace WeppsAdmin\Lists\Actions;

use WeppsCore\Request;
use WeppsCore\Connect;

class SaveItemTemplates extends Request {
    public $noclose = 1;
    public $scheme = [];
    public $listSettings = [];
    public $element = [];

    public function request($action = "") {
        $this->scheme = $this->get['listScheme'];
        $this->listSettings = $this->get['listSettings'];
        $this->element = $this->get['element'];

        if ($this->listSettings['TableName'] != 's_Templates') {
            return;
        }

        $this->copyTpl($this->element['FileTemplate']);
    }

    private function copyTpl($tpl) {
        $tmp = substr($tpl, 0, -4);
        $root = Connect::$projectDev['root'] . "/packages/WeppsExtensions/Template/";

        // Создаем .tpl файл
        if (!is_file("{$root}{$tmp}.tpl")) {
            copy("{$root}Template.tpl", "{$root}{$tmp}.tpl");
        }

        // Создаем .css файл
        if (!is_file("{$root}{$tmp}.css")) {
            copy("{$root}Template.css", "{$root}{$tmp}.css");
        }

        // Создаем .js файл
        if (!is_file("{$root}{$tmp}.js")) {
            copy("{$root}Template.js", "{$root}{$tmp}.js");
        }
    }
}

Результат: При создании нового шаблона автоматически генерируются файлы .tpl, .css, .js на основе базовых шаблонов.

ActionDrop: Предобработка перед удалением

Назначение

Класс ActionDrop выполняется перед удалением элемента из БД и позволяет:

  • Удалить связанные записи (каскадное удаление)
  • Удалить физические файлы
  • Выполнить проверки перед удалением
  • Отменить удаление при определенных условиях

Базовый пример: Удаление таблицы

Настройка в админке:

s_Configs_ConfigActionDrop = RemoveItemConfig.php

Класс-обработчик:

<?php
namespace WeppsAdmin\Lists\Actions;

use WeppsCore\Request;
use WeppsCore\Connect;

class RemoveItemConfig extends Request {
    public $noclose = 1;
    public $listSettings = [];
    public $id;
    public $element = [];

    public function request($action = "") {
        $this->listSettings = $this->get['listSettings'];
        $this->id = (int) $this->get['id'];

        if ($this->listSettings['TableName'] != 's_Config') {
            return;
        }

        // Получаем данные удаляемого элемента
        $sql = "SELECT * FROM s_Config WHERE Id = ?";
        $res = Connect::$instance->fetch($sql, [$this->id]); 

        if (!isset($res[0]['Id'])) {
            return;
        }

        $this->element = $res[0];

        // Удаляем настройки полей таблицы
        $sql = "DELETE FROM s_ConfigFields WHERE TableName = ?;\n";
        $sql .= "DROP TABLE IF EXISTS {$this->element['TableName']};\n";
        Connect::$db->exec($sql);
    }
}

Результат: При удалении записи из s_Config автоматически удаляется физическая таблица и все связанные настройки полей.

Продвинутый пример: Каскадное удаление

<?php
namespace WeppsAdmin\Lists\Actions;

use WeppsCore\Request;
use WeppsCore\Connect;
use WeppsCore\NavigatorData;

class RemoveItemDirectories extends Request {
    public $noclose = 1;
    public $listSettings = [];
    private $id;
    public $element = [];

    public function request($action = "") {
        $this->listSettings = $this->get['listSettings'];
        $this->id = (int) $this->get['id'];

        if ($this->listSettings['TableName'] != 's_Navigator') {
            return;
        }

        // Защита от удаления корневого раздела
        if ($this->id == 1) {
            Connect::$instance->close();
            return;
        }

        // Получаем все дочерние разделы
        $nav = new NavigatorData("s_Navigator");
        $child = $nav->getRChild($this->id);

        if (count($child) != 0) {
            // Удаляем текущий раздел и все дочерние
            $str = "0," . implode(",", $child);
            $sql = "DELETE FROM s_Navigator WHERE Id IN ({$str})";
            Connect::$db->query($sql);
        }
    }
}

Результат: При удалении раздела навигации автоматически удаляются все вложенные подразделы.

Кейс: Удаление поля из таблицы

<?php
namespace WeppsAdmin\Lists\Actions;

use WeppsCore\Request;
use WeppsCore\Connect;

class RemoveItemConfigFields extends Request {
    public $noclose = 1;
    public $listSettings = [];
    private $id;
    public $element = [];

    public function request($action = "") {
        $this->listSettings = $this->get['listSettings'];
        $this->id = (int) $this->get['id'];

        if ($this->listSettings['TableName'] != 's_ConfigFields') {
            return;
        }

        $sql = "SELECT * FROM s_ConfigFields WHERE Id = ?";
        $res = Connect::$instance->fetch($sql, [$this->id]); 

        if (!isset($res[0]['Id'])) {
            return;
        }

        $this->element = $res[0];

        // Удаляем физический столбец из таблицы
        $sql = "ALTER TABLE {$this->element['TableName']} 
                DROP COLUMN {$this->element['Field']}";
        Connect::$instance->query($sql);
    }
}

Результат: При удалении записи из s_ConfigFields автоматически удаляется соответствующий столбец из физической таблицы.

Практические примеры

Пример 1: Автоматическое заполнение артикула

Задача: При создании товара автоматически генерировать артикул на основе ID.

<?php
namespace WeppsAdmin\Lists\Actions;

use WeppsCore\Request;
use WeppsCore\Connect;

class SaveItemProducts extends Request {
    public $noclose = 1;
    public $listSettings = [];
    public $element = [];

    public function request($action = "") {
        $this->listSettings = $this->get['listSettings'];
        $this->element = $this->get['element'];

        if ($this->listSettings['TableName'] != 'Products') {
            return;
        }

        // Генерация артикула, если не указан
        if (empty($this->element['SKU'])) {
            $sku = 'PROD-' . str_pad($this->element['Id'], 6, '0', STR_PAD_LEFT);
            $sql = "UPDATE Products SET SKU = ? WHERE Id = ?";
            Connect::$instance->query($sql, [$sku, $this->element['Id']]);
        }
    }
}

Настройка: s_ConfigProductsActionModify = SaveItemProducts.php

Пример 2: Фильтр списка по статусу

Задача: Показывать в списке заказов только активные заказы, с возможностью переключения.

<?php
namespace WeppsAdmin\Lists\Actions;

use WeppsCore\Request;

class ViewListOrders extends Request {
    public $noclose = 1;
    public $condition = "";

    public function request($action = "") {
        $status = $_GET['status'] ?? 'active';

        switch ($status) {
            case 'active':
                $this->condition = "t.Status IN ('new', 'processing', 'shipping')";
                break;
            case 'completed':
                $this->condition = "t.Status = 'completed'";
                break;
            case 'cancelled':
                $this->condition = "t.Status = 'cancelled'";
                break;
            default:
                $this->condition = "1=1";
        }
    }
}

Настройка: s_ConfigOrdersActionShow = ViewListOrders.php

Пример 3: Добавление вычисляемого поля

Задача: Показывать общую стоимость товаров в списке (цена × количество).

<?php
namespace WeppsAdmin\Lists\Actions;

use WeppsCore\Request;

class ViewListProducts extends Request {
    public $noclose = 1;
    public $fields = "";
    public $scheme = [];

    public function request($action = "") {
        $this->scheme = $this->get['listScheme'];

        // Добавляем вычисляемое поле
        $this->fields = "(t.Price * t.Stock) as TotalValue";

        // Регистрируем поле в схеме
        $this->scheme['TotalValue'] = [
            'Field' => 'TotalValue',
            'Name' => 'Общая стоимость',
            'Type' => 'digit'
        ];
    }
}

Настройка: s_ConfigProductsActionShow = ViewListProducts.php

Пример 4: Валидация перед удалением

Задача: Запретить удаление категории, если в ней есть товары.

<?php
namespace WeppsAdmin\Lists\Actions;

use WeppsCore\Request;
use WeppsCore\Connect;
use WeppsCore\Utils;

class RemoveItemCategories extends Request {
    public $noclose = 1;
    public $listSettings = [];
    private $id;

    public function request($action = "") {
        $this->listSettings = $this->get['listSettings'];
        $this->id = (int) $this->get['id'];

        if ($this->listSettings['TableName'] != 'Categories') {
            return;
        }

        // Проверяем наличие товаров в категории
        $sql = "SELECT COUNT(*) as cnt FROM Products WHERE CategoryId = ?";
        $res = Connect::$instance->fetch($sql, [$this->id]);

        if ($res[0]['cnt'] > 0) {
            // Прерываем выполнение и показываем ошибку
            Utils::debug("Невозможно удалить категорию: в ней есть товары ({$res[0]['cnt']} шт.)", 1);
            Connect::$instance->close();
        }
    }
}

Настройка: s_ConfigCategoriesActionDrop = RemoveItemCategories.php

Передаваемые данные в Action-классы

Для ActionShow (список)

$this->get = [
    'listSettings' => [...],  // Настройки из s_Config
    'listScheme' => [...]     // Схема полей из s_ConfigFields
];

Возвращаемые свойства:

  • $this->condition - WHERE условия
  • $this->fields - дополнительные поля для SELECT
  • $this->scheme - измененная схема полей

Для ActionShowId (просмотр элемента)

$this->get = [
    'listSettings' => &[...],  // Настройки из s_Config (по ссылке)
    'listScheme' => &[...],    // Схема полей (по ссылке)
    'element' => &[...],       // Данные элемента (по ссылке)
    'headers' => &$headers     // Объект TemplateHeaders (по ссылке)
];

Возможности изменения:

  • $this->settings['ActionShowIdAddons'] - добавление вкладок
  • $this->element - изменение данных элемента
  • $this->scheme - изменение схемы полей
  • $this->headers->js() / $this->headers->css() - подключение файлов

Для ActionModify (сохранение)

$this->get = [
    'listSettings' => [...],  // Настройки из s_Config
    'listScheme' => [...],    // Схема полей
    'element' => [...]        // Сохраненные данные элемента
];

Доступ к данным:

  • $this->element['Id'] - ID сохраненного элемента
  • $this->element['Name'] - значения всех полей
  • $this->listSettings['TableName'] - имя таблицы

Для ActionDrop (удаление)

$this->get = [
    'listSettings' => [...],  // Настройки из s_Config
    'id' => 123               // ID удаляемого элемента
];

Примечание: Данные элемента нужно получать дополнительным запросом.

Паттерны и лучшие практики

1. Guard Clause (Ранний выход)

Используйте ранний return для проверки условий:

public function request($action = "") {
    // Guard clauses
    if ($this->listSettings['TableName'] != 'Products') {
        return;
    }

    if (empty($this->element['Id'])) {
        return;
    }

    // Основная логика только для нужной таблицы
    // ...
}

Преимущества:

  • Улучшенная читаемость кода
  • Меньше вложенности
  • Явные условия выполнения

2. Передача по ссылке

В ActionShowId данные передаются по ссылке - изменения применяются автоматически:

public function request($action = "") {
    // Используем ссылки
    $this->element = &$this->get['element'];
    $this->scheme = &$this->get['listScheme'];

    // Изменения применяются автоматически
    $this->element['CustomField'] = 'New Value';
}

3. Безопасность SQL-запросов

Всегда используйте подготовленные запросы:

// НЕПРАВИЛЬНО - SQL-инъекция!
$sql = "SELECT * FROM Products WHERE Id = {$this->element['Id']}";

// ПРАВИЛЬНО - безопасный запрос
$sql = "SELECT * FROM Products WHERE Id = ?";
$res = Connect::$instance->fetch($sql, [$this->element['Id']]);

4. Использование Connect::$instance

Глобальный экземпляр для работы с БД:

// Выборка данных
$sql = "SELECT * FROM table WHERE id = ?";
$result = Connect::$instance->fetch($sql, [$id]);

// Один запрос
$sql = "UPDATE table SET field = ? WHERE id = ?";
Connect::$instance->query($sql, [$value, $id]);

// Несколько запросов
$sql = "DELETE FROM table1 WHERE id = ?;\n";
$sql .= "DELETE FROM table2 WHERE ref_id = ?;\n";
Connect::$db->exec($sql);

5. Обработка ошибок

Используйте Utils::debug() для вывода ошибок:

use WeppsCore\Utils;

if (!$validationPassed) {
    Utils::debug("Ошибка валидации: некорректные данные", 1);
    Connect::$instance->close();
}

Диагностика и отладка

Проверка выполнения Action-класса

Добавьте временный вывод в начало метода:

public function request($action = "") {
    error_log("Action executed: " . __CLASS__);
    // ...
}

Просмотр передаваемых данных

public function request($action = "") {
    file_put_contents(
        Connect::$projectDev['root'] . '/debug.log',
        print_r($this->get, true),
        FILE_APPEND
    );
}

Проверка SQL-запросов

$sql = "SELECT * FROM Products WHERE CategoryId = ?";
error_log("SQL: " . $sql);
error_log("Params: " . print_r([$categoryId], true));
$result = Connect::$instance->fetch($sql, [$categoryId]);

Частые ошибки

1. Отсутствие Guard Clause

// НЕПРАВИЛЬНО - выполняется для всех таблиц
public function request($action = "") {
    $sql = "UPDATE Products SET ...";  // Ошибка, если не Products
}

// ПРАВИЛЬНО - проверка таблицы
public function request($action = "") {
    if ($this->listSettings['TableName'] != 'Products') {
        return;
    }
    $sql = "UPDATE Products SET ...";
}

2. Забыли $noclose = 1

// НЕПРАВИЛЬНО - запрос завершится преждевременно
class MyAction extends Request {
    public function request($action = "") {
        // ...
    }
}

// ПРАВИЛЬНО
class MyAction extends Request {
    public $noclose = 1;
    public function request($action = "") {
        // ...
    }
}

3. Неправильное имя класса

// Файл: SaveItemProducts.php
// НЕПРАВИЛЬНО - имя не совпадает с файлом
class SaveProducts extends Request { }

// ПРАВИЛЬНО
class SaveItemProducts extends Request { }

4. SQL-инъекции

// НЕПРАВИЛЬНО - опасно!
$sql = "DELETE FROM {$this->element['TableName']} WHERE Id = {$id}";

// ПРАВИЛЬНО - использовать только для системных таблиц с валидацией
if (!preg_match('/^[a-zA-Z_]+$/', $this->element['TableName'])) {
    return;
}

Резюме

Action-обработчики в Wepps Platform предоставляют мощный механизм расширения стандартного функционала административной панели без изменения ядра системы.

Ключевые возможности:

Поле Когда выполняется Основное применение
ItemsFields При загрузке списка Оптимизация выборки данных
ActionShow При отображении списка Фильтрация, добавление полей
ActionShowId При открытии элемента Добавление вкладок, изменение данных
ActionModify После сохранения Постобработка, создание связей
ActionDrop Перед удалением Каскадное удаление, валидация

Основные принципы:

  • Один класс = одна ответственность
  • Guard Clause для ранних проверок
  • Безопасность через подготовленные запросы
  • Передача по ссылке в ActionShowId
  • Прозрачная интеграция без изменения ядра

Когда использовать:

  • ✅ Нужна кастомная логика для конкретной таблицы
  • ✅ Требуется автоматизация рутинных операций
  • ✅ Необходимо расширить интерфейс редактирования
  • ✅ Нужно оптимизировать производительность списков

Когда не использовать:

  • ❌ Простые операции, которые можно сделать через схему полей
  • ❌ Логика, общая для всех таблиц (лучше изменить ядро)
  • ❌ Сложная бизнес-логика (создайте отдельное расширение)

Как фреймворк: Гибкость разработки

Полный контроль над кодом, архитектурой и расширениями для сложных проектов

wapps framework

Как CMS: Простота управления

Интуитивная админ-панель для редакторов контента без программирования

wapps cms

Как платформа: Готовые решения

Быстрый старт проектов с возможностью глубокой кастомизации под любые задачи

wapps platform
T
Очереди задач (Tasks Queue) в Wepps Platform: Асинхронная обработка задач

Система очередей задач (Tasks Queue) в Wepps Platform — это механизм для отложенной асинхронной обработки тяжелых или длительных операций. Задачи регистрируются в базе данных и обрабатываются отдельным процессом, что позволяет не блокировать основной поток выполнения приложения и обеспечивает надежную обработку даже при сбоях.

29.01.2026
P
Панели и Блоки: техническое руководство для разработчиков

Эта статья описывает архитектуру системы «Панели и Блоки» на техническом уровне: структуру таблиц, поля, шаблоны и возможности расширения.

Целевая аудитория: разработчики, которые настраивают и расширяют функционал платформы.

26.01.2026
P
Режим редактирования: практическое руководство по работе с Панелями и Блоками

Представьте ситуацию: разработчик создал страницу с несколькими панелями. В каждой панели уже есть блоки с контентом. Ваша задача — обновить информацию, изменить порядок или добавить новые элементы.

С чего начать?

24.01.2026

☝️ Будьте в курсе: полезные статьи, новости проекта и практические советы по работе с платформой Wepps.