Создание и сопровождение сайтов

Блог. Yii. Хорошие поведения - Behaviors в Yii

Поиск

Один из принципов ОО-программирования "Лучше использовать композицию, чем наследование". Воплотить этот принцип в жизнь нам помогают поведения (Behaviors).

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

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

В фреймворке yii есть готовая реализация поведения - CTimestampBehavior (это поведение используется в моделях наших продуктов)

  1. public function behaviors() {  
  2.     return array(  
  3.         'AutoTimestampBehavior' => array(  
  4.             'class' => 'zii.behaviors.CTimestampBehavior',  
  5.             'createAttribute' => null,  
  6.             'updateAttribute' => 'date_updated',  
  7.             'setUpdateOnCreate' => true,  
  8.         ),  
  9.     );  
  10. }  

 

Используя это поведение мы можем указать атрибуты, которые отвечают за дату обновления и дату создания. После чего поведение будет записывать необходимые данные в эти поля, избавляя нас от написания кода для этих атрибутов в каждой модели в отдельности.

Чтобы понять как это происходит глянем в код класса CTimestampBehavior:

  1. public function beforeSave($event) {  
  2.     if ($this->getOwner()->getIsNewRecord() && ($this->createAttribute !== null)) {  
  3.         $this->getOwner()->{$this->createAttribute} = $this->getTimestampByAttribute($this->createAttribute);  
  4.     }  
  5.     if ((!$this->getOwner()->getIsNewRecord() || $this->setUpdateOnCreate) && ($this->updateAttribute !== null)) {  
  6.         $this->getOwner()->{$this->updateAttribute} = $this->getTimestampByAttribute($this->updateAttribute);  
  7.     }  
  8. }  

Т.к. данный класс поведения наследуется от CActiveRecordBehavior, он реализует набор методов для обработки событий жизненного цикла ActiveRecord. В данном случае используется метод beforSave. Самое интересное в этом коде $this->getOwner() - это "магический" метод, позволяющий получить модель к которой мы "подцепили" данное поведение, т.е можно работать со свойствами и методами этой модели.

Сделаем для наших пользователей в ORE возможность загрузки аватара.

Воспользуемся готовым поведением fileimagearbehavior. Скачиваем данное поведение и распаковываем его в папку protected\components\behaviors\FileARBehavior. Создаем директорию для пользовательских аваторок uploads/users, выставляем на нее права - 777

Теперь нам нужно подключить это великолепное поведение к модели User, которая находится по адресу - /protected/models/User.php. Добавляем следующее строчки в класс User:

  1. public $avaImg;  
  2.   
  3. public function behaviors() {  
  4.     return array(  
  5.         'avaImgBehavior' => array(  
  6.             'class' => 'application.components.behaviors.FileARBehavior.ImageARBehavior',  
  7.             'attribute' => 'avaImg'// Эта переменная которую мы объявлили  
  8.             'extension' => 'png, gif, jpg'// Возможные расширения файла  
  9.             'prefix' => 'img_',  
  10.             'relativeWebRootFolder' => 'uploads/users'// this folder must exist  
  11.   
  12.             // 'forceExt' => png, // Если раскомментировать эту строчку, то изображения будут конвертироватся в png формат  
  13.   
  14.             //'useImageMagick' => '/usr/bin', # Поведение может использовать ImageMagick  
  15.   
  16.             // Определяем "форматы" в которых будут хранится изображения  
  17.             'formats' => array(  
  18.                 // ava_small - маленькая аватарка, ресайз изображения 90x90px  
  19.                 'ava_small' => array(  
  20.                     'suffix' => '_small',  
  21.                     'process' => array('resize' => array(90, 90)),  
  22.                 ),  
  23.                 // ava_big - аватарка побольше, ресайз изображения 200x200px  
  24.                 'ava_big' => array(  
  25.                     'suffix' => '_big',  
  26.                     'process' => array('resize' => array(200, 200)),  
  27.                 ),  
  28.                 // and override the default :  
  29.                 'normal' => array(  
  30.                     'process' => array('resize' => array(200, 200)),  
  31.                 ),  
  32.             ),  
  33.   
  34.             'defaultName' => 'default'// when no file is associated, this one is used by getFileUrl  
  35.             // defaultName need to exist in the relativeWebRootFolder path, and prefixed by prefix,  
  36.             // and with one of the possible extensions. if multiple formats are used, a default file must exist  
  37.             // for each format. Name is constructed like this :  
  38.             //     {prefix}{name of the default file}{suffix}{one of the extension}  
  39.         )  
  40.     );  
  41. }  

Читаем комментарии в коде, думаю там все понятно. Теперь нужно добавить правило для данного поля в метод rules():

  1. array('avaImg''file''types' => 'png, gif, jpg''allowEmpty' => true),  

С моделью мы разобрались. Теперь добавим изменения в форму в личном кабинете пользователя, файл - /protected/modules/usercpanel/views/_info.php В самом начале добавляем htmlOptions:

  1. $form=$this->beginWidget('CActiveForm'array(  
  2.     'enableAjaxValidation'=>false,  
  3.        'htmlOptions' => array('enctype'=>'multipart/form-data'),  

Добавляем поле, в котором пользоваетль будет выбирать картинку:

  1. <div class="row">  
  2.     <?php  
  3.     $avaSmallUrl = $model->avaImgBehavior->getFileUrl('ava_small');  
  4.     if($avaSmallUrl){  
  5.         echo CHtml::image($avaSmallUrl);  
  6.     }  
  7.     ?>  
  8.   
  9.     <?php echo $form->labelEx($model,'avaImg'); ?>  
  10.     <?php echo $form->fileField($model,'avaImg');; ?>  
  11.     <?php echo $form->error($model,'avaImg'); ?>  
  12. </div>  

В вышеприведенном коде, чтобы получить ссылку на картинку, пользуемся методом нашего поведния $avaSmallUrl = $model->avaImgBehavior->getFileUrl('ava_small'); и если картинка есть, то выводим ее в профиле. Добавим аватарку большего размера на страницу личного кабинета, в файле /protected/modules/usercpanel/views/index.php после заголовка h1 допишем:

  1. <div class="row">  
  2.     <?php  
  3.     $avaBigUrl = $model->avaImgBehavior->getFileUrl('ava_big');  
  4.     if($avaBigUrl){  
  5.         echo CHtml::image($avaBigUrl);  
  6.     }  
  7.     ?>  
  8. </div>  

Чтобы аватарка была видна не только самому пользователю, добавим ее на страницу объявления, рядом с "Телефон владельца" - /protected/modules/apartments/views/_view.php после строки c if(param('useShowUserInfo')) добавим вывод аватарки пользователя -

  1. <div>  
  2.     <?php  
  3.     $avaSmallUrl = $data->user->avaImgBehavior->getFileUrl('ava_small');  
  4.     if($avaSmallUrl){  
  5.         echo CHtml::image($avaSmallUrl);  
  6.     }  
  7.     ?>  
  8. </div>  

Итак, воспользовавшись готовым поведением мы добавили для каждого пользователя возможность загружать аватар. При этом не пришлось добавлять новое поле в базе данных. А при удалении пользователя поведение само удалит принадлежащие ему аватарки.