Разбираемся с каркасом расширений для FFCMS — hello world!

в 12:34, , рубрики: cms, php, Блог компании FFCMS, расширение, синтаксис

Разбираемся с каркасом расширений для FFCMS — hello world! Как я и обещал ранее, в своем предыдущем посте на habrahabr сегодня я хочу рассказать вам о том, как написать свое первое расширение для системы управления содержимым сайта FFCMS. В данном материале мы ознакомимся с вами с основным каркасом расширений, который необходим для взаимодействия с системой — иными словами мы напишем с вами «hello world» в представлении для ffcms как компонент, модуль и хук.

1. Введение

Для начала я хочу рассказать вам о структуре расположения расширений в системе FFCMS.
В версии 2.0 все расширения расположены в директории /extensions в которой представлены 4 директории в зависимости от типа расширения — components, modules, hooks, api (компоненты, модули, хуки и apicallback соответственно).
Все системные данные о расширении хранятся в системной таблице(база данных) — _extensions:

  • _extensions.type — тип расширения(enum('components', 'modules', 'hooks'))
  • _extensions.dir — название расширения (корневая директория и название класса)
  • _extensions.enabled — включено ли расширение? (1/0)
  • _extensions.version и _extensions.compatable — версия и указатель совместимости с версией системы
  • _extensions.path_choice/path_allow/path_deny — специальные поля для условий запуска модулей по тем или иным правилам URI

Самыми важными для нас являются колонки type и dir — в зависимости от них будет инициировано наше расширение. Логический путь инициации будет выглядеть следующим образом: ROOT. _extension.type. _extension.dir.
Как пример, для type = components и dir = news будет использоваться следующая директория: /extensions/components/news/.

2. Направление взаимодействия(указатель типа интерфейса)

Каждое расширение (компонент, модуль или хук) может иметь 2 основных реализации в зависимости от инициирующего интерфейса системы:

  • front — направление взаимодействия с интерфейсом пользователя
  • back — направление взаимодействия с интерфейсом администратора

В следствии этого в директории каждого расширения может быть 2 различных реализации(2 файла с моделью реализации): front.php и back.php.
Если объяснить это проще(человеческим языком) — то, что увидит пользователь должно быть обработано и отображено в реализации front.php, а то, что будет доступно из административной панели — back.php.
Как пример, для расширения с type = components и dir = news могут быть использованы 2 основных файла для реализации различных функциональных возможностей(администратора и пользователя) — /extensions/components/news/front.php и /extensions/components/news/back.php. Так же может быть создана дополнительная реализация, которая не зависит от направления взаимодействия с тем или иным интерфейсом: cron.php — реализация методов выполнения заданий по расписанию, но об этом позже.

3. Стандарт именования классов расширений

Название классов расширений определяется 3мя параметрами:

  1. Тип расширения(см. выше — _extensions.type)
  2. Директория|название расширения (см. выше — _extensions.dir)
  3. Направление взаимодействия (front или back)

Основываясь на этих параметрах для каждого направления взаимодействия расширения, в зависимости от его типа и имени(директории расположения) может быть определено название его класса(оно уникально во всей системе).
Так для нашего примера — type = components, dir = news, для направления реализации front имя класса должно быть: components_news_front, а для направления back: components_news_back.
Реализация класса расширения должна следовать паттерну singleton с статическим методом getInstance(), который должен вернуть объект инициированного класса. Для того, чтобы не представлять реализацию каждый раз можно наследовать абстрактный класс enginesingleton.
Каждый класс расширения должен иметь обязательную функцию динамического вызова: void make().
Так, итоговый каркас для нашего примера type = components, dir = news будет иметь следующий вид:
/extensions/components/news/front.php:

<?php
class components_news_front extends enginesingleton { // type = components, dir = news, iface = front, pattern = singleton
    public function make() {
          // модель реализации 
    }
}

/extensions/components/news/back.php:

<?php
class components_news_back extends enginesingleton { // type = components, dir = news, iface = back, pattern = singleton
    public function make() {
          // модель реализации 
    }
}

4. Контроль версий, совместимости и установщик

Кроме простой реализации расширений в системе FFCMS предусмотрены и алгоритмы, которые позволят использовать написанные вами расширения для всего сообщества. Для всех направлений реализации back могут быть реализованы следующие системные методы контроля за расширением:

  • void _install() — реализация установщика системы(работа с базой, языковыми версиями или прочие манипуляции)
  • void _update(int version_from) — реализация метода обновления расширения (если вы планируете улучшать ваше расширение — вам необходим механизм, который позволит в дальнейшем выполнить изменения в базе данных, языковых локалях и прочих направлениях)
  • string _version() — метод, который должен возвращать текущую версию расширения(в системе предусмотрен алгоритм проверки соответствия версии в базе и в файле реализации + вывод уведомлений о их несоответствии)
  • string _compatable() — метод, который должен возвращать версию системы FFCMS, с которой совместимо данное расширение(в системе предусмотрен алгоритм проверки совместимости расширения и системы)

Для нашего примера, описанного в статье выше с type = components, dir = news, iface = back данные методы контроля должны выглядеть следующим образом:
/extensions/components/news/back.php:

class components_news_back extends enginesingleton {

    public function _version() {
        return '1.0.1';
    }

    public function _compatable() {
        return '2.0.4';
    }

   public function _install() {
        // выполнение установки расширения
   }

   public function _update($v) {
        switch($v) {
             // case - условия/действия
        }
   }

