[CMF] Core

[CMF] Core 1.0.8

Нет прав для скачивания
Автозагрузчик WHM_Core_Autoloader
=================================
Общие положения
---------------
Ядро для работы и реализации функционала использует собственный автозагрузчик классов и собственный аналог реестра очень похож на реестр XenForo_Application, но со своими особенностями.

Для включения полного функционала ядра, кроме установки дополнения через админку, надо включить подмену стандартного автозагрузчика XenForo.

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

Обычный режим автозагрузчика. Production.
-----------------------------------------
Для включения автолоадера ядра надо в начало `config.php` добавить:
PHP:
CMF_Core_Autoloader::getProxy();


При таком подходе `CMF_Core_Autoloader` грузится прямо перед `XenForo_FrontController` что позволяет при желании расширить любой класс после него, кроме `XenForo_Application`, который загружается ранее:
~~~
1. index.php
2. library/XenForo/Autoloader.php
3. library/XenForo/Application.php
4. library/Zend/Registry.php
5. library/Lgpl/utf8.php
6. library/Zend/Config.php
7. library/config.php
8. library/CMF/Core/Autoloader.php
.....
.. library/XenForo/FrontController.php
~~~


Класс CMF_Core_Listener. Расширенная работа с событиями
=======================================================
Одно из неудобств XenForo при активной разработке это необходимость прописывать любое событие в админке. В XenForo можно работать напрямую с событиями через `XenForo_CodeEvent`, но функционал там только базовый, только статические методы и API не очень удобное. Поэтому был написан класс для расширенной работы с событиями `CMF_Core_Listener`, наследуемый от `XenForo_CodeEvent`, что позволило получить доступ ко всем оригинальным обработчикам событий XenForo.

Базовая работа с событиями
--------------------------
Для удобства описания всех обработчиков создано отдельное событие `init_listeners`, которое запускается из автозагрузчика, до всех остальных событий XenForo, даже раньше `init_dependencies`.

Сигнатура события:
Код:
`public static function initListeners(CMF_Core_Listener $events)`

**Внимание!** Событие стартует до полной инициализации приложения, например, еще не загружены опции, поэтому лучше использовать в нем только методы `CMF_Core_Listener`.

Добавление событий
------------------
Внутри события в объекте `$events` доступен оригинальный массив (данные об обработчиках из админки) обработчиков событий `$listeners`, со структурой `[Имя_события][Подсказка_события] => массив_обработчика`. `'_'` в качестве подсказки события по умолчанию (пустая подсказка).
Пример добавления нескольких событий в конец очереди:
PHP:
public static function initListeners(CMF_Core_Listener $events) {
    $events->listeners['init_application']['_'][] = array('Some_Class_Listener', 'initApplication');
    $events->listeners['template_hook']['_'][] = array('Some_Class_Listener', 'templateHook');
}


~~~
Также обработчки можно добавлять используя методы:
`prependListener($event, $callback, $hint)` - добавить обработчик в начало очереди события
`appendListener($event, $callback, $hint)` - добавить обработчик в конец очереди события
`addListeners(array $listeners, $prepend = false)` - добавить набор обработчиков для событий в начало или конец очереди, т.е. попросту объединение 2-х массивов обработчиков в нужном порядке.

Управление наследованием
------------------------
В XenForo очень удобно реализовано расширение классов через динамическое наследование используя `load_class_*` события, но описание их приходится делать в нескольких местах, также приходится писать много однотипного кода (обработчики событий `load_class_*` практически всегда одинаковые только отличаются названием классов), хотя в 99% случаев надо просто расширить какой-то один класс другим.
Для облегчения этого и чтобы все можно было прописать в 1 месте без необходимости делать обработчики для `load_class_*` событий добавлены специальные методы для описания наследования. Т.е. достаточно прописать в событии `init_listeners` цепочку наследования и обработчики событий для динамического наследования создадутся автоматом.

Для этого используется метод
Код:
`public function addExtenders($extenders, $prepend = false)`


Пример описания расширения `XenForo_DataWriter_Page` с помощью `Some_Addon_DataWriter_Node` и `Some_Addon_DataWriter_Page`, а также
расширения `XenForo_ViewPublic_Page_View` с помощью `Some_Addon_ViewPublic_Page_View`:
PHP:
public static function initListeners(CMF_Core_Listener $events) {
    $events->addExtenders(
        array(
            'XenForo_DataWriter_Page' => array(`Some_Addon_DataWriter_Node`, `Some_Addon_DataWriter_Page`),
            'XenForo_ViewPublic_Page_View' => 'Some_Addon_ViewPublic_Page_View'
        )
    );
}


**Внимание!** Обработчики событий, автоматически сгенерированные для событий `load_class_*` на основе описания `addExtenders` запускаются **в самую первую очередь** до обработчиков прописанных как в админке XenForo, так и обработчиков прописанных через `listeners` методы.

