Создание плагина

Создание плагина

Пройдём путь от пустой папки до рабочего плагина с маршрутом, контроллером и пунктом в админке. За основу удобно держать перед глазами plugins/leads/.

Быстрый старт: пустой плагин

Чтобы не начинать с нуля, скачайте минимальный рабочий плагин:

Скачать пустой плагин (zip)

Внутри: manifest.json, plugin.php (публичный маршрут /starter, страница админки /admin/starter, пункт меню, миграция), контроллеры, шаблон админки, языки и docs/. Установите через Плагины → Установить из zip и нажмите «Включить» — либо распакуйте папку starter/ в plugins/. Затем переименуйте starter в свой slug.

Slug плагина не должен содержать дефис: из него строится PHP-namespace (Celena\Plugin\<Slug>), а дефис в namespace недопустим. Используйте myplugin или my_plugin.

Ниже — как всё устроено, чтобы собрать своё с нуля.

С чего начать

  1. Придумайте slug (латиница, цифры, дефис), например reviews.
  2. Создайте папку 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, скопируются ассеты.

Дальше: Хуки и события, Миграции и база данных, Безопасность.