Как выглядит Model если используешь DAO

Обсуждение документации. Переводы Cookbook и авторские рецепты.
carlton
Сообщения: 7
Зарегистрирован: 2009.12.10, 00:05

Как выглядит Model если используешь DAO

Сообщение carlton »

Подскажите, правильно ли использовать в моделе

Код: Выделить всё

class Something extends CActiveRecord
если не планируется использовать AR в проекте?
Как правильно оформить код в Model если используешь DAO?
Спасибо.

Тема переросла в полноценный рецепт:
http://yiiframework.ru/doc/cookbook/ru/model.dao
Аватара пользователя
slavcodev
Сообщения: 3134
Зарегистрирован: 2009.04.02, 21:42
Откуда: Valencia
Контактная информация:

Re: Как выглядит Model если используешь DAO

Сообщение slavcodev »

если АР не нужен тогда уж наверное можно

Код: Выделить всё

class Something extends CModel
а дальше напсиать методов save, update и тд
Жду Yii 3!
pirrat
Сообщения: 193
Зарегистрирован: 2009.04.03, 09:41

Re: Как выглядит Model если используешь DAO

Сообщение pirrat »

рассказываю как делаю я и мои коллеги ...
считаю такой вариант наиболее верным, если кто то не согласен ,готов подискутировать! =)

Допустим есть у нас сущность фильм, эту сущность мы описываем в модели Film.
Объект фильма ни какого отношения к БД не имеет, а данные объекта могут храниться где угодно: бд, кэш, xml, сфинкс etc

В нашем случае(поскольку мы работаем с бд) один объект модели Film соответствует одной записи в бд.

например будет выглядеть она у нас так:
<?php

Код: Выделить всё

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
getLastFims
updateFilm
etc


в этом классе я уже работаю с хранилищами данных (бд, xml, etc) и создаю инстансы модели Film

вот пример с 2 методами

Код: Выделить всё

class FilmManager {

    /**
     *
     * @param int $id
     * @return Film
     */
    public static 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 = $row['name'];

        //получаем комментарии для фильма
        $film->comments = CommentsManager::getCommentsForFilm( $row['id']);

        return $film;

    }

   /**
    *
    * @param string $year
    * @return array массив объектов Film 
    */
    public static function getFilmByYear($year) {
        $cl = new SphinxClient ();
        $cl->SetServer ( 'localhost', 3312 );
        $cl->SetConnectTimeout ( 1 );
        $cl->SetArrayResult ( true );
        $cl->SetMatchMode ( SPH_MATCH_ALL );


        $cl->SetFilter("year",$year);


        $res = $cl->Query ( $search, "filmIndex" );

        $ids = array();
        foreach($res['matches'] as $match) {
            array_push($ids, $match['id']);
        }

        $db = Yii::app()->db;
        $film_table = Film::tableName();

        $sql = "SELECT * FROM {$film_table} WHERE id IN (:IDS)";
        $command=$db->createCommand($sql);
        $command->bindParam(":IDS", implode(',',$ids));
        $rows = $command->queryAll();

        $films = array();

        foreach($rows as $row) {
            $film = new Film;
            $film->id = $row['id'];
            $film->name_ru = $row['name_ru'];

            array_push($films, $film);
        }

        return $films;

    }
} 
getFilmById берет данные из бд
а getFilmsByYear берет данные из sphinx

но возвращают они оба инстансы модели Film!

в контроллере (да где угодно) мы уже работаем с моделями через фасад:

Код: Выделить всё

           
            $film = FilmManager::getFilmById(1);
            echo $film->getName();

            foreach($film->getComments() as $comment)
            {
                  echo $comment->getText();
                  echo $comment->getAuthor()->getName();
            } 
как то так, жду комментов.

зы: мб в рецепты отправить?!
pirrat
Сообщения: 193
Зарегистрирован: 2009.04.03, 09:41

Re: Как выглядит Model если используешь DAO

Сообщение pirrat »

