Створення розширень
===================

Оскільки створення розширень передбачає їх використання сторонніми розробниками, процес створення вимагає додаткових зусиль. Нижче наведені основні правила, яких необхідно дотримуватися при створенні розширень:

* розширення повинно бути самодостатнім, тобто залежність від зовнішніх ресурсів повинна бути мінімальна. Дуже незручно, коли для роботи розширення потрібно встановлювати додаткові пакети, класи та інші файли ресурсів;
* всі файли розширення повинні бути зібрані в одній директорії, імʼя якої має збігатися із назвою розширення;
* класи розширення повинні починатися з префікса, щоб уникнути конфліктів імен з класами інших розширень;
* розширення повинно включати детальну документацію по 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, зображення), що використовуються модулем, повинні поширюватися разом з модулем, а сам модуль повинен публікувати ресурси, щоб вони були доступні для веб-користувачів.

Загальний компонент
-------------------

Розробка загального компонента аналогічна написання класу. Компонент, як і модуль, повинен бути самодостатнім і зручним для використання розробниками.