Написание обновления плагина

Время от времени наступает момент, когда плагину нужно изменить содержимое или структуру данных, которые он хранит либо в базе данных, либо в dataroot.

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

Миграции и преобразования такого рода могут занять много времени, если нужно обработать много данных. Именно поэтому Elgg предоставляет класс Elgg\Upgrade\AsynchronousUpgrade, который можно использовать для реализации долго работающих обновлений.

Объявление обновления плагина

Плагин может сообщить о необходимости обновления под ключом upgrades в файле elgg-plugin.php. Каждое значение массива должно быть полностью квалифицированным именем класса обновления, который расширяет класс Elgg\Upgrade\AsynchronousUpgrade.

Пример из файла mod/blog/elgg-plugin.php:

return [
        'upgrades' => [
                Blog\Upgrades\AccessLevelFix::class,
                Blog\Upgrades\DraftStatusUpgrade::class,
        ]
];
Имена классов в примере относятся к классам:
  • mod/blog/classes/Blog/Upgrades/AccessLevelFix

  • mod/blog/classes/Blog/Upgrades/DraftStatusUpgrade

Примечание

Классы обновления ядра Elgg можно объявить в engine/lib/upgrades/async-upgrades.php.

Класс обновления

Класс, расширяющий класс Elgg\Upgrade\AsynchronousUpgrade, имеет большую свободу в том, как он хочет обрабатывать фактическую обработку данных. Однако он должен объявить некоторые константные переменные, а также позаботиться о пометке того, был ли каждый обработанный элемент успешно обновлён или нет.

Базовая структура класса следующая:

<?php

namespace Blog\Upgrades;

use Elgg\Upgrade\AsynchronousUpgrade;
use Elgg\Upgrade\Result;

/**
 * Fixes invalid blog access values
 */
class AccessLevelFix extends AsynchronousUpgrade {

        /**
         * Version of the upgrade
         *
         * @return int
         */
        public function getVersion() {
                return 2016120300;
        }

        /**
         * Should the run() method receive an offset representing all processed items?
         *
         * @return bool
         */
        public function needsIncrementOffset() {
                return true;
        }

        /**
         * Should this upgrade be skipped?
         *
         * @return bool
         */
        public function shouldBeSkipped() {
                return false;
        }

        /**
         * The total number of items to process in the upgrade
         *
         * @return int
         */
        public function countItems() {
                // return count of all blogs
        }

        /**
         * Runs upgrade on a single batch of items
         *
         * @param Result $result Result of the batch (this must be returned)
         * @param int    $offset Number to skip when processing
         *
         * @return Result Instance of \Elgg\Upgrade\Result
         */
        public function run(Result $result, $offset) {
                // fix 50 blogs skipping the first $offset
        }
}

Предупреждение

Не предполагайте, когда ваш класс будет создан или когда/как часто будут вызываться его публичные методы.

Методы класса

getVersion()

Это должно возвращать целое число, представляющее дату добавления обновления. Оно состоит из восьми цифр и имеет формат yyyymmddnn, где:

  • yyyy — год

  • mm — месяц (с ведущим нулём)

  • dd — день (с ведущим нулём)

  • nn — это инкрементирующее число (начиная с 00), которое используется в случае, если два отдельных обновления были добавлены в один и тот же день

shouldBeSkipped()

Это должно возвращать false, если только обновление не понадобится.

Предупреждение

Если возвращается true, обновление не может быть запущено позже.

needsIncrementOffset()

Если true, ваш метод run() получит в качестве $offset количество уже обработанных элементов. Это полезно, если вы только изменяете данные и вам нужно использовать $offset в функции типа elgg_get_entities(), чтобы узнать, сколько вы уже обработали.

Если false, ваш метод run() получит в качестве $offset общее количество ошибок. false следует использовать, если ваш процесс удаляет или перемещает данные в сторону от процесса. Например, если вы удаляете 50 объектов при каждом run(), вам действительно не нужен $offset.

countItems()

Получить общее количество элементов для обработки во время обновления. Если неизвестно, может быть возвращено Batch::UNKNOWN_COUNT, но run() должен вручную пометить обновление как завершённое.

run()

Должен выполнять часть фактического обновления. И в зависимости от того, сколько времени это занимает, он может вызываться несколько раз в течение одного запроса.

Получает два аргумента:

  • $result: экземпляр объекта Elgg\Upgrade\Result

  • $offset: смещение, с которого должна начинаться следующая часть обновления (или общее количество ошибок)

Для каждого элемента, который обрабатывает метод, он должен вызвать либо:

  • $result->addSuccesses(): если элемент был успешно обновлён

  • $result->addFailures(): если не удалось обновить элемент

Оба метода по умолчанию обрабатывают один элемент, но вы можете дополнительно передать количество элементов.

Дополнительно он может установить столько сообщений об ошибках, сколько сочтёт необходимым, в случае, если что-то пойдёт не так:

  • $result->addError("Error message goes here")

Если countItems() вернул Batch::UNKNOWN_COUNT, то в какой-то момент run() должен вызвать $result->markComplete() для завершения обновления.

В большинстве случаев ваш метод run() захочет передать параметр $offset в одну из функций elgg_get_entities():

/**
 * Process blog posts
 *
 * @param Result $result The batch result (will be modified and returned)
 * @param int    $offset Starting point of the batch
 * @return Result Instance of \Elgg\Upgrade\Result;
 */
public function run(Result $result, $offset) {
        $blogs = elgg_get_entitites([
                'type' => 'object'
                'subtype' => 'blog'
                'offset' => $offset,
        ]);

        foreach ($blogs as $blog) {
                if ($this->fixBlogPost($blog)) {
                        $result->addSuccesses();
                } else {
                        $result->addFailures();
                        $result->addError("Failed to fix the blog {$blog->guid}.");
                }
        }

        return $result;
}

getUpgrade()

Используйте эту функцию для получения связанной сущности ElggUpgrade, которая относится к этому обновлению.

Интерфейс администрирования

Каждое обновление, расширяющее класс Elgg\Upgrade\AsynchronousUpgrade, отображается в панели администратора после запуска обновления сайта из панели управления.

Во время выполнения обновлений Elgg предоставляет:

  • Оценочная продолжительность обновления

  • Количество обработанных элементов

  • Количество ошибок

  • Возможные сообщения об ошибках