Создание плагина
Создание плагина
Пройдём путь от пустой папки до рабочего плагина с маршрутом, контроллером и пунктом в админке. За основу удобно держать перед глазами plugins/leads/.
Быстрый старт: пустой плагин
Чтобы не начинать с нуля, скачайте минимальный рабочий плагин:
Внутри: manifest.json, plugin.php (публичный маршрут /starter, страница админки /admin/starter, пункт меню, миграция), контроллеры, шаблон админки, языки и docs/. Установите через Плагины → Установить из zip и нажмите «Включить» — либо распакуйте папку starter/ в plugins/. Затем переименуйте starter в свой slug.
Slug плагина не должен содержать дефис: из него строится PHP-namespace (Celena\Plugin\<Slug>), а дефис в namespace недопустим. Используйтеmypluginилиmy_plugin.
Ниже — как всё устроено, чтобы собрать своё с нуля.
С чего начать
- Придумайте
slug(латиница, цифры, дефис), напримерreviews. - Создайте папку
plugins/reviews/и в ней —manifest.jsonиplugin.php.
manifest.json
{
"slug": "reviews",
"name": "Отзывы",
"version": "1.0.0",
"author": "Вы",
"description": "Отзывы клиентов: приём с фронта и модерация в админке.",
"requires": { "php": ">=8.3", "celena": ">=1.0" },
"migrations": "migrations/"
}
plugin.php — точка входа
plugin.php подключается при загрузке активного плагина. Здесь регистрируют маршруты, хуки и теги. Контейнер доступен через Kernel::container().
<?php
declare(strict_types=1);
use Celena\Core\Http\Router;
use Celena\Core\Kernel;
use Celena\Core\Database\Connection;
use Celena\Core\Plugin\Hook;
use Celena\Plugin\Reviews\Controllers\ReviewsController;
use Celena\Plugin\Reviews\Admin\ReviewsAdminController;
$container = Kernel::container();
// 1. Идемпотентный прогон миграций (если есть).
try {
$dir = __DIR__ . '/migrations';
if (is_dir($dir)) {
(new \Celena\Core\Database\MigrationRunner(
$container->make(Connection::class),
$container->make(\Celena\Core\Logger::class),
))->run($dir);
}
} catch (\Throwable) {}
// 2. Маршруты.
$router = $container->make(Router::class);
$router->post('/api/reviews/submit', ReviewsController::class . '@submit'); // публичный
$router->get('/admin/reviews', ReviewsAdminController::class . '@index'); // админка
// 3. Пункт в боковом меню админки.
Hook::addFilter('admin.sidebar', function (array $items): array {
$icon = new \Celena\Core\Template\Tags\IconTag();
$items[] = [
'svg' => $icon(['name' => 'star', 'size' => 22, 'class' => 'sb-ico']),
'label' => 'Отзывы',
'url' => '/admin/reviews',
'slug' => 'reviews',
];
return $items;
});
Маршруты/admin/регистрируются БЕЗ общего middleware — авторизацию проверяет сам контроллер (см. ниже). Публичные/api/доступны всем.
Контроллеры
PSR-4: класс Celena\Plugin\Reviews\Admin\ReviewsAdminController лежит в plugins/reviews/src/Admin/ReviewsAdminController.php.
Админский контроллер
Наследует Celena\Module\AdminBase\AdminController — он даёт view(), redirect(), доступ к $this->auth, $this->options, и общий layout админки:
<?php
declare(strict_types=1);
namespace Celena\Plugin\Reviews\Admin;
use Celena\Core\Database\Connection;
use Celena\Core\Http\Request;
use Celena\Core\Http\Response;
use Celena\Core\Template\Engine;
use Celena\Core\Security\Auth;
use Celena\Module\Settings\Options;
use Celena\Module\AdminBase\AdminController;
final class ReviewsAdminController extends AdminController
{
public function __construct(Engine $engine, Auth $auth, Options $options, private readonly Connection $conn)
{
parent::__construct($engine, $auth, $options);
}
public function index(Request $request): Response
{
if ($this->auth->user() === null) {
return Response::redirect('/admin/login');
}
$items = $this->conn->builder('reviews')->orderBy('created_at', 'desc')->get();
return $this->view('plugins/reviews/admin/index', ['items' => $items], 'reviews', 'Отзывы');
}
}
$this->view($template, $vars, $activeMenu, $title) рендерит шаблон в теме админки с боковым меню, уведомлениями и текущим пользователем.
Публичный контроллер
Фронт-контроллер не наследует AdminController; зависимости приходят через конструктор (авто-внедрение):
namespace Celena\Plugin\Reviews\Controllers;
use Celena\Core\Database\Connection;
use Celena\Core\Http\Request;
use Celena\Core\Http\Response;
use Celena\Core\Security\Csrf;
final class ReviewsController
{
public function __construct(private readonly Connection $conn) {}
public function submit(Request $request): Response
{
if (!Csrf::validate((string) $request->input('_csrf'))) {
return Response::json(['error' => 'csrf'], 419);
}
$this->conn->builder('reviews')->insert([
'author' => \Celena\Core\Security\Filter::string((string) $request->input('author')),
'text' => \Celena\Core\Security\Filter::string((string) $request->input('text')),
'created_at' => date('Y-m-d H:i:s'),
]);
return Response::json(['ok' => true]);
}
}
Шаблоны
Шаблоны плагина лежат в plugins/reviews/templates/. Движok резолвит их по префиксу plugins/<slug>/:
$this->view('plugins/reviews/admin/index', …);
// → plugins/reviews/templates/admin/index.tpl
Активная тема может перебить шаблон, создав файл templates/<theme>/plugins/reviews/admin/index.tpl.
Ассеты, языки, документация
assets/reviews.cssиassets/reviews.js(имя = slug) подключатся автоматически тегом{plugin_assets}. Прочие файлы — прямой ссылкой/public/assets/plugins/reviews/....languages/ru.json,languages/en.jsonподхватятся автоматически; используйте{lang key="reviews.x"}.- Положите
docs/README.md— он откроется в админке кнопкой документации плагина.
Включение
Зайдите в Плагины, нажмите «Включить» у вашего плагина — применятся миграции, подключится plugin.php, скопируются ассеты.
Дальше: Хуки и события, Миграции и база данных, Безопасность.