ок, пока никто не прокомментировал(мб не кому не интересно?) перехожу к совету 2.
с методами, типа getFilmById, возвращающих один экземпляр модели, все ясно, но вот с методами возвращающими массивы объектов что то не так.
Да, конечно массив - хороший тип данных, но слишком они примитивные и не гибкие, да и вообще это как то не по ООПшному =)
что же делать? создать свой тип для коллекций!
как? да очень просто!
сразу пример:

Код: Выделить всё

class FilmCollection extends CList {

    private $totalCount; //общее число фильмов, без установленных лимитов

    public function add($item)
    {
        if(!($item instanceof Film))
            throw new CException('ээ, что такое вы в меня суете, я принимаю только объекты типа Film');
        
        parent::add($item);
    }

    public function setTotal($count)
    {
        $this->totalCount = $count;
    }

    public function getTotal()
    {
        return $this->totalCount;
    }
}
 
Метод Фасада FilmManager возвращающий коллекцию фильмов, при этом сразу предусмотрим, что на выборку установленны лимиты( для пагинации)

Код: Выделить всё

    /**
     *
     * @param string $director
     * @param int $limit
     * @param int $offset
     * @return FilmCollection 
     */
    public function getFilmsByDirector($director, $limit, $offset) {
        $db = Yii::app()->db;
        $film_table = Film::tableName();
        $sql = "SELECT * FROM {$film_table} WHERE director = :Director
        LIMIT :Offset, :Limit";
        $command=$db->createCommand($sql);
        $command->bindParam(":Director", $director);
        $command->bindParam(":Limit", $limit);
        $command->bindParam(":Offset", $offset);
        $rows = $command->queryAll();
        
        $filmCollection = new FilmCollection;
        
        foreach($rows as $row) {
            $film = new Film;
            $film->id = $row['id'];
            $film->name = $row['name'];
            
            $filmCollection->add($film);
        }
        
        //считаем фильмы без лимитов
        $sqlForCount = "SELECT COUNT(*) FROM {$film_table} WHERE director = :Director";
        $command2=$db->createCommand($sqlForCount;
        $command2->bindParam(":Director", $director);
        $count = $command2->queryScalar();
        
        $filmCollection->setTotal($count);
        return $filmCollection;        
        
    }
 
Как работать с полученной коллекцией я надеюсь объяснять не придется...

В итоге мы получаем очень гибкую архитектуру по сравнению с тем если бы работали с простыми типами, и прозрачную если бы работали с AR!
да кода конечно писать придется не мало, но плюсов у такой архитектуры предостаточно!
Последний раз редактировалось pirrat 2009.12.17, 11:53, всего редактировалось 1 раз.
Ekstazi
Сообщения: 1428
Зарегистрирован: 2009.08.20, 22:54
Откуда: Молдова, Бельцы
Контактная информация:

Re: Как выглядит Model если используешь DAO

Сообщение Ekstazi »

Э, па не проще переименовать filmanager в filmfactory и сразу наследовать её от CList. Получится нечто похожее на реализацию CActiveRecords.
Тогда можно будет вынести все в BaseSphinxFactory. И все фабрики/мэнэджеры наследовать от этого класса.

P.S.: Как вариант еще для работы со сфинкс можно написать свой dbConnection класс, однако реализация не будет лишена сложностей, если не найти способ переделать CDbCommandBuilder
pirrat
Сообщения: 193
Зарегистрирован: 2009.04.03, 09:41

Re: Как выглядит Model если используешь DAO

Сообщение pirrat »

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

следующий этам это как раз таки введение ещё одного слоя:

первый слой FilmManager с котором мы проводим всю работу из вне, он обращается к слою ниже, в котором определены основные фасады, такие как :
FilmSphinxManager, FilmDbManager, FilmXmlManager а они уже создают экземпляры Film и коллекции FilmCollection...
к Сфинксу естественно идет у нас обращение не так как я расписал(это лишь для простоты объяснения) а через специальный драйвер.

Опиши поподробнее по реализации фабрик, мб вместе прийдем к идеальному решению!
Ekstazi
Сообщения: 1428
Зарегистрирован: 2009.08.20, 22:54
Откуда: Молдова, Бельцы
Контактная информация:

Re: Как выглядит Model если используешь DAO

Сообщение Ekstazi »

Ну в принципе у тебя и так уже выходит фабрика, однако я имел в виду вот что:

Код: Выделить всё

class Film extends CModel
{
protected $isNewrecord;
protected $id;
protected $name;

public __construct($isNew=true)
{
$this->isNewRecord=$isNew;
}

public function save(){
  if($this->isNewRecord){
//новая
  }else{
//Измененая
  }
}

public static function findById($id)
{
    $data=$sphinx->find(array('id'=>$id));
    $model=new __CLASS__(false);
    $model->attributes=$data;
    return $model;
}

public function behaviors(){
   return array(
     'list'=>'CList',
   );
}
} 
Получается монолитный объект умеющий искать по Id и являющийся списком.
Из плюсов:
1) меньше классов и разроблености
2) так как класс монолитен, то усложняется отладкеа ошибок
P.S.: думаю идея понятна, осталось только сделать мост для работы со sphinx
pirrat
Сообщения: 193
Зарегистрирован: 2009.04.03, 09:41

