Последняя стабильная версия: 1.1.9

Рецепты

Система Orphus

DAO и модели

Active Record в Yii довольно мощный и гибкий. Для своей гибкости, ресурсов, при не очень большом количестве записей, он потребляет не так много, но в некоторых случаях AR может не подходить:

  • Отличное от БД хранилище данных (например, XML или Redis).
  • Производительность и потребление памяти при работе с большим количеством объектов.
  • Недостаточная гибкость (например, одновременная работа с несколькими хранилищами данных).

В этом уроке мы ни в коем случае не призываем отказываться от AR, а всего-лишь рассматриваем альтернативный способ построения моделей.

Модель

Для примера опишем сущность «фильм» в модели Film. При этом данные фильма могут храниться как в БД, так и в XML, кэше, Redis и т.д. В нашем случае, поскольку мы работаем с БД, один объект модели Film соответствует одной записи в бд.

class Film extends CModel {
    public $id;
    public $name;
    public $comments; // коллекция комментариев
 
    public function rules() {
        return array(
                array('name', 'required'),
                array('name', 'length', 'max'=>255),
        );
    }
 
    public function getName(){
        return $this->name;
    }
 
    public function getId(){
        return $this->id;
    }
 
    public function setName($newName){
        $this->name = $newName;
    }
 
    public function getComments(){
         return $this->comments;
    }
}

Фасад и фабрики

Далее создадим фасад FilmManager, реализующий все основные методы для работы с моделью, такие как:

  • getFilmById.
  • getFilmsByYear.
  • getLastFilms.
  • updateFilm.
  • и другие.

Данный класс позволяет прозрачно работать с реализованными в отдельных классах-фабриках методами для разных хранилищ данных (БД, XML, Sphinx и др.).

class FilmManager {
    public function __construct(){
 
    }
 
    public static function factory($driver = 'DB'){
        return new 'Film'.$driver.'Manager';
    }
 
    /**
     *
     * @param int $id
     * @return Film
     */
    public function getFilmById($id) {
    }
}

Создаём фабрики:

class FilmDBManager extends FilmManager {
    public function getFilmById($id) {
        $db = Yii::app()->db;
        $film_table = Film::tableName();
 
        $sql = "SELECT * FROM {$film_table} WHERE id=:ID LIMIT 1";
        $command=$db->createCommand($sql);
        $command->bindParam(":ID", $id);
        $row = $command->queryRow();
 
        $film = new Film;
        $film->id = $row['id'];
        $film->name_ru = $row['name_ru'];
 
        //получаем комментарии для фильма
        $film->comments = CommentsManager::getCommentsForFilm($id);
 
        return $film;
    }
}
class FilmXMLManager extends FilmManager {
    public  function getFilmById($id) {
       // берём из xml данные, создаем экземпляр класса Film
       return $film;
    }
}

Будем работать с фабриками через фасад:

// получаем фильм из БД
$film = FilmManager::factory()->getFilmById($id);
echo $film->getName();
 
foreach($film->getComments() as $comment){
    echo $comment->getText();
    echo $comment->getAuthor()->getName();
}
 
// получаем фильм из XML
$film = FilmManager::factory('XML')->getFilmById($id);
echo $film->getName();

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

Коллекция

В случае, когда метод фабрики возвращает не одну модель, а несколько, логично возвращать не массивы, а типизированные коллекции. Создадим свой тип коллекции для хранения фильмов:

class FilmCollection extends CList {
    // общее число фильмов
    private $totalCount;
 
    // Задаём тип параметра Film.
    // При попытке добаления объекта другого типа
    // произойдёт ошибка.
    public function add(Film $item){
        parent::add($item);
    }
 
    public function setTotal($count){
        $this->totalCount = $count;
    }
 
    public function getTotal(){
        return $this->totalCount;
    }
}