S2Документация → Разработка расширений

Разработка расширений

С технической точки зрения расширение S2 — это папка с несколькими файлами. Имя папки должно совпадать с идентификатором расширения. Устанавливаемые расширения помещаются в папку _extensions. Основной файл, присутствующий в каждом расширении, называется manifest.xml.

manifest.xml

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

Пример файла manifest.xml:

<?xml version="1.0" encoding="utf-8"?>

<extension for="S2" engine="1.0">
	<id>example_extension</id>
	<title>Пример расширения</title>
	<version>0.1</version>
	<description>Краткое описание расширения.</description>
	<author>Иван Иванов</author>
	<minversion>1.0a3</minversion>
	<maxtestedon>1.0a5</maxtestedon>
	<dependencies>
		<dependency>another_extension</dependency>
	</dependencies>
	<note type="install">Это сообщение выводится перед установкой расширения.</note>

	<install><![CDATA[
// Код установки
	]]></install>

	<uninstall><![CDATA[
// Код удаления
	]]></uninstall>

	<hooks>
		<hook id="idx_start"><![CDATA[
// Подключение файла foobar.php из папки расширения
require $ext_info['path'].'/foobar.php';
		]]></hook>

		<hook id="idx_template_pre_replace"><![CDATA[
// Вызов функции из файла foobar.php
example_extension_foo_function();
		]]></hook>
	</hooks>
</extension>

Рассмотрим подробнее формат этого файла.

Заголовки

<?xml version="1.0" encoding="UTF-8"?>

Это объявление XML, в котором говорится о версии XML и кодировке. S2 работает с кодировкой UTF-8, поэтому вы должны использовать её и сохранять файлы расширения в этой кодировке.

<extension for="S2" engine="1.0">

Здесь указывается движок (S2) и версия формата расширений. Версия может пригодиться в будущем, если движок существенно изменится и старые расширения станут несовместимыми. В настоящее время используется версия 1.0.

Информация о расширении

<id>example_extension</id>

Это идентификатор расширения. С его помощью S2 различает установленные расширения, поэтому идентификатор обязательно должен быть уникальным. Обратите внимание на то, что идентификатор расширения должен совпадать с именем папки расширения. Желательно использовать строчные латинские буквы и «_» (знак подчеркивания, без кавычек). Идентификатор не может быть длиннее 50 символов.

<title>Пример расширения</title>

Собственно, само название расширения. Можете придумать что-нибудь более оригинальное :)

<version>0.1</version>

Версия расширения. Придерживайтесь определенных правил нумерации расширений (см. ниже).

<author>Иван Иванов</author>

Ваше имя. Выдумывать не приходится :)

<minversion>1.0a3</minversion>
<maxtestedon>1.0a5</maxtestedon>

Минимальная версия движка, в которой заработает расширение, и максимальная версия, в которой оно было протестировано. Если движок слишком старый, в установке расширения будет отказано. Если расширение не протестировано на новой версии движка, при установке будет выдано предупреждение.

Сообщения пользователю

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

Примеры сообщений:

<note type="install">Это сообщение выводится перед установкой расширения.</note>
<note type="install">И это тоже.</note>
<note type="uninstall">Это сообщение выводится перед удалением расширения.</note>

Зависимости

Здесь указывается список расширений, без которых ваше расширение не сможет работать. Это полезно, если вы делаете расширение расширения или подключаете служебное расширение, содержащее какую-нибудь библиотеку.

<dependencies>
	<dependency>another_extension</dependency>
</dependencies>

Если хотя бы одно из расширений из этого списка отключено или не установлено, в установке вашего расширения будет отказано. А еще расширения из этого списка нельзя будет отключить или удалить, пока установлено ваше расширение.

Установка

<install><![CDATA[
// Код установки
]]></install>

Здесь можно указать код, который будет выполнен при установке. Вы можете, например, создать новую таблицу в базе данных или добавить новую колонку к существующей таблице.

В коде установки можно проверять, соответствуют ли параметры окружения системным требованиям. Если из секции установки возвращается строка, установка расширения останавливается, а строка выводится пользователю как сообщение об ошибке. Например, если расширение не работает на PHP 4, код установки можно начать с проверки версии PHP:

<install><![CDATA[
if (version_compare(PHP_VERSION, '5.0.0', '<'))
	return 'Error! This extension does not work on PHP 4. Please upgrade to PHP 5.';

// Теперь мы уверены, что версия PHP не ниже 5
]]></install>

Важное замечание, касающееся секции install, состоит в том, что она выполняется не только при установке, но и во время обновления расширения. Поэтому перед однократными действиями (вроде создания таблиц) нужно проверять, действительно ли это первая установка, или расширение обновляется. Для этого можно просто проверять, существуют ли уже создаваемые объекты, или использовать константу EXT_CUR_VERSION. Она определена, если расширение обновляется, и равна текущей установленной версии расширения.

