Создание расширений =================== Поскольку создание расширений подразумевает их использование сторонними разработчиками, процесс создания требует дополнительных усилий. Ниже приведены основные правила, которые необходимо соблюдать при создании расширений: * расширение должно быть самодостаточным, т.е. зависимость от внешних ресурсов должна быть минимальна. Очень неудобно, когда для работы расширения требуется устанавливать дополнительные пакеты, классы и иные файлы ресурсов; * все файлы расширения должны быть собраны в одной директории, имя которой должно совпадать с названием расширения; * классы расширения должны начинаться с префикса, чтобы избежать конфликтов имён с классами других расширений; * расширение должно включать подробную документацию по его установке и API, чтобы сократить время, затрачиваемое другими разработчиками на его подключение и изучение; * расширение должно использовать надлежащую лицензию. Если вы хотите, чтобы ваше расширение могло быть использовано как открытыми, так и закрытыми проектами, вы можете воспользоваться лицензиями BSD, MIT и др., но не GPL, т.к. последняя требует, чтобы код, в котором используется ваше расширение, также был открыт. Ниже мы расскажем о том, как создать новое расширение в соответствии с классификацией, приведённой в [обзоре](/doc/guide/extension.overview). Пояснения в равной степени распространяются и на расширения, используемые исключительно в собственных проектах. Компонент приложения --------------------- [Компонент приложения](/doc/guide/basics.application#application-component) должен реализовывать интерфейс [IApplicationComponent] или расширять класс [CApplicationComponent]. Основной метод, который необходимо реализовать, — [IApplicationComponent::init]. В этом методе происходит инициализация компонента. Метод вызывается после того, как компонент создан и установлены начальные значения, указанные в [конфигурации приложения](/doc/guide/basics.application#application-configuration). По умолчанию компонент приложения создаётся и инициализируется только в момент первого обращения к нему в ходе обработки запроса. Если необходимо принудительно создавать компонент сразу после создания экземпляра приложения, то нужно добавить идентификатор этого компонента в свойство [CApplication::preload]. Поведение --------- Для того чтобы создать поведение, необходимо реализовать интерфейс [IBehavior]. Для удобства в Yii имеется класс [CBehavior], реализующий этот интерфейс и предоставляющий некоторые общие методы. Наследуемые классы могут реализовать дополнительные методы, которые будут доступны в компонентах, к которым прикреплено поведение. При разработке поведений для [CModel] и [CActiveRecord] можно наследовать классы [CModelBehavior] и [CActiveRecordBehavior] соответственно. Эти базовые классы предоставляют дополнительные возможности, специально созданные для [CModel] и [CActiveRecord]. К примеру, класс [CActiveRecordBehavior] реализует набор методов для обработки событий жизненного цикла ActiveRecord. Наследуемый класс, таким образом, может перекрыть эти методы и выполнить код, участвующий в жизненном цикле AR. Следующий код демонстрирует пример поведения ActiveRecord. Если это поведение прикреплено к объекту AR и вызван метод `save()`, атрибутам `create_time` и `update_time` будет автоматически присвоено текущее время. ~~~ [php] class TimestampBehavior extends CActiveRecordBehavior { public function beforeSave($event) { if($this->owner->isNewRecord) $this->owner->create_time=time(); else $this->owner->update_time=time(); } } ~~~ Виджет ------ [Виджет](/doc/guide/basics.view#widget) должен расширять класс [CWidget] или производные от него. Наиболее простой способ создать виджет — расширить существующий виджет и переопределить его методы или заменить значения по умолчанию. Например, если вы хотите заменить CSS стиль для [CTabView], то, используя виджет, нужно установить свойство [CTabView::cssFile]. Можно также расширить класс [CTabView], чтобы при использовании виджета не требовалась постоянная конфигурация, следующим образом: ~~~ [php] class MyTabView extends CTabView { public function init() { if($this->cssFile===null) { $file=dirname(__FILE__).DIRECTORY_SEPARATOR.'tabview.css'; $this->cssFile=Yii::app()->getAssetManager()->publish($file); } parent::init(); } } ~~~ Выше мы переопределяем метод [CWidget::init] и, если свойство [CTabView::cssFile] не установлено, присваиваем ему значение URL нового CSS стиля по умолчанию. Файл нового CSS стиля необходимо поместить в одну папку с файлом класса `MyTabView`, чтобы их можно было упаковать как расширение. Поскольку CSS стиль не доступен из веб, его необходимо опубликовать как ресурс. Чтобы написать виджет с нуля, нужно, как правило, реализовать два метода: [CWidget::init] и [CWidget::run]. Первый метод вызывается, когда мы используем конструкцию `$this->beginWidget` для вставки виджета в представление, а второй — когда используется конструкция `$this->endWidget`. Если необходимо получить и обработать содержимое, заключённое между вызовами этих двух функций, то можно запустить [буферизацию вывода](http://php.net/manual/en/book.outcontrol.php) в [CWidget::init] и получать сохранённый вывод для дальнейшей обработки в методе [CWidget::run]. Когда виджет используется на странице, он обычно подключает CSS стили, JavaScript файлы и прочие файлы ресурсов. Файлы такого рода называются *ресурсы*, поскольку они хранятся вместе с файлом класса виджета и, как правило, не доступны веб-пользователям. Для того чтобы они стали доступны пользователям, их необходимо опубликовать, используя [CWebApplication::assetManager], как показано во фрагменте кода выше. Помимо этого, если необходимо подключить файлы CSS стилей или JavaScript на текущей странице, их необходимо зарегистрировать посредством [CClientScript]: ~~~ [php] class MyWidget extends CWidget { protected function registerClientScript() { // …подключаем здесь файлы CSS или JavaScript… $cs=Yii::app()->clientScript; $cs->registerCssFile($cssFile); $cs->registerScriptFile($jsFile); } } ~~~ Виджет также может иметь собственные файлы представлений. В этом случае необходимо создать директорию `views` внутри директории, содержащей файл класса виджета, и поместить в неё все файлы представлений. Для подключения представлений виджета в его классе используется конструкция `$this->render('ViewName')`, аналогичная соответствующему методу контроллера. Действие ------ [Действие](/doc/guide/basics.controller#action) должно расширять класс [CAction] или производные от него. [IAction::run] — основной метод, который необходимо реализовать для действия. Фильтр ------ Фильтр должен расширять класс [CFilter] или производные от него. Основными методами, которые необходимо реализовать, являются [CFilter::preFilter] и [CFilter::postFilter]. Первый вызывается до выполнения действия, второй — после. ~~~ [php] class MyFilter extends CFilter { protected function preFilter($filterChain) { // применяется до выполнения действия return true; // значение false возвращается, если действие не должно выполняться } protected function postFilter($filterChain) { // применяется после завершения выполнения действия } } ~~~ Параметр `$filterChain` — экземпляр класса [CFilterChain], содержащий информацию о действии, к которому применяются фильтры в настоящий момент. Контроллер ---------- [Контроллер](/doc/guide/basics.controller), распространяемый как расширение, должен наследовать класс [CExtController], а не класс [CController]. Основной причиной этого является то, что для класса [CController] предполагается, что файлы представлений располагаются в `application.views.ControllerID`, а для [CExtController] считается, что файлы представлений находятся в директории `views`, расположенной в той же директории, что и файл класса этого контроллера. Очевидно, что расширение-контроллер удобнее распространять, когда все файлы расширения собраны в одном месте. Валидатор --------- Валидатор должен расширять класс [CValidator] и реализовывать его метод [CValidator::validateAttribute]. ~~~ [php] class MyValidator extends CValidator { protected function validateAttribute($model,$attribute) { $value=$model->$attribute; if($value has error) $model->addError($attribute,$errorMessage); } } ~~~ Команда консоли --------------- [Консольная команда](/doc/guide/topics.console) должна расширять класс [CConsoleCommand] и реализовывать его метод [CConsoleCommand::run]. При желании можно переопределить метод [CConsoleCommand::getHelp], который отвечает за информационную справку по команде. ~~~ [php] class MyCommand extends CConsoleCommand { public function run($args) { // $args — массив аргументов, переданных с командой } public function getHelp() { return 'Usage: how to use this command'; } } ~~~ Модуль ------ Информация о порядке создания и использования модулей представлена в разделе [Модуль](/doc/guide/basics.module#using-module). Если сформулировать требования в общем виде, то модуль должен быть самодостаточным, файлы ресурсов (CSS, JavaScript, изображения), используемые модулем, должны распространяться вместе с модулем, а сам модуль должен публиковать ресурсы, чтобы они были доступны для веб-пользователей. Компонент общего вида ----------------- Разработка компонента общего вида аналогична написанию класса. Компонент, как и модуль, должен быть самодостаточен и удобен для использования другими разработчиками.