Re: Как выглядит Model если используешь DAO

Сообщение pirrat »

Понял тебя, я так делал как только начал работать с MVC.
но в итоге мы как раз таки ушли от такой архитектуры к той что я описал: и разделили объекты сущности, коллекции и фабрики их создающих, поскольку она более прозрачна и гибка по нашему мнению ,но конечно же имеет и свои минусы.

ЗЫ: у нас все эти типы хранятся в директории models, видели бы вы кол-во файлов там)) на проекте dostavka[dot]ru это уже более 200 файлов, и то при этом коллекции далеко не везде используются...
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Как выглядит Model если используешь DAO

Сообщение samdark »

По-моему очень хороший вариант для тех, кому по каким-либо причинам не подходит AR. В рецепты поместить однозначно стоит.
carlton
Сообщения: 7
Зарегистрирован: 2009.12.10, 00:05

Re: Как выглядит Model если используешь DAO

Сообщение carlton »

Sam Dark писал(а):По-моему очень хороший вариант для тех, кому по каким-либо причинам не подходит AR. В рецепты поместить однозначно стоит.
Поддерживаю, действительно хорошо написано.
Именно то, что я хотел без AR.
pirrat
Сообщения: 193
Зарегистрирован: 2009.04.03, 09:41

Re: Как выглядит Model если используешь DAO

Сообщение pirrat »

AR может не подходить по многим причинам:
-производительность
-отсутствие AR (данное решение подходит для любого фреймворка и даже языка)
-хранилище данных (кто сказал что данные хранятся токо в бд)
-гибкость (многие вещи с AR приходится реализовывать через костыли, когда например требуются сложные запросы). а из полученных данных из хранилищ мы уже формируем любые объекты которые нам нужны.
-порог вхождения. для программистов мало знакомых с архитектурой конкретной реализации orm, гораздо проще будет разобраться в коде построенного по такой архитектуре.

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

При необходимости распишу ещё подробнее: по работе с коллекциями, по созданию прослоек между менеджерами и моделями и тд.
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Как выглядит Model если используешь DAO

Сообщение samdark »

Да, пожалуй стоит подробнее. Я попробую потом собрать всё в рецепт.
Ekstazi
Сообщения: 1428
Зарегистрирован: 2009.08.20, 22:54
Откуда: Молдова, Бельцы
Контактная информация:

Re: Как выглядит Model если используешь DAO

Сообщение Ekstazi »