    public function make() {
        // основная реализация
    }

5. Hello, world habrahabr!

Ну что же, раз с каркасами расширений мы, хоть и бегло, ознакомились — пришло время написать наш «hello world» в виде расширения для FFCMS.
В качестве примеров мы напишем 2 реализации «hello habrahabr» — компонент и модуль, реализации «front» (для пользователя).

5.1. Компонент hello

Итак, первым нашим примером мы реализуем компонент с именем «hello» (type = components, dir = hello, iface = front). Для начала, добавим инициацию вызова нашего расширения в таблицу _extetnsions(мы же разработчики, не добро нам писать установщик перед отладкой):

INSERT INTO ffcms_extensions (`id`, `type`, `configs`, `dir`, `enabled`, `path_choice`, `path_allow`, `path_deny`, `version`, `compatable`) VALUES (NULL, 'components', '', 'hello', '1', '', '', '', '1.0.1', '2.0.4');

И создадим необходимую файловую архитектуру и наполним каркас нашего компонента кодом:
/extensions/components/hello/front.php

<?php


class components_hello_front extends enginesingleton {

    public function make() {
        // содержимое реализации
    }
}

Теперь пользователи сайта (интерфейса front) могут обратиться к нашему компоненту по URI: /hello/ (который может выглядеть по разному в зависимости от включеня/выключения мульти-язычности и ЧПУ, к примеру: /index.php/ru/hello/, /ru/hello/ или просто /hello/).
Однако в данный момент наш компонент не выполняет никаких действий — что ж, давайте зададим вывод фразы «Hello habrahabr» в позицию шаблона {{ content.body }}:

<?php

use enginetemplate;

class components_hello_front extends enginesingleton {

    public function make() {

        $response = "Hello habrahabr!";

        template::getInstance()->set(template::TYPE_CONTENT, 'body', $response);
    }
}

и если вы все сделали правильно вы увидите примерно такой результат по выше описанному URI.
Теперь я покажу вам несколько простых манипуляций с данным примером, с которыми вы обязательно столкнетесь в результате разработки расширений для FFCMS.
Не хорошо ведь, что наше представление находится внутри модели? Давайте перенесем содержимое текста ответа в шаблон и добавим парсинг с помощью twig случайного числа для данного шаблона. Создадим шаблонное представление /templates/default/components/hello/view.tpl и наполним его содержимым:

<h1>Привет!</h1>
<hr />
<p>Случайное число: {{ local.randomval }}</p>

А код расширения теперь будет выглядеть следующим образом:
/extensions/components/hello/front.php

<?php

use enginetemplate;
use enginesystem;

class components_hello_front extends enginesingleton {

    public function make() {
        $params = array();
        $params['local']['randomval'] = system::getInstance()->randomInt(1); // случайное число - 10 ^ (1-1) .. 10 ^ 1
        $tpl = template::getInstance()->twigRender('components/hello/view.tpl', $params); // рендерим содержимое шаблона с передачей локальных переменных в виде массива
        template::getInstance()->set(template::TYPE_CONTENT, 'body', $tpl); // назначаем обработанный шаблон в позицию content.body
    }
}

Основной особенностью компонентов является их работа в зависимости от выбранного роутинга URI — поэтому я просто обязан показать вам пример обработки URI в теле компонента.
В шаблонном представлении добавим 1 строку:

<p>URL[0] в компоненте: {{ local.second_way }}</p>

а в код front.php добавим следующее:

use enginerouter;
...

        $way = router::getInstance()->shiftUriArray(); // получаем URI без языка, чпу и адреса компонента
        $params['local']['second_way'] = $way[0]; // добавляем к рендерингу значение элемента URI

Теперь, обратившись к нашему компоненту по URI /hello/hello-developer.html мы увидим следующее в результатах обработки:

ffcms component render result

В дальнейшем вы можете использовать возможности класса router для маршрутизации внутри вашего компонента — да, в ffcms нет явно выделенного контроллера(как у модели mvc), однако ничто не мешает вам его реализовать.

5.2. Модуль habr

Модульная реализация предполагает под собой в первую очередь контроль и взаимодействие с шаблонными позициями. Так, если компонент будет инициирован (запущена его обработка до class::instance()->make()) только в случае обращения к определенному URI то модуль, может быть инициирован вне зависимости от роутинга.
Что ж, для нашего модуля habr (type = modules, dir = habr, iface = front) мы в первую очередь создадим точку инициации в нашей таблице _extensions:

INSERT INTO ffcms_extensions (`id`, `type`, `configs`, `dir`, `enabled`, `path_choice`, `path_allow`, `path_deny`, `version`, `compatable`) VALUES (NULL, 'modules', '', 'habr', '1', '1', '*', '', '1.0.1', '2.0.4');

Cразу отмечу, что позже мы так же сможем задавать правила роутинга для модуля(простыми словами — где работать а где нет) а сейчас мы выбрали тип правила «1» (где работать?) и указали маршрут * (везде).
Теперь создадим файловую архитектуру и модель нашего модуля:
/extensions/modules/habr/front.php:

<?php

use enginetemplate;

class modules_habr_front extends enginesingleton {

    public function make() {
        $response = 'Hello, habrahabr.ru!';
        template::getInstance()->set(template::TYPE_MODULE, 'habr', $response);
    }
}

И в основном шаблонном представлении (/templates/default/main.tpl) добавим отображение содержимого нашего модуля:

{{ module.habr }}

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

6. Заключение и ссылки

Таким образом мы познакомились с основной логикой построения каркаса расширений для системы управления содержимым сайта FFCMS 2.0 версии. Я надеюсь, что данный материал был полезным для вас и простым для вашего восприятия.
Документация FFCMS: FFCMS API 2.0
Наш замечательный форум: помощь на форуме
Github: zenn1989/ffcms

Автор: zenn

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js