Удаление

<uninstall><![CDATA[
// Код удаления
]]></uninstall>

Хуки

Хуки — это специальные «теги» в коде S2, вместо которых подставляются соответствующие фрагменты кода из manifext.xml. Обычно хук выглядит следующим образом:

($hook = s2_hook('idx_template_pre_replace')) ? eval($hook) : null;

Самое важное в такой записи — это имя хука. В этом примере приведен хук idx_template_pre_replace. Он находится в файле index.php (префикс «idx_»). Вместо того чтобы размещать какой-то фрагмент кода в этом месте файла index.php, его нужно поместить под соответствующим именем в секцию hooks manifest.xml.

	<hooks>
		<hook id="idx_start"><![CDATA[
// Подключение файла foobar.php из папки расширения
require $ext_info['path'].'/foobar.php';
		]]></hook>

		<hook id="idx_template_pre_replace"><![CDATA[
// Вызов функции из файла foobar.php
example_extension_foo_function();
		]]></hook>
	</hooks>

В этой секции должны быть описаны все хуки, используемые в расширении. Фрагменты PHP-кода экранируются разделом CDATA (располагаются между <![CDATA[ и ]]>). Они будут подставляться в код движка вместо соответствующих хуков. В этом примере первый хук idx_start просто подключает php-файл из папки расширения (для получения пути к папке расширения мы использовали $ext_info['path']). А второй хук idx_template_pre_replace вызывает функцию из подключенного файла.

С помощью хуков можно не только вызывать функции из внешних файлов, но и дополнять или заменять функциональность ядра S2.

Повторяющиеся хуки

Если несколько хуков имеют совпадающий код, их можно указать через запятую и избежать повторения кода.

<hooks>
	<hook id="idx_start, ai_start"><![CDATA[
// Какие-то общие действия для сайта и панели управления
	]]></hook>
</hooks>

Порядок выполнения хуков

Хукам можно назначать приоритет, что может быть полезно для предотвращения несостыковок с другими расширениями. Например, если ваш хук возвращает значение (через return), возможно, стоит сделать так, чтобы он сработал последним и не помешал выполнению таких же хуков других расширений.

Приоритет — это число от 1 до 10, где 1 соответствет выполняющемуся в первую очередь коду, а 10 — в последнюю. Если хуки двух расширений имеют одинаковый приоритет, порядок запуска определяется порядком установки. По умолчанию приоритет равен 5. Его можно указать явно:

<hooks>
	<hook id="idx_start" priority="2"><![CDATA[
// подключаем файл из папки расширения
require $ext_info['path'].'/foobar.php';
	]]></hook>
</hooks>

Особенности разработки расширений

Использование префиксов

Чтобы предотвратить конфликты имен объектов, добавляйте префикс в виде идентификатора расширения к названиям функций, глобальных переменных, хуков в глобальной области видимости, новых таблиц базы данных, новых полей в имеющихся таблицах, css-классов и т. д. К хукам внутри функций можно добавлять префикс вида fn_<название функции>_.

Каждый разработчик может добавлять свой префикс к идентификаторам своих расширений. Не используйте префикс «s2_», он обозначает «официальные» расширения.

Массив $ext_info

В хуках можно использовать переменную $ext_info, содержащую информацию о расширении:

$ext_info['id']
Идентификатор текущего расширения, например, example_extension.
$ext_info['path']
Путь к папке расширения относительно корня сайта, например, ./_extensions/example_extension. Полезно при подключении PHP-файлов.
$ext_info['url']
URL папки с расширением, например, http://example.com/_extensions/example_extension. Полезно при подключении к страницам стилей и картинок.

Область видимости хуков

Хуки встречаются в исходном коде S2 как в глобальной области видимости, так и внутри функций. В последнем случае код хуков выполняется в локальной области видимости. Это значит, что для обращения к глобальным переменным может потребоваться использование директивы global или массива $GLOBALS.

Массив $ext_info определяется в текущей области видимости хука. Это значит, что при вызове какой-то функции из хука, находящегося в другой функции, внути вызываемой функции нельзя получить доступ к переменной $ext_info даже при помощи директивы global.

Подключение файлов

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

Эта рекомендация обусловлена соображениями производительности. Код хуков хранится в файле _cache/cache_hooks.php и выполняется с помощью eval(). Чем меньше кода в хуках, тем меньше накладных расходов при подключении этого файла и тем быстрее выполняются конструкции eval(). К тому же подключение кода в include или require оптимизируется акселератором (акселераторы присутствуют в большинстве установок PHP). Другой аргумент заключается в упрощении отладки (см. ниже).

