---
title: 'Анатомия плагина OctoberCMS'
url: 'https://graker.ru/news/2016/08/25/octobercms-plugin-anatomy'
language: ru
type: post
author: 'Роман Грачев'
published: '2016-08-25T17:40:00+03:00'
updated: '2017-03-13T13:09:45+03:00'
categories:
  - OctoberCMS
  - Разработка
---

# Анатомия плагина OctoberCMS

Функционал OctoberCMS, как и любой другой CMS, можно расширять. Единицей такого расширения является *плагин*. В этой заметке я расскажу немного о том, что такое плагин для OctoberCMS, как его сделать, из чего он состоит и что куда класть внутри.

Б*о*льшую часть информации, рассматриваемой далее, можно почерпнуть из [официальной документации OctoberCMS](http://octobercms.com/docs/plugin/registration). В этой заметке я постараюсь не столько пересказать содержимое доков, сколько обобщить то, что мне удалось выяснить в процессе разработки и использования плагинов для October.

### Где взять плагины

Главным местом обитания плагинов для OctoberCMS является [маркет](http://octobercms.com/plugins), плагины там бывают как бесплатные, так и платные. Основное удобство маркета — быстрая инсталляция. Кроме того, на маркете можно объединять плагины в проекты (сборки), но об этом как-нибудь в другой раз.

Плагин с маркета устанавливается в один клик из админки (в которой есть удобный поиск):

![Установка плагинов](https://graker.ru/sites/default/files/styles/max_1300x1300/public/media/october-plugin-anatomy/oct_plugins.jpg.webp?itok=RFoG5LRA)

Кстати, не каждый сразу найдет, где именно находится установка плагинов. Она находится в меню «Настройки», пункт Система → Обновления (`/backend/system/updates`). Так уж вышло.

А еще можно установить плагин в одну строчку командой `artisan`:

```
php artisan plugin:install RainLab.Blog
```

и удаляется так же быстро:

```
php artisan plugin:remove RainLab.Blog
```

Здесь RainLab — имя автора плагина, а Blog — название.

Плагины, которым повезло меньше и они (пока) отсутствуют на маркете, обитают обычно, как и сам October, на гитхабе. Там же обитают и dev-версии популярных бесплатных плагинов с маркета, ссылки на их репозитории обычно указаны на странице плагина.

Если плагин отсутствует на маркете, устанавливать его придется вручную. Это нетрудно: нужно просто скачать директорию с плагином и положить в `plugins/<authorname>/`, где `<authorname>` — это имя автора плагина (например `plugins/graker/blogarchive`). И затем нужно запустить миграции командой:

```
php artisan october:up
```

Нужно заметить, что October по умолчанию считает плагины, лежащие в plugins, включенными, а `october:up` нужен только для запуска миграций.

> [Миграции](http://octobercms.com/docs/database/structure) — это файлы, описывающие создание (и удаление) таблиц БД, необходимых для работы плагина.

Поэтому установленный вручную плагин будет работать и без `october:up`. Просто необходимые ему таблицы БД не создадутся и при первой же попытке что-либо считать или записать — произойдет ошибка.

### Как создать плагин

Самый простой способ — использовать [скаффолдинг](http://octobercms.com/docs/console/scaffolding), то есть с помощью команды `artisan`:

```
php artisan create:plugin Author.MyPlugin
```

Команда создаст в /plugins директорию author (если ее еще нет), а в ней — директорию myplugin с файлами плагина. И да, как вы уже догадались, плагины в OctoberCMS группируются по авторам. Кроме того, нужно иметь в виду, что каждый плагин должен быть в неймспейсе `AuthorName\PluginName` (с соблюдением регистра).

### Структура плагина

В директории с плагином можно найти вот что:

- assets/ — директория с ассетами (скриптами, картинками, стилями);
- classes/ — директория со вспомогательными классами, нужными для работы плагина;
- components/ — компоненты, определенные в плагине;
- config/ — настройки плагина (вариант «в коде»);
- controllers/ и models/ — очевидно, контроллеры и модели;
- console/ — команды artisan;
- updates/ — миграции и файл `version.yaml`;
- widgets/ — описанные в плагине виджеты;
- Plugin.php — регистрационный файл плагина.

Теперь посмотрим на основные составляющие плагин классы более подробно.

### Регистрационный файл

Начнем с конца — с файла `Plugin.php`. Этот файл содержит основную информацию о том, что вообще в нашем плагине есть: как он называется, какие компоненты, настройки, пункты меню и обработчики событий в нем зарегистрированы и т.д. Поэтому файл `Plugin.php` называется в документации *регистрационным*.

Как добавить в плагин компоненты, модели и настройки — я покажу чуть позже. А пока посмотрим на начало регистрационного файла (на примере моего плагина [PhotoAlbums](https://github.com/graker/photoalbums)).

```
class Plugin extends PluginBase
{
  /**
   * Returns information about this plugin.
   *
   * @return array
   */
  public function pluginDetails()
  {
    return [
      'name'        => 'PhotoAlbums',
      'description' => 'Create, display and manage galleries of photos arranged in albums',
      'author'      => 'Graker',
      'icon'        => 'icon-camera-retro',
      'homepage'    => 'https://github.com/graker/photoalbums',
    ];
  }
  // ...
}
```

Это основная информация о плагине: название, описание, автор, иконка (для меню админки, из набора [October](https://octobercms.com/docs/ui/icon)) и место жительства.

Если наш плагин должен иметь зависимости (от других плагинов), они тоже объявляются в регистрационном файле, в свойстве `$require`:

```
public $require = ['RainLab.Blog', 'RainLab.Pages'];
```

— так мы объявили зависимости от плагинов [Blog](http://octobercms.com/plugin/rainlab-blog) и [Static Pages](http://octobercms.com/plugin/rainlab-pages).

### Метод boot()

Метод `boot()` класса нашего плагина будет вызываться всякий раз перед обработкой входящего запроса. Этот метод хорошо подходит, чтобы, например, прицепиться к событиям, на которые мы хотим реагировать в нашем плагине.

```
public function boot() {
  Event::listen('backend.form.extendFields', function (Form $widget) {
      // модифицируем $widget
  });
}
```

В данном примере мы реагируем на событие `backend.form.extendFields`, которое позволяет нам модифицировать форму, созданную в другом плагине (это не единственный способ модифицировать форму, просто пример).

События в OctoberCMS — это основное средство взаимодействия между плагинами. Можно подслушивать и реагировать на системные события, события, определенные в других плагинах, и конечно генерировать свои события. Подробнее об этом можно почитать в [документации](https://octobercms.com/docs/services/events).

### Настройки и страницы админки

Обычно в плагине объявляется одна или несколько страниц админки с настройками, формами, списками и все такое.

Страницы админки (то есть, бэкэнда) объявляются в регистрационном файле. Вот пример объявления страницы для настроек:

```
  public function registerSettings()
  {
    return [
      'settings' => [
        'label'       => 'MyPlugin',
        'description' => 'Manage MyPlugin Settings.',
        'icon'        => 'icon-camera-retro',
        'class'       => 'Author\MyPlugin\Models\Settings',
        'order'       => 100,
        'permissions' => ['author.myplugin.access_settings'],
      ]
    ];
  }
```

В результате в админке, на странице *Settings* в левой колонке, в секции *Other* появится пункт MyPlugin, содержащий форму настроек нашего плагина. Состав формы зависит от того, что мы укажем в модели настроек. Для настроек используется модель `Author\MyPlugin\Models\Settings`, наследующая класс `Model` и реализующая поведение `System.Behaviors.SettingsModel`. Хранятся такие настройки в базе данных.

Есть возможность хранить настройки и в коде, но в таком случае для них не генерируется страница админки. И то, и другое [описано](https://octobercms.com/docs/plugin/settings) в документации.

Помимо форм настроек, в регистрационном файле можно объявить и необходимые страницы админки «верхнего уровня», то есть которые появляются в верхнем меню. Обычно эти страницы используются контроллерами для вывода списков моделей и форм создания/редактирования моделей. Объявляются страницы админки вот так:

```
  public function registerNavigation()
  {
    return [
      'myplugin' => [
        'label' => 'My Plugin',
        'url' => Backend::url('author/myplugin/mymodels'),
        'icon'        => 'icon-camera-retro',
        'permissions' => ['author.myplugin.manage_mymodels'],
        'order'       => 500,
        'sideMenu' => [
          'new_model' => [
            'label'       => 'New MyModel',
            'icon'        => 'icon-plus',
            'url'         => Backend::url('author/myplugin/mymodels/create'),
            'permissions' => ['author.myplugin.manage_mymodels'],
          ],
          'mymodels' => [
            'label'       => 'MyModels',
            'icon'        => 'icon-copy',
            'url'         => Backend::url('author/myplugin/mymodels'),
            'permissions' => ['author.myplugin.manage_mymodels'],
          ],
        ],
      ],
    ];
  }
```

В данном примере регистрируется пункт меню админки My Plugin, и два подпункта — New MyModel (форма для создания модели) и MyModels (список существующих моделей). Подпункты будут показаны в боковом меню при выборе My Plugin в главном (верхнем) меню. Причем страница, соответствующая пункту MyModels будет показана по умолчанию (т.к. у этой страницы указан такой же url, как у страницы верхнего уровня).

Также для каждой страницы задается иконка и разрешения, которыми должен обладать пользователь, чтобы он мог зайти на страницу. Вот пример страницы админки для плагина PhotoAlbums (тут определено больше пунктов в сайдбаре):

![](https://graker.ru/sites/default/files/styles/max_1300x1300/public/media/october-plugin-anatomy/oct_backend.jpg.webp?itok=MQWZH3rX)

Как привязать к этим страницам контроллеры, будет показано ниже.

### Модели

Одна из основных причин создать новый плагин — это необходимость объявить новый тип данных на сайте и описать работу с ними. Поскольку Laravel, на базе которого сделан October — это MVC-фреймворк, новые типы данных — это [модели](https://octobercms.com/docs/database/model). Модель — это класс Model из Laravel, реализующий паттерн Active Record. В October класс модели дополнен разными полезностями, такими как генерация формы модели из yaml-файла или возможность приаттачить файл в одну строчку кода.

В рамках создаваемого плагина модели живут в директории `models` и регистрируются автоматически, так что объявлять их в `Plugin.php` не нужно. Для каждой модели нужно создать файл `models/MyModel.php` с классом модели, наследующим класс Model.

Кроме того, в директории `models/mymodel/` можно создать два файла: `colums.yaml` и `fields.yaml`. Файл `columns.yaml` описывает поля модели, которые можно вывести в списке моделей:

```
# ===================================
#  List Column Definitions
# ===================================

columns:
    title:
        label: Title
        searchable: true

    created_at:
        label: Created
        type: date

    updated_at:
        label: Updated
type: date
```

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

Файл `fields.yaml` описывает форму, используемую для создания и редактирования модели:

```
# ===================================
#  Form Field Definitions
# ===================================

fields:
    title:
        label: Title
        span: full
        placeholder: My model title
        required: true

    description:
            label: Description
            type: richeditor
            size: large
            span: right

    image:
        label: Photo
        type: fileupload
        mode: image
        imageWidth: 320
        imageHeight: 240
        span: left
```

Этот файл также используется контроллером для генерации формы создания/редактирования модели.

Чтобы не создавать файлы и директории вручную, можно просто написать:

```
php artisan create:model AuthorName.MyPlugin MyModel
```

и файлы будут сгенерированы.

### Миграции

Разумеется, чтобы созданными моделями можно было пользоваться, для них нужно создать таблицы в БД. По умолчанию модель ожидает, что ее таблица будет называться вот так:

```
authorname_myplugin_mymodels
```

То есть в нижнем регистре имя автора плагина → подчеркивание → название плагина → подчеркивание → название модели *во множественном числе (!)*. Название таблицы, если очень хочется, можно изменить, сообщив его в свойстве модели.

Чтобы сгенерировать таблицу для модели, нужно создать файл миграции. Если для создания файлов модели мы использовали команду `artisan create:model`, то файл `create_mymodels_table.php` будет сгенерирован в директории `updates/` автоматически.

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

```
<?php namespace AuthorName\MyPlugin\Updates;
use Schema;
use October\Rain\Database\Updates\Migration;
class CreateMyModelsTable extends Migration
{
  public function up()
  {
    Schema::create('authorname_myplugin_mymodels', function($table)
    {
      $table->engine = 'InnoDB';
      $table->increments('id');
      $table->string('title')->nullable();
      $table->text('description')->nullable();
      $table->integer('user_id')->unsigned()->nullable()->index();
      $table->timestamps();
    });
  }

  public function down()
  {
    Schema::dropIfExists('authorname_myplugin_mymodels');
  }
}
```

Метод `up()` будет вызван по команде `php artisan october:up` и создаст описанную таблицу автоматически. А метод `down()` предназначен для отката миграции (удаления данных) и вызывается по команде `php artisan october:down`.

### Обновления и версии плагина

Чтобы миграции запустились автоматически при установке плагина, или при выполнении `october:up`, их нужно описать в [файле обновлений](https://octobercms.com/docs/plugin/updates), `updates/version.yaml`:

```
1.0.1:
    - Initialize plugin
    - create_mymodels_table.php
```

Соответственно, если мы доработали плагин и наши доработки требуют изменений в БД (например, надо добавить поле в таблицу), то мы создаем еще одну миграцию и добавляем запись в `version.yaml`:

```
1.0.1:
    - Initialize plugin
    - create_mymodels_table.php
1.1.0:
    - Add column to mymodels table
    - update_mymodels_table.php
```

Тогда при запуске `october:up` миграция из `update_mymodels_table.php` будет выполнена автоматчески. Также записи в `version.yaml` нужно добавлять для любых изменений, если плагин живет на маркете — чтобы пользователи могли автоматически обновиться.

Кстати, если нужно полностью (с потерей данных!) переустановить плагин, есть команда `plugin:refresh`:

```
php artisan plugin:refresh AuthorName.PluginName
```

### Контроллеры

Чтобы добавленными в плагин моделями можно было управлять (создавать, редактировать и удалять), нам понадобится [контроллер](http://octobercms.com/docs/backend/controllers-ajax). Его можно сгенерировать командой

```
php artisan create:controller AuthorName.MyPlugin MyModels
```

Появится файл с классом контроллера `controllers/MyModels.php` и директория `controllers/mymodels/` с файлами представлений (view) и настроек.

Контроллеры и их представления — это по сути кирпичики, из которых (а также из настроек) построен бэк-энд сайта. Как правило, под каждую модель в плагине нужно создать отдельный контроллер.

Контроллеры в October устроены так, чтобы по возможности избавить разработчика от рутинных операций создания списков моделей и форм создания/обновления/удаления. Нужно лишь немного сконфигурировать контроллер и описать свою уникальную бизнес-логику, и админка будет готова. Для примера, сделаем страницу со списком созданных моделей. Что для этого нужно.

Во-первых, нужно сообщить в классе контроллера, что он реализует поведение ([behavior](http://octobercms.com/docs/services/behaviors)) списка и указать, в каком файле лежат настройки списка:

```
class MyModels extends Controller
{
  public $implement = [
    'Backend.Behaviors.ListController'
  ];
  public $listConfig = 'config_list.yaml';

  // ...
}
```

Затем создать файл конфига `controllers/mymodels/config_list.yaml`:

```
# ===================================
#  List Behavior Config
# ===================================

# Укажем адрес конфига колонок модели (мы его создали выше)
list: $/authorname/myplugin/models/mymodel/columns.yaml

# Укажем класс модели
modelClass: AuthorName\MyPlugin\Models\MyModel

# Заголовок списка
title: Мои модели

# URL для редактирования модели
recordUrl: authorname/myplugin/mymodels/update/:id

# Количество записей на странице
recordsPerPage: 20

# Делаем колонки сортируемыми
showSorting: true

# Тулбар над списком
toolbar:
    # Указываем, где описаны кнопки тулбара
    buttons: list_toolbar

    # Настраиваем поиск
    search:
        prompt: backend::lang.list.search_prompt
```

Это пример конфига для списка моделей, он не содержит все возможные настройки (они есть [в документации](http://octobercms.com/docs/backend/lists)), но даже из этого примера получается весьма функциональная страница админки с возможностью выводить модели списком, сортировать по заголовку и дате создания/обновления (как мы написали ранее в `columns.yaml`), удалять выбранные модели и даже искать модели по названию. Таблица будет сгенерирована вся такая симпатичная (см. картинку выше) и кнопочки/поиски/сортировки из коробки будут работать с AJAX, т.е. без перезагрузки страницы.

Единственное что осталось, чтобы список был виден — привязать его к странице настроек, которую мы ранее зарегистрировали в `Plugin.php`. Для этого мы в конструкторе нашего контроллера `MyModels` напишем:

```
  public function __construct()
  {
    parent::__construct();
    BackendMenu::setContext('Author.MyPlugin', 'myplugin', 'mymodels');
  }
```

Здесь мы связали контроллер с пунктом меню админки My Plugin, описанным ранее в `Plugin.php`.

### Компоненты

Если контроллеры определяют бэкэнд, то для фронтэнда сайта строительным материалом в October служат компоненты. Компонент — это динамически генерируемый кусочек содержимого сайта. Лента записей в блоге, отдельный пост, верхнее меню, список тегов — это примеры компонентов.

Можно сказать, что компонент — это связующее звено между активной темой сайта (в которой определены все страницы фронт-энда, фрагменты и шаблоны) и плагинами сайта. Именно с помощью компонентов плагин предоставляет активной теме доступ к генерируемому в рамках плагина содержимому.

Как это происходит? Допустим, мы делаем блог и включили плагин Blog. Теперь нам нужно сделать страницу с лентой постов. Мы создаем в теме страницу и прикрепляем к ней компонент Blog Posts, который и рендерит ленту постов. Выглядеть код нашей страницы будет примерно вот так:

```
title = "Blog"
url = "/blog/:page?"

[blogPosts]
postsPerPage = "10"
==
{% component 'blogPosts' %}
```

То есть мы просто указываем настройки компонента (в данном случае, количество постов на страницу) и рендерим его с помощью тега `{% component 'blogPosts' %}`. Все остальное происходит внутри компонента, то есть в плагине Blog. Конечно, скорее всего мы захотим внутри нашей темы изменить дефолтную верстку компонента. Это нетрудно сделать: у каждого компонента есть один или несколько шаблонов, которые нужно просто скопировать в директорию `<тема>/partials/<имя компонента>/` и переопределять разметку там. Скопированные файлы подхватятся автоматически.

Теперь посмотрим, как добавить компонент в плагин. Для этого тоже есть удобная команда artisan:

```
php artisan create:component Author.MyPlugin MyComponent
```

Команда создаст в директории `components` файл `MyComponents.php` с классом компонента, а также файл с шаблоном `default.htm` в директории `components/mycomponent`. Файл шаблона содержит результирующую разметку, которая будет вставлена на страницу вместо тега `{% component 'mycomponent' %}`:

```
{% set model = __SELF__.model %}

<h1>{{ model.title }}</h1>

{% if model.description %}
    <div class="description row">
        <div class="col-xs-12">
            {{ model.description|raw }}
        </div>
    </div>
{% endif %}
```

Разумеется, в файле разметки можно обращаться к переменным и функциям, заданным в классе `MyComponent`. То есть класс компонента выступает контроллером для выводимых нами данных, а шаблон компонента — это представление.

Посмотрим на класс компонента:

```
<?php namespace Author\MyPlugin\Components;

use Cms\Classes\Page;
use Cms\Classes\ComponentBase;
use Author\MyPlugn\Models\MyModel;

class MyComponent extends ComponentBase
{
  public function componentDetails()
  {
    return [
      'name'        => 'My Model',
      'description' => 'Component to output MyModel'
    ];
  }

  public function defineProperties()
  {
    return [
      'id' => [
        'title'       => 'Id',
        'description' => 'URL id parameter',
        'default'     => '{{ :id }}',
        'type'        => 'string'
      ],
    ];
  }

  // ...
```

В `componentDetails()` мы сообщаем имя и описание компонента, а в `defineProperties()` — задаем свойства (настройки) компонента. Эти настройки появятся в интерфейсе при прикреплении компонента к странице или шаблона, их также будет видно в коде страницы или шаблона в виде текста `id = {{ :id }}`, как было показано в примере выше. В данном случае у нашего компонента есть свойство `id`, которое по умолчанию берет id модели для показа из URL страницы.

Благодаря свойствам мы можем использовать один и тот же компонент в разных частях сайта, лишь изменив его свойства.

Теперь посмотрим на продолжение класса компонента. Поскольку компонент выводит на страницу нашу модель MyModel, нам нужно ее загрузить.

```
  // ...

  public $model;

  public function onRun() {
    $id = $this->property('id');
    $this->model = $this->page->album = MyModel::find($id);
  }
}
```

Метод `onRun()` вызывается всякий раз при загрузке страницы и отлично подходит для подготовки данных для показа. Мы получаем id модели из определенного нами свойства компонента, затем загружаем модель по id и сохраняем в свойство $model, чтобы модель была доступна шаблону как `__SELF__.model`.

В отличие от моделей и контроллеров, чтобы компонент стал доступен, его нужно зарегистрировать в `Plugin.php`:

```
public function registerComponents()
{
    return [
        'Author\MyPlugin\Components\MyModel' => 'myModel'
    ];
}
```

Указанное имя `myModel` будет использоваться при вставке компонента на страницы.

Подробнее о компонентах, их свойствах и шаблонах можно почитать [в документации](https://octobercms.com/docs/plugin/components).

Это все, что я хотел рассказать об устройстве плагинов. Разумеется, в документации можно найти еще больше подробностей. Изложенного здесь, на мой взгляд, достаточно для общего представления о том, как устроен плагин в OctoberCMS.

Спасибо за внимание.
