Триггером называется метод XS2, выполняющийся на узле при его вставка, редактировании, удалении, копировании или перемещении. Многим хорошо знакомы триггеры, использующиеся в базах данных при вставке, изменении или удалении записи. Аналогия с триггерами в БД в данном случае очевидна и полезна. Практически ни один из серьезных веб-проектов при реализации его на платформе XS2 не обходится без триггеров.
Для демонстрации мы выбрали одну из самых распространенных задач, решаемых с помощью триггеров, - генерацию превью изображений. Представим, что на нашем сайте есть тип узла "photo", с помощью которого в системе представлены фотографии, загружаемые пользователем в свою личную фотогалерею. Среди прочих полей у данного типа имеются два поля типа "image": "Thumbnail" и "FullImage". При добавлении нового узла мы будем сохранять файл фотографии, посланный пользователем, в поле "FullImage", а в поле "Thumbnail" помещать сгенерированное налету превью размером 150 пикселей по большей стороне.
Для того, чтобы создать триггер, необходимо добавить новый метод на произвольном типе узлов. В форме добавления/изменения метода необходимо галочками указать, при каком из пяти действий будет срабатывать триггер. В версии 2 платформы XS2 существует также возможность указать, будет ли срабатывать триггер до или после выполнения действия. В версии 1 платформы XS2 триггер всегда вызывается после действия, и это накладывает определенные ограничения на его функциональность. Во версии 2 разработчик имеет возможность назначить один и тот же метод триггером для нескольких типов. В версии 1 для повторного использования одного метода в разных триггерах можно воспользоваться функцией xs2Fetch.
Важно помнить, что метод, выполняющий роль триггера, является обыкновенным методом XS2, за исключением следующих отличий:
Он вызывается автоматически во время определенных действий, производимых с узлом
Он может быть прикреплен только к типу узла
В нем доступна переменная $THE['NODE'], содержащая текущий узел, с которым производится действие, и некоторые дополнительные поля
Так же, как и обычный метод, триггер можно вызвать через браузер, - эта возможность особенно полезна при отладке. Чаще всего триггер состоит только из процессора, что объясняется его специфической функцией, однака, ничто не препятствует наличию у него шаблона.
Итак, добавим триггер с произвольным именем и прикрепим его к нашему типу "photo". Галочками отметим, что он должен выполнятся при вставке нового узла (действие "UPDATE"). Далее напишем в нем следующий код:
В листинге используется функция xs2u_imgResize из XS2 API, которая создает превью на основе полной версии. Полное описание этой функции можно посмотреть в справочнике XS2 API.
Рассмотрим дополнительные поля, доступные в $_THE['NODE']:
В первой версии XS2 в триггере через переменную $_THE['NODE'] доступны следующие дополнительные поля, помимо полей узла:
$_THE['NODE']['EVENT'] - название текущего действия, производимого над узлом. Это поле может принимать следующие строковые значения: "insert" (вставка узла), "update" (редактирование узла), "delete" (удаление узла), "copy" (копирование узла), "move" (перемещение узла в другого родителя)
$_THE['NODE']['PARAMS']['NodId'] - это поле доступно только при действии "copy" и содержит идентификатор исходного узла, копией которого является текущий. Обратите внимание, что поле $_THE['NODE']['NodId'] при этом содержит идентификатор только что вставленной копии.
$_THE['NODE']['PARAMS']['ParId'] - это поле доступно только при действии "move" и содержит идентификатор узла, который был родительским для текущего узла до его перемещения. Обратите внимание, что поле $_THE['NODE']['ParId'] содержит идентификатор родителя, в который только что переместился текущий узел.
Во второй версии XS2 доступны следующие дополнительные поля:
$_THE['NODE']['EVENT'] - название текущего действия, производимого над узлом. Это поле может принимать следующие строковые значения: "insert" (вставка узла), "update" (редактирование узла), "delete" (удаление узла), "copy" (копирование узла), "move" (перемещение узла в другого родителя)
$_THE['NODE']['POINT'] - точка (момент) выполнения триггера. Данное поле принимает следующие строковые значение: "before" (до действия), "after" (после действия). С помощью этого поля можно узнать, выполняется ли текущий триггер до или после действия.
$_THE['NODE']['OLD'] - содержит образ текущего узла, каким он был до начала текущего действия. Таким образом, данное поле содержит в себе в том числе и ту информацию, которая в первой версии передавалась через поля $_THE['NODE']['PARAMS']['NodId'] и $_THE['NODE']['PARAMS']['ParId']. Эти поля сохранены во второй версии платформы для обратной совместимости, однако их использование нежелательно.
Во второй версии XS2 разработчик может определять точку вызова триггера: до или после действия. Важно помнить, что в триггерах, выполняющихся до действия (претриггер), все изменения, внесенные в $_THE['NODE'], не сохранятся. Таким образом изменения полей узла возможно только в триггере, выполняющемся после действия (посттриггер). Претриггер используется чаще всего для проверки данных, предназначенных для сохранения в узле. Поскольку действие еще не произошло, в претриггере можно отменить его, выбросив исключение XS2_Exception() с произвольным текстом ошибки. Приведем пример отмены вставки узла. Следующий листинг содержит код претриггера, выполняющегося до вставки нового узла типа "Пользователь". Он проверяет, есть ли в системе пользователь с таким же электронным адресом и, если есть, отменяет вставку узла.
Одна из потенциальных опасностей использования триггеров - возникновение цикличных вызовов. Рассмотрим следующий листинг. Это триггер, который выполняется при создании нового узла типа "category":
Суть этого триггера заключается в том, что он создает дополнительную подкатегорию при создании категории. Казалось бы, ничего страшного в коде нет, но давайте представим, что произойдет при добавлении новой категории. Сработает наш триггер и добавится новая подкатегория, однако при ее создании снова сработает триггер и создастся подкатегория подкатегории. Создание подподкатегории снова приведет к вызову триггера и т.д. Возникнет бесконечный цикл или клинч (deadlock), весьма опасный для функционирования системы. Для того, чтобы этого не происходило, в XS2 запрещен вызов триггеров внутри других триггеров. То есть код, приведенный в Листинге 25, в XS2 сработает как ожидается, - будет создана всего одна подкатегория.
Иногда защита от цикличного вызова триггеров в XS2 становится причиной замешательства разработчиков. Забыв о ней, они рассчитывают на цикличность выполнения триггеров, самостоятельно контролируя клинч. В результате они тратят большое количество времени на поиск причины, по которой триггер не срабатывает внутри другого триггера. Причина же - встроенный контроль клинча XS2. Как же тогда добиться эффекта цикличности, когда это, действительно, необходимо? В большинстве случаев цикличный вызов многих триггеров можно заменить простым перебором необходимых узлов. Приведем пример. У нас есть большой каталог товаров. При добавлении новой категории небходимо увеличить значение поля "ChildrenCounter" на единицу у всех ее категорий-предков. Если бы в XS2 не было защиты от клинча, то код триггера, срабатывающего при добавлении и - что очень важно - изменении категории, выглядел бы так:
Однако, XS2 заблокирует вызов триггера внутри другого триггера, и такой код приведет к тому, что счетчик увеличится только у непосредственного родителя текущей категории. Решить поставленную задачи можно следующим образом:
Мы просто вернули всех предков текущего узла с помощью функции XS2 API xs2GetPath и пересчитали счетчик для каждого из них.
Мы рассмотрели триггеры - один из мощнейших инструментов добавления собственной функциональности в XS2. Однако большие возможности триггеров влекут за собой и большую ответственность. Разрабатывая код триггеров, необходимо помнить о том, что он, скорее всего, будет использоваться достаточно часто, т.е. для каждого узла данного типа, поэтому он должен быть предельно оптимален.