Использование поведений Behavior в Yii
Это статья про Yii1, но в конце есть ссылка на вебинар по второй версии. А пока...
Большинство рецептов по Yii на этом сайте так или иначе сводятся к написанию поведения и подключению его к своему проекту. В обсуждении статьи о шаблонизаторе для вывода виджетов появился комментарий с просьбой подробно осветить работу с поведениями и, собственно, объяснить, для чего же они на самом деле нужны и почему их любят использовать некоторые продвинутые разработчики.
Быстрый поиск по существующим ресурсам показал, что по этой теме больше вопросов на форумах, чем ответов в блогах. Можно обратить внимание на этот материал, где описано подключение уже готового чужого поведения. В официальном руководстве тоже не очень много информации. Действительно, ни на странице об использовании, ни в теме о создании расширений нет подробного описания работы поведений и не раскрыты преимущества их использования. Большинство статей просто приводят пример подключения поведения CTimestampBehavior
. То есть, напишите такой угрюмый код:
class Post extends CActiveRecord { public function behaviors() { return array( 'CTimestampBehavior' => array( 'class'=>'zii.behaviors.CTimestampBehavior', 'setUpdateOnCreate'=>false, 'createAttribute'=>'created_at', 'updateAttribute'=>'updated_at', ), ); } }
...и ваши волосы станут нежными и шелковистыми...
Читатели же, видя это, чаще склоняются к старому доброму
class Post extends CActiveRecord { public function beforeSave() { if (parent::beforeSave()) { if ($this->isNewRecord) $this->created_at = time(); else $this->updated_at = time(); return true; } else return false; } }
Или, предвкушая комментарии о слишком вольном использовании пространства, можно предложить невменяемую альтернативу:
class Post extends CActiveRecord { public function beforeSave(){ $field = $this->isNewRecord ? 'created_at' : 'updated_at'; return parent::beforeSave() && $this->$field = time(); } }
Теперь перейдём к поведениям.
Введение в поведения на примере
В начале поймём суть того, что же называют «поведением».
Внутреннюю работу с поведениями обеспечивает класс CComponent
, от которого так или иначе наследуются все классы в Yii. Поэтому не важно, с чем мы будем экспериментировать. Для начала стоит сказать, что в Yii имеется базовый класс поведения CBehavior
, его наследник CModelBehavior
и наследник второго уровня CActiveRecordBehavior
, который дополнен обработчиками событий модели CActiveRecord
. Но в первом примере это пока не важно.
Итак, представим, что в нашем проекте есть несколько десятков моделей, и у пары из них нам нужно организовать поддержку отображения изображений. Не будем записывать здесь все стандартные методы модели. Покажем в примере только добавляемый нами код:
/** * @property string $image */ class News extends CActiveRecord { const IMAGE_PATH = 'upload/images/news'; }
/** * @property string $image */ class Post extends CActiveRecord { const IMAGE_PATH = 'upload/images/posts'; }
В поле image
модели в таблице будет храниться случайно сгенерированное имя файла вроде as0hd3ac.jpg
, и рядом с оригиналом будет лежать превью small_as0hd3ac.jpg
. Самого процесса загрузки пока касаться не будем.
Пусть в представлении нам нужно получать для вывода на странице полный адрес превью и оригинала этого изображения. Мы можем поступить так:
<a href="<?php echo Yii::app()->request->baseUrl . '/' . Post::IMAGE_PATH . '/' . $model->image; ?>"> <img src="<?php echo Yii::app()->request->baseUrl . '/' . Post::IMAGE_PATH . '/small_' . $model->image; ?>" alt="" /> </a>
Если кто-то скажет, что вместо вписывания тэга <img src="..." />
более красиво использовать CHtml::image(...)
и посоветует написать так:
<?php echo CHtml::link( CHtml::image(Yii::app()->request->baseUrl . '/' . Post::IMAGE_PATH . '/small_' . $model->image), Yii::app()->request->baseUrl . '/' . Post::IMAGE_PATH . '/' . $model->image );
то мы примем вызов и напишем вообще так:
<?php echo CHtml::link(CHtml::image($model->imageThumbUrl), $model->imageUrl);
Удобно и красиво.
Для работы этого варианта мы должны добавить соответствующие геттеры в наши модели:
/** * @property string $image */ class Post extends CActiveRecord { protected $path = 'upload/images/posts'; public function getImageUrl(){ return Yii::app()->request->baseUrl . '/' . $this->path . '/' . $this->image; } public function getImageThumbUrl(){ return Yii::app()->request->baseUrl . '/' . $this->path . '/small_' . $this->image; } }
Вынесем повторяющийся код во вспомогательный приватный метод getBaseImagePath()
и скопируем этот же код в другие модели:
/** * @property string $image */ class Post extends CActiveRecord { protected $path = 'upload/images/posts'; public function getImageUrl(){ return $this->getBaseImagePath() . $this->image; } public function getImageThumbUrl(){ return $this->getBaseImagePath() . 'small_' . $this->image; } private function getBaseImagePath(){ return Yii::app()->request->baseUrl . '/' . $this->path . '/'; } }
/** * @property string $image */ class News extends CActiveRecord { protected $path = 'upload/images/news'; public function getImageUrl(){ return $this->getBaseImagePath() . $this->image; } public function getImageThumbUrl(){ return $this->getBaseImagePath() . 'small_' . $this->image; } private function getBaseImagePath(){ return Yii::app()->request->baseUrl . '/' . $this->path . '/'; } }
Таким образом, чтобы везде работал метод getImageUrl()
, его нужно поместить во все необходимые модели. То есть мы получим десять копий одного и того же кода.
Как вариант решения этой проблемы можно предложить наследование. А именно создать базовый класс, поместить в него эти методы и наследовать нужные нам модели от него:
/** * @property string $image */ abstract class ImageModel extends CActiveRecord { protected $path = ''; public function getImageUrl(){ return $this->getBaseImagePath() . $this->image; } public function getImageThumbUrl(){ return $this->getBaseImagePath() . 'small_' . $this->image; } private function getBaseImagePath(){ return Yii::app()->request->baseUrl . '/' . $this->path . '/'; } }
class Post extends ImageModel { protected $path = 'upload/images/posts'; }
class News extends ImageModel { protected $path = 'upload/images/news'; }
Тогда эти методы появятся в каждой унаследованной от класса ImageModel
модели. Задача решена.
Но давайте помечтаем дальше. Чтобы не записывать каждый раз в контроллере вывода категорий блога, портфолио и интернет-магазина строку
$category = Category::model()->findByAttributes(array('alias'=>$alias));
мы можем добавить в модель категории метод findByAlias()
:
$category = Category::model()->findByAlias($alias);
А если у нас иерархические категории, то и findByPath()
:
$path = 'programming/flash/games'; $category = Category::model()->findByPath($path);
Аналогично оба метода можно вынести в базовый класс CategoryModel
и унаследовать нужные от него.
А теперь представим ситуацию, что в категории интернет-магазина тоже нужно загружать изображения. Мы могли бы это осуществить, унаследовав модель категории магазина от двух классов сразу: от ImageModel
и CategoryModel
, но PHP прямым образом не позволит нам этого сделать.
В версии 5.4 языка PHP появился механизм обхода запрета множественного наследования реализации посредством трейтов, что можно было бы использовать для этих целей, но они не так функциональны, как поведения.
Дальнейшие мечты подскажут нам вынести ещё какие-нибудь общие методы в новый класс и каким-либо образом подключить к нужным моделям.
Итак, мы столкнулись с необходимостью выносить группы методов в отдельные блоки и каким-либо образом подключить к нужным классам, не копируя их код. Именно для решения этой задачи служат поведения.
Перенос кода модели в поведение
Как мы упоминали, в Yii имеются три класса для поведений. Чаще используются универсальный CBehavior
и унаследованный от него CActiveRecordBehavior
.
Попробуем написать класс поведения для наших моделей и переместить туда наши методы:
class ImageBehavior extends CActiveRecordBehavior { public $imagePath = ''; public $imageField = ''; public function getImageUrl(){ return $this->getBaseImagePath() . $this->owner->{$this->imageField}; } public function getImageThumbUrl(){ return $this->getBaseImagePath() . 'small_' . $this->owner->{$this->imageField}; } private function getBaseImagePath(){ return Yii::app()->request->baseUrl . '/' . $this->imagePath . '/'; } }
Обратите внимание, что $this
в поведении ссылается на сам объект поведения. Чтобы обратиться к модели, к которой привязано поведение, нужно использовать $this->owner
или $this->getOwner()
.
Также для большей свободы использования мы заменили имя поля image
на публичную переменную $imageField
, чтобы его можно было менять. Аналогично можно создать поведение с методами для категорий.
Поведения у нас теперь есть, теперь мы можем «прицепить» их к любой нашей модели:
class Post extends CActiveRecord { public function behaviors(){ return array( 'imageBehavior' => array( 'class' => 'ImageBehavior', 'imagePath' => 'upload/images/posts', 'imageField' => 'image', ), ); } }
class Category extends CActiveRecord { public function behaviors(){ return array( 'imageBehavior' => array( 'class' => 'ImageBehavior', 'imagePath' => 'upload/images/categories', 'imageField' => 'image', ), 'categoryBehavior' => array( 'class' => 'CategoryBehavior', 'aliasField' => 'alias', ), ); } }
Имена поведений в строках
'imageBehavior' => array(...), 'categoryBehavior' => array(...),
чисто случайны. Их можно называть как угодно.
Теперь мы можем легко использовать «прикреплённые» методы так, как будто они есть в самой модели (или через имя поведения):
$model = Post::model()->findByPk($id); echo $model->getImageUrl(); echo $model->imageBehavior->getImageUrl();
Теперь рассмотрим процесс обработки событий модели CActiveRecord
в поведении.
Класс
CActiveRecordBehavior
включает в себя уже настроенный функционал для обработки событий модели, к которой его привязывают, а именноonBeforeSave
,onAfterSave
и подобных.
Мы условились не рассматривать процесс загрузки изображений. Сделаем исключение для загрузки файлов.
Пусть у нас есть модель с кодом для загрузки файлов:
class Post extends CActiveRecord { protected $filePath = 'upload/files'; public function rules(){ return array( array('file', 'file', 'types'=>'doc,xls,pdf', 'allowEmpty'=>true, 'safe'=>false), ); } protected function beforeSave(){ if (parent::beforeSave()) { if ($file = CUploadedFile::getInstance($this, 'file')){ $this->deleteFile(); $file->saveAs($this->filePath . '/' . $file->name); $this->file = $file->name; } return true; } return false; } protected function beforeDelete(){ if (parent::beforeDelete()) { $this->deleteFile(); return true; } return false; } public function deleteFile(){ unlink($this->filePpath . '/' . $this->file); $this->file = ''; } public function getFileUrl(){ return Yii::app()->request->baseUrl . '/' . $this->filePath . '/' . $this->file; } }
Если мы посмотрим исходный код CActiveRecordBehavior
, то увидим там список подключаемых обработчиков и заготовки методов:
class CActiveRecordBehavior extends CModelBehavior { ... public function events() { return array_merge(parent::events(), array( 'onBeforeSave'=>'beforeSave', 'onAfterSave'=>'afterSave', 'onBeforeDelete'=>'beforeDelete', 'onAfterDelete'=>'afterDelete', 'onBeforeFind'=>'beforeFind', 'onAfterFind'=>'afterFind', )); } protected function beforeSave($event) { } protected function afterSave($event) { } ... }
Чобы выполнить какой-либо процесс при наступлении определённого события, нам достаточно перекрыть нужный метод.
Мы можем безболезненно перенести наш код в отдельный класс поведения. При этом учтём, что вместо $this
нужно для обращения к самой модели использовать $this->owner
. В отличие от методов модели, в поведении нам не нужно возвращать true
из методов beforeSave()
и beforeDelete()
. Также эти методы в поведении должны быть публичными:
class FileBehavior extends CActiveRecordBehavior { public $filePath = ''; public $fileField = 'file'; public function beforeSave($event){ if ($file = CUploadedFile::getInstance($this->owner, $this->fileField)){ $this->deleteFile(); $file->saveAs($this->filePath . '/' . $file->name); $this->owner->{$this->fileField} = $file->name; } } public function beforeDelete($event){ $this->deleteFile(); } public function deleteFile(){ unlink($this->filePath . '/' . $this->owner->{$this->fileField}); $this->owner->{$this->fileField} = ''; } public function getFileUrl(){ return Yii::app()->request->baseUrl . '/' . $this->filePath . '/' . $this->owner->{$this->fileField}; } }
Теперь код нашей модели стал таким:
class Post extends CActiveRecord { protected $filePath = 'upload/files'; public function rules(){ return array( array('file', 'file', 'types'=>'doc,xls,pdf', 'allowEmpty'=>true, 'safe'=>false), ); } public function behaviors(){ return array( 'fileBehavior' => array( 'class' => 'FileBehavior', 'filePath' => 'upload/images/posts', 'fileField' => 'file', ), ); } }
В момент подключения поведения к компоненту (в нашем случае к модели) внутри него вызывается метод attach()
, в который передаётся сама модель параметром $owner
. Этот метод можно переопределить и произвести в нём, например, динамическое добавление правил валидации в модель для поля file
. Именно эти процессы производятся в полноценном поведении для загрузки файлов рецепта из официального руководства.
Подключение поведений к произвольным компонетам
Мы научились прикреплять поведения к моделям CActiveRecord
. Аналогично используя тот же метод behaviors()
мы можем подключать их и к компонентам, производным от классов CController
и CFormModel
.
Например, в вышеприведённом уроке мы подключили поведение к базовому контроллеру, чтобы во всех контроллерах стал доступным метод decodeWidgets()
.
Похожий функционал реализует и класс CApplicationComponent
, от которого наследуются компоненты нашего приложения. Вместо метода behaviors()
он содержит свойство behaviors
, которому можно присвоить массив поведений прямо в конфигурационном файле:
class MyBehavior extends CBehavior { public function updateLoginAt() { if ($this->owner->id) User::model()->updateByPk($this->owner->id, array('last_login_at'=>time())); } }
return array( 'components' => array( ... 'user' => array( 'allowAutoLogin' => true, 'loginUrl' => array('/site/login'), 'behaviors' => array ( 'myBehavior' => array( 'class' => 'MyBehavior', ), ), ), ), );
Теперь все методы класса MyBehavior
станут доступными внутри экземпляра класса CWebUser
, то есть мы в контроллере или в любом другом месте можем вызвать:
Yii::app()->user->updateLoginAt();
и это сработает.
Также через конфигурационный файл мы можем привязать поведение даже к самому экземпляру приложения:
return array( 'components'=>array(...), 'behaviors' => array ( 'myBehavior' => array( 'class' => 'MyBehavior', ), ) );
Это понадобилось нам, например, для подключения маршрутов модулей в рамках события onBeginRequest
приложения. По аналогии с исходным кодом CActiveRecordBehavior
мы должны указать отслеживаемые события в перекрытом методе events()
и вписать свой код в метод beginRequest()
:
class MyBehavior extends CBehavior { public function events() { return array_merge(parent::events(),array( 'onBeginRequest'=>'beginRequest', )); } public function beginRequest($event) { ... } }
Теперь наш код будет выполняться перед процессом разбора текущего URL.
Динамическое подключение поведений
Все рассматриваемые нами примеры реализовывали статическое подключение поведений (их список жёстко прописывался в коде). При создании экземпляра компонента автоматически создаются и экземпляры всех поведений.
Например, вызов
$posts = Post::model()->findAll()
произведёт выборку записей блога из базы данных, для каждой записи создаст экземпляр класса Post
и подключит к нему экземпляр поведения ImageBehavior
.
В этом ничего страшного нет, так как класс ImageBehavior
достаточно «лёгкий» и его методы для модели нужны всегда.
Другое дело, когда у нас есть очень большое поведение, которое, например, выполняет рутинную работу только при сохранении записи, и мы не хотим, чтобы оно подключалось каждый раз.
Для этих целей можно напрямую использовать методы класса CComponent
:
// добавление нескольких поведений attachBehaviors($behaviors) // удаление всех добавленных detachBehaviors() // добавление одного attachBehavior($name, $behavior) // удаление какого-либо одного detachBehavior($name) // включение всех добавленных enableBehaviors() // отключение всех добавленных disableBehaviors() // включение одного enableBehavior($name) // отключение одного disableBehavior($name)
Пусть у нас есть поведение для фильтрации текста:
class MyBehavior extends CActiveRecordBehavior { public function beforeSave($event){ $model = $this->getOwner(); $model->text = $this->processText($model->text); } private function processText($text){ return ...; } }
Мы не будем его описывать в модели:
class Post extends СActiveRecord { }
Вместо этого мы модифицируем экшен создания записи:
class PostController extends Ccontroller { public function actionCreate() { $model = new Post(); if (isset($_POST['Post'])) { $model->attributes = $_POST['Post']; $model->attachBehavior('myBehavior', array( 'class' => 'MyBehavior', )); $success = $model->save(); $model->detachBehavior('myBehavior'); if ($success){ Yii::app()->user->setFlash('post-form', 'Сохранено'); $this->redirect($model->url); } } $this->render('create', array('model'=>$model)); } }
Здесь мы вручную прикрепили к модели поведение, сохранили запись и сразу же удалили это поведение по его имени. Аналогично нужно изменить actionUpdate()
.
Имейте в виду, что ручное прикрепление мы производим уже после загрузки модели, следовательно методы
beforeFind()
иafterFind()
в динамически подключенном поведении не сработают.
Достоинства использования поведений
Когда у нас какой-либо функционал есть только в одной модели, то можно обойтись без поведений. А если похожих моделей много, то любую общую группу методов можно перенести в поведение и потом просто подключать к каждой нужной модели.
Также поведения удобны и для дистрибьюции расширений. Например, большое полноценное поведение DCategoryBehavior содержит около двадцати методов, а поведение для интеграции форума phpBB активно использует события beforeSave
и beforeDelete
модели User
. Такие объёмы кода логичнее распространять как сейчас отдельными библиотеками-поведениями и одним движением подключать парой строк к любой модели, чем прилагать здоровую инструкцию с текстом «откройте данный файл, скопируйте все имеющиеся там методы в свою модель, переименуйте такие-то параметры на имена аналогичных полей вашей модели, создайте в модели метод beforeSave
и напишите там...».
Удобно это и для упрощения разработки ваших проектов. Вот несколько полноценных библиотек, которые можно использовать на любом сайте:
Поведения для моделей:
- Загрузка файлов
- Поддержка нескольких языков
- Фильтрация текста
- Методы для простых и вложенных категорий
- Чекбоксы для множественного выбора категорий
- Снхронизация пользователей форума и сайта
Поведения для контроллера
Вы можете сделать у себя любые другие.
Предположим, что нам нужно создать на сайте новый раздел «Фильмы». Там должны быть сущности:
- фильмы (с описанием и картинкой),
- герои (с описанием и картинкой, множественный выбор),
- категории (вложенность, множественный выбор),
- жанры (множественный выбор),
Если раньше нам пришлось бы прописывать десятки одинаковых методов в каждой модели, то теперь достаточно просто сгенерировать через Gii модели Film и FilmHero, FilmGenre, FilmCategory, добавить связи и перечислить внутри них нужные поведения: для фильтрации описания, загрузки изображения, поддержки вложенных категорий, множественного выбора.
А недостаток один. Это создание экземпляров всех поведений для каждого экземпляра-владельца, что может повысить потребление памяти на пару мегабайт и снизить скорость работы с ActiveRecord моделью на несколько миллисекунд. Но на самом деле на этот недостаток редко обращают внимание.
И, кстати, поведения внутри себя используют события. Про них ещё есть отдельный пост.
UPD: Провели отдельный вебинар про поведения в Yii2.
Браво, Дмитрий! Спасибо за полноценное описание и качественные примеры. Наконец то ясность использования стала очевидной. Я бы сказал это единственный качественный труд столь насущной темы - что в нашем сегменте, что у иностранцев. Много перелопатил в поисках ответов, но ничего не найдя вернулся к "beforeSave" и им подобным.
Мне кажется, теперь после выхода данной статьи в инете появятся клоны по теме поведений, надеюсь хоть в душе спасибо Вам скажут.. :))
Я и не против того, чтобы клоны целого моего сайта появлялись для разных языков и фреймворков ))
Кстати, еще есть интересные для новичков темы по геттерам/сеттерам и скопам
Хоть, там особо и нечего писать, но тем не менее - на примерах (дыра в обучении), так же ничего не находил, приходилось разбираться в чужом коде что бы понять устройство. что как мне кажется не совсем верно.
Конкретно интересуют геттеры и сеттеры в Yii? Или вообще общие примеры использования getXxx и setXxx и __get и __set в разных языках?
Да, Дмитрий - я про yii. Есть опять же сухие описания, никаких примеров. Поэтому я думаю новички с удовольствием почитали бы об этом. еще бы по скоупам пробежаться.
Спасибо, мегаполезная статья. Да и весь ваш блог - зачитаешься)
Спасибо за статью, очень полезно, примеры из практики как раз то что надо! Стиль изложения информации доступный, прочитал и понятно, что к чему. После вашей статьи, теперь хочется "поведения" использовать почти везде )).
Радует то, что вы не только про поведения расписали, но и про геттеры, просто класс! Если будет время напишите про связи (relation, один-ко-многим, многие-ко-многим, при каких ситуациях, что лучше использовать и когда).
Понятие «связь» идёт из теории реляционных баз данных. Различные ORM разных фреймворков предоставляют похожие инструменты для работы с ними. Самоучитель по тем же ActiveRecord, Doctrine или Propel вводной информации по основам SQL и основам PHP не содержат, и для разработчика желательно уже уметь работать со связями на уровне SQL, а именно различать их типы и вручную строить запросы с JOIN.
Так что для знакомства со связями, нормальными формами и другими основными понятиями из теории реляционных баз данных лучше прочитать какие-нибудь книги по MySQL. После этого уже станут очевидными ответы на вопросы использования связей, псевдонимов, критерий, ленивых и жадных загрузок, именованных групп условий и прочих вещей внутри Yii или любого другого фреймворка на любом языке.
Присоединяюсь к благодарностям! Доступным, человеческим языком всё описано, разложено по полочкам. Есть приятные "отходы от темы".
Конкретно по этой статье тоже большая благодарность! В некоторые вещи внесена ясность!
А вы не могли бы написать статью, с такими интересными примерами, про события или про консольные команды? В каких случаях их уместно использовать, принцип работы..
Может напишу. Добавлю пока в список тем на будущее.
Афтар, возьмите коды те что вы выложили и проверьте там куча ашибак.
Исправил пару опечаток. Если найдёте ещё, то сообщите.
return this->getBaseImagePath() . 'small_' . $this->owner->{$this->imageField}; ага но еще забыли
Автор, проверьте свой комментарий, в нем куча ошибок.
Спасибо, здорово написано, все понятно!
Спасибо за познавательную статью!
Есть ли способ найти все модели которые используют определенное поведение ?
Как наиболее оптимальное решение на ум приходит подключение интерфейса-маркера одновременно с поведением и перебор классов:
или проходить по массиву $model->behaviors() и проверять их классы:
Классы можно получить, например, взяв список файлов в папках models.
Но это статические варианты. Если поведения подключается динамически, то можно использовать «утиную» типизацию. А именно создавать экземпляр и проверять, есть ли в нём какое-либо поле, имеющееся в поведении:
Также можно вызывать $model->asa('myBehavior'), но имя поведения пишется произвольно и может не совпадать с именем класса.
можно поставить IDE по поиску найти имя класса - выдаст все модели контроллеры и компоненты где этот класс упоминается :)
Дмитрий, спасибо Вам за статью, очень ценный труд, прочитал и с первого раза всё стало понятно, всё очень доступно и просто объяснено, примеры просто отличные.
Спасибо за статью. Замечательный блог. У вас появился еще один постоянный читатель.
Еще раз спасибо!
Дмитрий, спасибо за Ваш блог. Очень много полезной информации на пути изучения Yii. Жду с нетерпением новых статей)
Здравствуйте, Дмитрий.
Спасибо за статью!
Подскажите, как можно определить наличие метода (например "methodExample") , если указанный метод присоединяется с помощью поведения (например "MethodExampleBehavior") ?
Единственный способ, который я нашел, такой:
Но это криво.
Видимо это не предусмотрено. Можно было бы получить список поведений $_m из CComponent и самому проходить по нему с помощью method_exists() для вашего вопроса или get_class() для определения наличия определённого поведения. Но, увы, это поле приватное и геттера не имеет. Так что даже «костыль» с isset($object->methodExample()) подойдёт.
А можно behaviors вставить в CFormModel?
Да. Также, как и в CActiveRecord модель в методе behaviors().
Скажите, я правильно понял, что "поведение" вызывается до применения фильтров в контроллере? Т.е. поведением я при определенном условии смогу переопределить массив возвращаемый accessRules().
Задача состоит в том, что бы динамически определять разрешения в зависимости от того в какой группе пользователь состоит + какой контроллер используется.
В контроллере поведение вызывается по требованию внутри _call(), то есть если вызван несуществующий метод. Если у Вас есть поведение, подключенное к контроллеру, то можно просто переправить запрос на метод поведения:
Спасибо. Я правда реализовал немного по другому. От обобщающего подхода пришлось отказаться и по этому в каждом контроллере сделал так:
ну а в поведении уже собственно вся обработка данных и возврат массива.
Не знаю на сколько такое решение "по фэншую" :)
забыл добавить, что конечно же при обработке данных в поведении все "зарезервированные" если можно так сказать методы выбрасываются
Отлично описано! Спасибо!
Спасибо, очень погла статья!
Отличная статья!
Спасибо за отличное разьяснение.
В строчке
if ($file = CUploadedFile::getInstance($this->owner, $this->fileField))){
Лишняя ")"
Спасибо. Исправил.
Cпасибо, очень доходчиво, продолжайте!
Спасибо большое за внятное разъяснение!
Благодаря вам, я написал поведение, которое для меня по кр. мере и думаю для многих будет крайне полезным - большинству словян надо хранить даты и вытягивать даты в нашем формате а не западном - проблема типов DATE в MySQL решалась разными способами, но поведением, думаю получается наиболее элегантно:
теперь в любой модели, где есть DATE поля, просто прицепите:
и все волшебно заработает "как надо" :)
Пожалуйста, пересмотрите код и предложите "улучшения" или укажите на ошибки если есть.
Небольшой UPDATE:
чтобы сохранялись "пустые" (NULL) даты (если необходимо стирать иногда дату из Input) и правильно их обрабатывать пришлось добавить еще по одной строке в каждый их методов поведения (сразу после if ($column->dbType === 'date')):
Дмитрий, здравствуйте!
Не совсем понял как использовать в классе поведения метод beforeValidate().
В контроллере примерно так:
При этом методы поведения beforeSave(), afterSave() вызываются, также вызывается afterValidate(), но вот beforeValidate() не вызывается
А Вы не перекрывали метод beforeValidate в самой модели?
Да, забыл вызвать parent::beforeValidate() в beforeValidate() модели.....
Большое спасибо! Надо больше спать :)
Супер статья, спасибо Дмитрий!
Спасибо!!!
У меня вопрос - чем отличается CActiveRecordBehavior от СBehavior?
Различаются только набором отслеживаемых событий. CModelBehavior наследуется от CBehavior, но содержит ещё и встроенные обработчики afterConstruct, beforeValidate и afterValidate. В CActiveRecordBehavior аналогично добавлены события из CActiveRecord.
Вижу живое общение на тему поведения в yii. Дмитрий, может вы сможете помочь. Я расширяю модель своим поведением, т.е. появляется некий метод. Но вот беда, я не могу задать модели-родителю свойства из под поведения, т.е. хочу чтобы в модели-owner е появился аттрибут. Поведения типа CActiveRecordBehavior
Просто добавьте публичное поле в класс поведения или сделайте геттер+сеттер. Если хотите добавить в rules модели свой атрибут, то это можно сделать в метода attach():
Это если я правильно понял ваш вопрос.
Это в случае с валидатором, а что делать чтобы присвоить просто свойства модели, которых у нее нет?
Так? Чтобы в итоге потом можно было обратиться к ним:
$model->calls;
Тогда просто вот так:
И эти свойства появятся в модели.
К сожалению это не работает, я с самого начала такую конструкцию пробовал. Видимо есть какая то очередность инициализации параметров, как итог модель потом не видит этих свойств.
Очерёдность видна в методе __get($name) класса CActiveRecord и потом в аналогичном класса CComponent.
Добрый день,
подскажите пожалуйста, допустим есть несколько поведений, подключенных к одной модели, у каждого есть свой AfterSave и BeforeSave , AfterDelete и т.д. Допустим такие же методы лежат и в самой модели, как выявить к примеру порядок вызовов этих after Действий?
Поведения подключаются в том порядке, в котором они перечислены в массиве внутри behaviors().
Допустим, что в модель Вы добавили метод:
Вы выполняете какие-то свои действия и через parent вызываете CActiveRecord::afterSave, который выглядит так:
и который, как мы видим, запускает выполнение события 'onAfterSave'. Поведения при подключении подписывается на нужные им события (добавляют свои методы-обработчики в массив CComponent::_e), а метод CComponent::raiseEvent обходит этот массив циклом foreach и запускает каждый обработчик.
Так что в начале фреймворком запускается метод afterSave в модели. Если Вы его переопределили, то запускается именно ваш метод. А потом Вы сами вызываете parent::afterSave() и он уже запускает обработчики во всех поведениях по порядку подключения этих поведений.
Есть ли принципиальные отличия для статических методов, находящихся в поведении от обычных?
Почему то обращаюсь к статическому методу, который находится в поведении, при этом поведение к модели подключено и ловлю ошибку:
Call to undefined method Product::staticFunc()
Для перехвата статических методов нужно использовать магический метод __callStatic, а в CComponent есть только __call для нестатических методов. Так что можете либо добавить в модель метод __callStatic (но в нём всё равно нужно будет создавать экземпляр модели, чтобы как-то добраться до её поведений), либо просто сделать ваш метод нестатическим.
Спасибо!
Для добавления слушателя событий делать так
для замены слушателей события - так
для добавления в момент подключения
я правильно понял?
Да, можно так.
Дмитрий, большое спасибо, было очень полезно прочитать Вашу статью
Здорово, полезная статься
пытался прочитать на смартфоне 4", обрезаются вставки кода (
Отличнейшая статья!
Добрый день, Дмитрий,
Подскажите, Как в yii скормить модели картинки, без формы?
Столкнулся с проблемой при парсинге списка данных. В данных имеются ссылки на папки с картинками, которые необходимо загрузить вместе с данными.
Если использовать следующую конструкцию:
То файлы загрузятся, но дополнительно нужно будет прописать кучу логики на изменение размера миниатюры и т.д.
Я бы хотел, чтобы картинки при $model->save() загружались таким же образом, как это происходит при загрузке элемента через форму. Т.к. отрабатываются поведение для основной картинки и поведение для доп. изображений ( в частности метод поведений afterSave())..
Сами данные успешно сохраняются, но вот картинки не подкидываются. Может неправильно прописал данные в $_FILES? OS linux mint
Пробовал так:
Дмитрий, добрый день. Увидел битую ссылку в статье
... ни на странице об использовании, ни в теме о создании /a расширений нет ...
Можно заменить .ru на .com и будет работать.
Спасибо за статью
Исправил. Спасибо!
а поведение перезаписывает уже существующие методы?
Нет.