Обратите внимание на то, что рекомендация относится только к хукам, а не к коду установки или удаления. Более того, код удаления расширения нельзя выносить во внешние файлы. Дело в том, что код удаления вместе с другой информацией о расширении сохраняется во время установки в базе данных. При соблюдении этого требования расширение может быть корректно удалено даже в том случае, если на диске файлов расширения уже нет.

return внутри хуков

Значение, возвращаемое с помощью конструкции return, может обрабатываться в некоторых хуках. Рассмотрим для примера фрагмент файла index.php:

$return = ($hook = s2_hook('idx_get_content')) ? eval($hook) : null;
if (!$return)
{
	if (!defined('S2_ARTICLES_FUNCTIONS_LOADED'))
		require S2_ROOT.'_include/articles.php';
	s2_parse_page_url($request_uri);
}

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

Например, если в расширение добавить следующий хук, при обращении по адресу <адрес сайта>/secret_url мы увидим сообщение Secret text.

<hooks>
	<hook id="idx_get_content"><![CDATA[
if ($request_uri == '/secret_url')
{
	$page = array('text' => '<p>Secret text</p>');
	$template = s2_get_service_template();
	return true;
}
	]]></hook>
</hooks>

Языковые файлы

Желательно, чтобы расширения поддерживали смену языка. Если расширение содержит строки, которые можно переводить, они должны быть вынесены в языковой файл. Путь к языковому файлу может быть таким: _extensions/<ext_id>/lang/<Language>.php, например, _extensions/example_extension/lang/English.php. Сами строки лучше объединить в массив:

<?php
 
$lang_example_extension = array(
	'Example 1'   =>   'An example language string',
);

Для подключения языкового файла можно использовать такой код:

if (file_exists($ext_info['path'].'/lang/'.S2_LANGUAGE.'.php'))
	require $ext_info['path'].'/lang/'.S2_LANGUAGE.'.php';
else
	require $ext_info['path'].'/lang/English.php';

Номера версий

Номера версий расширений должны быть построены по тому же приципу, что и у S2, например 1.2.3. Здесь главное число — 1, второстепенное число — 2, номер выпуска — 3. Главное число увеличивается при полном переписывании, второстепенное — при добавлении функциональности, номер выпуска — после исправления мелких ошибок. Альфа- и бета-версии можно обозначать так: 1.0a3, 1.0b2.

Расширения должны быть отключаемыми

Расширения можно отключить из панели управления или с помощью константы S2_DISABLE_HOOKS. При этом код удаления расширений не вызывается, отключение касается только хуков. Нужно помнить об этом при разработке расширений. В частности, нельзя вносить разрушительные изменения в базу данных (удалять стандартные таблицы или параметры настройки, изменять форматы полей и т. д.).

Расширяемые расширения

Если вы планируете распространять будущее расширение, подумайте о задаче, которую оно будет решать. Желательно взять только одну задачу, но решить ее хорошо. Сложную систему можно разбить на несколько связанных расширений с применением механизма зависимостей. Например, интернет-магазин может быть отделен от системы приема платежей, оставаясь зависимым от нее. В этом случае интернет-магазин могут не устанавливать те пользователи, которым нужны только платежи.

Расширения сами по себе могут быть расширены. Добавляйте хуки в код своих расширений, чтобы другие разработчики смогли изменить их функциональность. Хуки расширений работают точно так же, как и обычные хуки. Чтобы убедиться, что расширяемое расширение установлено, можно использовать зависимости в manifest.xml.

Отладка

Из-за того что содержимое файла manifest.xml считывается один раз (при установке), отладка расширений затруднена. Основная трудность связана с тем, что любые изменения файла manifest.xml вступают в силу только после удаления и повторной установки расширения. Ее можно преодолеть с помощью расширения s2_manage_extensions, которое добавляет кнопку «Обновить хуки» в список расширений. После нажатия этой кнопки хуки перечитываются заново.

Комментарии

#1. 16 июля 2012 года, 22:06. WeslomPo пишет:
Не хватает списка хуков. Я так понял это типа «событий»?
#2. 17 июля 2012 года, 00:48. пишет:
Можно и так сказать, если считать, что выполнение определенной строчки кода — это событие.

В документации списка нет, потому что на текущей стадии бета-версии хоть я и расставил много хуков, но добавляю их по мере необходимости.

Список легко получить поиском по всем файлам фрагмента "$hook = s2_hook(". Но он на самом деле не спасет от необходимости заглядывать в код, потому что нужно знать не только положение хуков, но и то, какие действия можно совершить в хуках, например, изменить переменную, вернуть результат из функции и т. д.

Я могу помочь на форуме с поиском нужных хуков по описанию функциональности расширения.