Pirat, да, монолитность плоха тем что не на столько гибкая и сложна в отладке, однако на сколько я понял даёт прирост по скорости(хотя для php он незначителен). В фрэймвоке класс CActiveRecord реализован имено монолитным(не считая CHasManyRelation и прочих).
pirrat
Сообщения: 193
Зарегистрирован: 2009.04.03, 09:41

Re: Как выглядит Model если используешь DAO

Сообщение pirrat »

монолитность не только плоха тем что не так гибка а тем что ещё усложняет и даже иногда нарушает логику.
Простой случай, когда модель будет является и объектом сущности и объектом коллекции (пусть даже эта логика у нас описана в поведении):
то у нас теоретически будут поля такие $name; //название фильма, $totalCount;// кол-во элементов в коллекции.
ну как минимум это не логично, поскольку один объект - несколько сущностей...

про производительность сложно судить, но я не вижу никаких проблем в производительности архитектуры, которую я описал.
да, файлов больше и в памяти хранится больше объектов, но опкодкэш думаю покроет это...
pirrat
Сообщения: 193
Зарегистрирован: 2009.04.03, 09:41

Re: Как выглядит Model если используешь DAO

Сообщение pirrat »

ок. попробуем дальше описать архитектуру.
Мы Создали фасад, который создает объекты и коллекции, если у нас подразумевается использование только одного хранилища, то мы можем все таки и оставить, но зачастую
в одном проекте используется несколько хранилищ, как в уже приведенном примере: бд и сфинкс.
как тут быть? писать разные методы в одном фасаде? - не логично.
Здесь можно применить несколько архитектурных решений, опишу один из вариантов.
Выделим мы конкретные фабрики по работе с каждым хранилищем в отдельные классы, но обращаться мы к ним будем через один фасад FilmManager а точнее через фабричный метод.

Код: Выделить всё

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;

    }
}
 
api по работе с фасадом немного поменялось:

Код: Выделить всё


//из бд берем данные
$film = FilmManager::factory()->getFilmById($id);

//а тут решили из xml

$film =  FilmManager::factory('XML')->getFilmById($id);
 
В итоге имеем: один API по обращению к фасаду, один тип возвращаемых объектов, простейшая смена хранилища данных.
Последний раз редактировалось pirrat 2009.12.17, 20:12, всего редактировалось 4 раза.
pirrat
Сообщения: 193
Зарегистрирован: 2009.04.03, 09:41

Re: Как выглядит Model если используешь DAO

Сообщение pirrat »

обновил предыдущий пост ,а то с первого раза как то криво патерн реализовал)
pirrat
Сообщения: 193
Зарегистрирован: 2009.04.03, 09:41

Re: Как выглядит Model если используешь DAO

Сообщение pirrat »

Стало все таки интересно сравнить производительность...
Сделал замеры производительности AR и создание коллекции объектов вышеописанным способом.
Выборка на 1000 записей. все окружение совершенно одинаковое для обоих вариантов.
Замеры произведены средствами Xdebug и при включенном apc.

Время выполнения:
AR 965ms (выполнение метода findAll) (1032ms общее) 7934,5кб
При этом суммарное время 930ms заняло выполнение метода populateRecord для каждой записи.


DAO 135ms (выполнение метода Фасада, который создает коллекцию объектов). (160ms общее) 6654,7кб
при этом сама выборка и получение данных (в обоих случаях) заняла 35ms, остальное ушло на создание коллекции.
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Как выглядит Model если используешь DAO

Сообщение samdark »

Кеш схемы для AR при этом был включен?
pirrat
Сообщения: 193
Зарегистрирован: 2009.04.03, 09:41

Re: Как выглядит Model если используешь DAO

Сообщение pirrat »

да, был включен.
при этом код был несколько раз запущен, чтобы сработали все кэши (метаданных, apc)
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Как выглядит Model если используешь DAO

Сообщение samdark »

Хм… довольно странно выходит. Если сами запросы выходят одинаковые и на схему ничего не тратится, то чем кардинально отличается построение коллекции AR Yii от твоего варианта?
Ответить