Наследование базовых классов XenForo
------------------------------------
Используя собственный автозагрузчик файлов можно решить проблему наследования нерасширяемых классов XenForo.
Например, чтобы динамически расширить класс который вызывается по прямому имени - тот же, `XenForo_Link`, можно сделать физическую копию класса, но с измененным именем, например `XFProxy_XenForo_Link`, потом от него динамически отнаследовать цепочку как любой динамический класс в XenForo (задекларировать через `eval`), после чего опять же через `eval` задекларировать класс `XenForo_Link`.

Т.е. вместо инклуда файла с `XenForo_Link` получаем цепочку:

1. Создание копии файла с подменой оригинального имени класса на класс с префиксом, т.е. `XFProxy_XenForo_Link`.
2. Стандартное динамическое декларирование с наследованием цепочки `XFCP_*` классов через `eval` с инклудами файлов аддонов.
3. Декларирование оригинального имени класса `XenForo_Link` через `eval`, который расширяет последний класс в цепочке динамического наследования из предыдущего п.2.

Для удобной работы с таким прокси наследованием добавлено новое событие `load_class_proxy_class` по интерфейсу аналогичное остальным `load_class_*` событиям, только с некоторыми особенностями связанными со спецификой такого наследования.

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

Т.к. это динамическое наследование происходит внутри автозагрузчика классов, то проверять для каждого загружаемого класса возможность расширения его с помощью события `load_class_proxy_class` без хинтинга неэффективно в плане производительности, поэтому только если обработчик будет создан с хинтигом запустится событие `load_class_proxy_class`.

При использовании метода `addProxyExtenders` для события `load_class_proxy_class` обработчики автоматически автоматически создаются с хинтингом:
PHP:
public static function initListeners(CMF_Core_Listener $events) {
    $events->addProxyExtenders(
        array(
            'Some_XenForo_Class' => 'Some_AddOn_Class'
        )
    );
}


~~~

**Внимание!** При расширении абстрактных классов, в имени расширяемого класса или в имени дочернего класса должно быть слово `Abstract`, т.е. Convention over Configuration, например:
PHP:
public static function initListeners(CMF_Core_Listener $events) {
    $events->addProxyExtenders(
        array(
            'XenForo_DataWriter' => 'AddOn_DataWriter_Abstract'
        )
    );
}


~~~

**Внимание!** Стоит помнить что при такой динамической подмене тело статических методов начинает принадлежать другому классу. Для примера, еслу будем расширять
аддоном класс `XenForo_Link` (хороший пример, т.к. в нем одни статические методы), то тело всех методов (т.е. сами базовые родительские методы) будет находиться в `XFProxy_XenForo_Link`, а не внутри `XenForo_Link`. И как следствие обращаться к статическим методам оригинального класса из дочерних методов аддона, динамически отнаследованых в `load_class_proxy_class` надо будет только по полному пути, напрмиер `XFProxy_XenForo_Link::buildAdminLink` или `parent::buildAdminLink`, но не `XenForo_Link::buildAdminLink`, т.к. класс с именем `XenForo_Link` еще не загружен, и не `self::buildAdminLink`, который может давать неожиданные результаты, учитывая особенности видимости `self` для статических методов не инстанцированных классов.

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

Например, можно расширить 1 классом `Some_Addon_DataWriter_Node`, три класса: `XenForo_DataWriter_Node`, `XenForo_DataWriter_Page` и `XenForo_DataWriter_Forum` и это не вызовет ошибки даже если в коде все 3 класса будут одновременно использоваться в одном вызове.

PHP:
public static function initListeners(CMF_Core_Listener $events) {
    $events->addExtenders(
        array(
            'XenForo_DataWriter_Node'  => 'Some_Addon_DataWriter_Node',
            'XenForo_DataWriter_Page'  => 'Some_Addon_DataWriter_Node',
            'XenForo_DataWriter_Forum' => 'Some_Addon_DataWriter_Node'
        )
    );
}


~~~
**Внимание!** Мультинаследование работает только при описании наследования через `addExtenders`, во всех других случаях повторное декларирование будет вызывать стандартную ошибку `PHP Fatal error: Cannot redeclare class`.


CMF_Core - Расширенное ядро
===========================

Основные проблемы при разработке дополнений под XenForo
-------------------------------------------------------
При разработке и поддержке дополнений для XenForo основные проблемы с которыми сталкивается разработчик:

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


Все эти проблемы и неудобства позволяет решить ядро

Содержание
----------
#### 1. [Автозагрузчик CMF_Core_Autoloader.](autoloader.md)
#### 2. [Класс CMF_Core_Listener. Расширенная работа с событиями. Расширение любых классов XenForo.](listeners.md)
Назад
Сверху Снизу