Как создать Behavior для CakePHP

Как я уже писал, я люблю выносить обработку данных в модели. При этом у меня часто получаются модели со схожими функциями, как в одном проекте так и в разных. А меня очень удручает, когда надо писать один и тот же код по несколько раз, а от копирования кода меня просто передёргивает, потому что я уже вижу как через полгода буду себя проклинать. До CakePHP я в таких случаях просто создавал общего потомка и там реализовал общую функциональность. Недостаток этого метода - это то, что в PHP можно наследовать можно только один класс.

К счастью, разработчики CakePHP придумали как удобнее выносить одинаковую функциональность из моделей. Эта возможность появилась в CakePHP 1.2 и называется behaviors.

Возьмём, нашу модель Word. У меня часто бывают такие таблицы, которые состоят из id и name. Я покажу как сделать так, чтобы к другим моделям такого типа можно было применять общую функциональность. Но при этом мы можем указать в каждой конкретной модели, какие behaviors к ней подключать. Самое удобное, что можно подключать их несколько, а не только одну.

Модель выглядела так
app/models/word.php

PHP:
  1. <?php
  2. class Word extends AppModel {
  3.     var $name = 'Word';
  4.     var $validate = array(
  5.         'id' => VALID_NUMBER,
  6.         'name' => VALID_NOT_EMPTY,
  7.     );
  8.  
  9.     /**
  10.      * Adds a word and returns id
  11.      *
  12.      * @param string $name
  13.      * @return integer If failed to save returns false
  14.      */
  15.     function add($name) {
  16.         $item = array(
  17.             'name'  => $name,
  18.         );
  19.         $this->create();
  20.         if ($this->save($item)) {
  21.             return $this->getInsertId();
  22.         } else {
  23.             return false;
  24.         }
  25.     }
  26.  
  27.     /**
  28.      * Generates list of lowercase names and its ids
  29.      *
  30.      * @return array (a(name=>id), ...)
  31.      */
  32.     function generateListFlipLow(&$model) {
  33.         $items = $this->generateList();
  34.         foreach ($items as $id=>$name) {
  35.             $items[$id] = low($name);
  36.         }
  37.         return array_flip($items);
  38.     }
  39. }
  40. ?>

С применением behavior этот же класс будет выглядеть так

PHP:
  1. <?
  2. class Word extends AppModel {
  3.  
  4.     var $name = 'Word';
  5.     var $validate = array(
  6.         'id' => VALID_NUMBER,
  7.         'name' => VALID_NOT_EMPTY,
  8.     );
  9.     var $actsAs = array('NameList');
  10. }
  11. ?>

И всё. Через запятую можно указать несколько behaviors. Если надо передать настройки в behavior, то надо писать

PHP:
  1. var $actsAs = array('NameList'=>array('setting1'=>'value1', 'setting2'=>'value2'));

Сами behaviors находятся в app/models/behaviors. Для нашей модели - это
app/models/behaviors/name_list.php

PHP:
  1. <?
  2.  
  3. class NameListBehavior extends ModelBehavior {
  4.  
  5.     var $settings = array();
  6.  
  7.     function setup(&$model, $config = array()) {
  8.         $this->settings = $config;
  9.     }
  10.  
  11.     /**
  12.      * Adds a word and returns id
  13.      *
  14.      * @param string $name
  15.      * @return integer If failed to save returns false
  16.      */
  17.     function add(&$model, $name) {
  18.         $item = array(
  19.             'name'  => $name,
  20.         );
  21.         $model->create();
  22.         if ($model->save($item)) {
  23.             return $model->getInsertId();
  24.         } else {
  25.             return false;
  26.         }
  27.     }
  28.  
  29.     /**
  30.      * Generates list of lowercase names and its ids
  31.      *
  32.      * @return array (a(name=>id), ...)
  33.      */
  34.     function generateListFlipLow(&$model) {
  35.         $items = $model->generateList();
  36.         foreach ($items as $id=>$name) {
  37.             $items[$id] = low($name);
  38.         }
  39.  
  40.         return array_flip($items);
  41.     }
  42.  
  43. }
  44.  
  45. ?>

В методе setup мы настраиваем behavior. В $config передаются настройки, которые можно указать в модели.
Во всех методах добавляется первый параметр $model. Если вы переносите методы из модели, то надо не забыть все $this поменять на $model.

Как видите, создавать behaviors очень просто. Особенно, если мы заранее создали тест и можем автоматически убедиться, что всё по-прежнему работает :)


Понравилось?

  1. Подпишись через RSS
  2. Расскажи о http://php.southpark.com.ua друзьям.
    Все способы хороши: ICQ, E-mail, свой блог, комментарий в чужом блоге или сообщение на форуме
  3. Добавь статью на news2.ru, Хабрахабр или в закладки

Огромное спасибо!

И не стесняйтесь комментировать - у меня стоит плагин, который убирает rel="nofollow" у людей, которые написали больше 5 комментариев.

RSS feed | Trackback URI

2 комментария »

Comment by Евгений
2007-12-09 22:45:54

Владимир, несколько замечаний.
1) Методы с общими именами типа add в поведениях лучше не использовать. Это связано с тем, что кейк будет вызовет метод с таким именем из первого по списку поведения.
2) $this->settings = $config;
На самом деле нужно хранить конфигурации вех моделей, так как в реестре классов будет храниться только один экземпляр.
Поэтому используем $this->settings[$model->alias]. Причем именно $model->alias а не $model->name, поскольку разработка ядра это рекомендует.

 
2007-12-09 23:41:08

Евгений, огромное спасибо, второй пункт для меня - открытие.

А add нигде в Cake не используется - у меня таких Behavior для однотипных объектов много и везде называется одинаково, я уже привык, что можно чуть проще создавать записи для простых таблиц. Я вот доведу до ума и выложу свой ExtTreeBehavior, который работает в связке с ExtJs (поддерживается json вывод записей с подгрузкой только нужных, переименование, правильное удаление и перетягивание с учётом порядка).

Хотя, действительно, стоило бы придумать менее распространённое название вроде addItem. У меня есть негативный опыт создания в моделях метода getId(), который перекрывает метод предка - Model. Самое обидное, что это уже несколько раз случалось и каждый раз тратил по 15 минут с дебаггером, чтобы найти причину. :)

 
Имя (required)
E-mail (required - never shown publicly)
URL
Текст комментария
You may use <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> in your comment.