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

Обсуждение документации. Переводы Cookbook и авторские рецепты.
pirrat
Сообщения: 193
Зарегистрирован: 2009.04.03, 09:41

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

Сообщение pirrat »

Я согласен с вами, что это не гибкий метод и можно многое улучшить, но рецепт дает направление в проектировании слоя данных, а не готовое решение.
Почему был написан данный рецепт: у многих возникает вопрос: "как правильно работать с дао?", и чаще всего я вижу примеры такие:
в классе модели описываются все методы, на подобии getLastNews, возвращающие простые типы и не дающие никакой гибкости по сравнению с ООП подходом, в том числе не возможна стандартная валидация полей и многое другое.
Я предложил более гибкий подход: выделение фабрик, создание коллекций, отделение сущностей и тд
Многое что можно было бы сделать в рецепте не приведено, например:
автоматическое присвоение свойствам значений(то что вы описали), хотя я в одном из постов дал пример как можно это реализовать:

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

        
public function setAttributes($model, $attributes)
        {
            foreach($attributes as $name => $value)
            {
                if(property_exists($model, $name))
                    $model->$name = $value;
            }
        } 
Можно создать базовый класс для всех фабрик и вынести туда этот метод.
Можно(да и нужно) создать базовый класс для коллекций, куда вынести методы getTotal,setTotal и др.
Можно ещё много чего, но я ещё раз повторяю: дано направление в правильном(более менее) проектирование работы со слоем данных, а не готовое решение!
Слишком много низкоуровневого и ручного кода:

- ручной SQL
Вся гибкость этого похода. в том что мы сами строим SQL(да не только sql, данные можно брать откуда угодно), так же как если бы просто мы писали запросы и возвращали простые типы, но при этом у нас более гибкий метод работы с объектами, при этом кол-ва кода увеличивается не намного.
Если не хотите работать "руками" ,есть ORM решения!!!
Давайте подумаем, как использовать DAO, но без такого жесткого хардкодинга.
уже придумали - AR.
Давайте, только давайте не будем изобретать велосипед(ORM), и будем использовать "ручной sql"!!!
Последний раз редактировалось pirrat 2009.12.27, 14:39, всего редактировалось 2 раза.
pirrat
Сообщения: 193
Зарегистрирован: 2009.04.03, 09:41

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

Сообщение pirrat »

Sam Dark писал(а):Переработал труд pirrat и оформил в рецепт: http://yiiframework.ru/doc/cookbook/ru/model.dao
Спасибо!
как будет время, вышлю дополнения, улучшения и уточнения.
isergey
Сообщения: 83
Зарегистрирован: 2010.01.16, 21:05

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

Сообщение isergey »

Было бы неплохо дополнить рецепт примером, при котором используется внешний ключ, ну, допустим, страна производитель. В моделе фильма это будет country_id.
Вопрос исчерпан. Прошу прощения.
isergey
Сообщения: 83
Зарегистрирован: 2010.01.16, 21:05

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

Сообщение isergey »

Друзья, простите, что насилую труп, но у меня возникло несколько вопросов по поводу реализации такого подхода.

Во-первых, мне кажется, что путаются понятия между классом-фабрикой, классом управления моделью - менеджером (сущностью).
Что касается фабрики. То ли php ограничивает ваш разум, то ли дело в человеке :), но уже как бы сформировано понятие "фабрика классов", т.е., например, в java - это абстрактный класс, который нужен лишь для того, чтобы создавать другие объекты. В нашем же случае, вы обозвали класс FilmDBManager фабрикой, (это воля автора, можно хоть василием пупкиным назвать), хотя "фабричного" смысла он не несет и является скорее объектом доступа к данным.
Напротив, класс-фабрику FilmManager, который создает объекты доступа к данным, вы обозвали фасадом. И смотря на этот класс, я не могу понять, фасадом чего он является? Судя по примеру использования, FilmManager как раз таки выполняет роль фабрики объектов доступа к данным, но тут возникает вопрос, зачем ему метод getFilmById()? Ведь этот метод никогда не будет вызван в FilmManager(!!!), а будет всегда вызываться в объекте досупа к данным т.е. в FilmDBManager (может быть стоит создать интерфейс и от этого интерфейса наследовать FilmDBManager если вы хотите иметь определенный набор методов?). Тут вроде понятно. Если вы вводите свою трактовку устоявшимся понятием, будте добры расшифровать ее.

Теперь следующий вопрос, уже не идеологический, а бытовой.

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

public static function factory($driver = 'DB'){
        return new 'Film'.$driver.'Manager';
} 
Вот этот метод создан для универсальности. Мне кажется он мало пригоден для реальных условий. Вот скажите, допустим у мнея будет база в XML, я вынужден вызывать этот метод так?

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

$film = FilmManager::factory('XML')->getFilmById($id); // Меня этот XML должен всюду преследовать?  
может быть

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

$film = FilmManager::factory(config['dbtype'])->getFilmById($id);  // и мой код свалиться в непонятной ошибке если пользователь задаст некорректную базу + Меня этот config['dbtype'] должен всюду преследовать?  
Простите за крик души, но хотелось бы более качественных описаний, приучающих людей к хорошему тону. И еще, вас читают маленкие дети, а вы им мозги засоряете :)
pirrat
Сообщения: 193
Зарегистрирован: 2009.04.03, 09:41

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

Сообщение pirrat »

Я уже и забыл про этот топик...
По большей части согласен с вашей критикой.

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

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

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

Сообщение pirrat »

что касаемо сформированных понятий, согласен что они отчасти являются ошибочными.
для менеджеров( FilmDbManager), на данный момент склоняюсь к варианту - Mapper (в виду шаблона DataMapper).

Фабрикой изначально назвал, ввиду того что - менеджеры действует как Фабрики для экземпляров моделей.
Фасад - не помню уже с чего мне пришёл этот паттерн в голову, но если заметить, то в постах я один и тот же класс называй а то фабрикой а то фасадом(сам запутался немного в именовании)...
isergey
Сообщения: 83
Зарегистрирован: 2010.01.16, 21:05

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

Сообщение isergey »

Возникает еще один вопрос по поводу такой организации.
Допустим, в моделе Film есть еще поле director_id (режисер фильма, допустим, может быть только один). Теперь я хочу вывести таблицу фильмов, отсортировнанных по имени режисера (заметьте не по id режисера, а по полю, хранящегося в другой модели. Единственная проблема, режисеры храняться в XML базе, а наши фильмы в SQL, т.е. нельзя сделать такой запрос:

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

select film.name, director.name FROM film, director WHERE film.director_id = director.id ORDER BY  director.name
Как поступать в этом случае?
pirrat
Сообщения: 193
Зарегистрирован: 2009.04.03, 09:41

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

Сообщение pirrat »

а как бы вы решили такую задачу не при такой организации?!

сортировать коллекцию средствами php : создать коллекцию, а затем отсортировать её функцией usort!

FilmCollection::sortByDirector

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

public function sortByDirector() {
usort($this->_d, function($a,$b) {
            $an = strtolower($a->getDirector()->getName());
            $bn = strtolower($b->getDirector()->getName());
            if($an==$bn) return 0;
            return ($an > $bn) ? +1 : -1;
        }); 
}
 
$collection->sortByDirector();

В случае использования простого массива вместо коллекции, просто применить к нему usort
isergey
Сообщения: 83
Зарегистрирован: 2010.01.16, 21:05

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

Сообщение isergey »

Эта сортировка годится для выбранного набора, а что делать, если у нас есть оффсеты? Т.е. при сортировке силами базы, оффсет делается на отсортированном множестве, в нашем случае, множество извлечется неотсортированным.
pirrat
Сообщения: 193
Зарегистрирован: 2009.04.03, 09:41

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

Сообщение pirrat »

Вы ставите вопрос - будто бы это проблема описанной архитектуры, хотя это не так.
вы тоже самое сделайте на простых типах: без коллекций ,объектов и тп, и тогда будет понятно, что архитектура тут не имеет значения.

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

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

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

Сообщение Ekstazi »

Спустя почти год я въехал в этот рецепт. Ему не хватает более подробного описания фабрик, нечто вроде объяснения, что фасад является мостом между приложением и БД, и использует адаптеры для работы с хранилищем данных, которые в свою очередь являются фабриками. Как-то так, но более красивыми словами думаю бы здорово облегчило понимание.
Аватара пользователя
Sentox
Сообщения: 18
Зарегистрирован: 2010.12.22, 14:26

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

Сообщение Sentox »

Облегчил бы понимание всё тот же преславутый UML.
Что что а графика между разработчиками полезна.
...
suppp
Сообщения: 67
Зарегистрирован: 2011.01.19, 19:07

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

Сообщение suppp »

Подскажите, почему мы наследуемся от CModel? какие преимущества или функционал дает нам это наследование по сравнению с написанием класса с нуля? (без наследования)
Ekstazi
Сообщения: 1428
Зарегистрирован: 2009.08.20, 22:54
Откуда: Молдова, Бельцы
Контактная информация:

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

Сообщение Ekstazi »

Ну во-первых CModel - стандартный класс для всех моделей, то есть он предоставляет необходимые базовые методы.
rak
Сообщения: 2181
Зарегистрирован: 2010.11.02, 23:40
Контактная информация:

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

Сообщение rak »

suppp писал(а):Подскажите, почему мы наследуемся от CModel? какие преимущества или функционал дает нам это наследование по сравнению с написанием класса с нуля? (без наследования)
например, валидация данных
Hyperion
Сообщения: 4
Зарегистрирован: 2011.03.24, 22:30

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

Сообщение Hyperion »

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

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

Сообщение samdark »

Через CArrayDataProvider.
Hyperion
Сообщения: 4
Зарегистрирован: 2011.03.24, 22:30

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

Сообщение Hyperion »

Пробовал реализовать, создав новый класс от CDataProvider. Приведу куски кода, может скажете, почему по сравнению с тем же Active Record время работы скрипта увеличилось с 250мс до 560мс.
Маппер:

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

class CharacterMapper
{
    private $_table = 'chars.characters';

    private function getDbConnection()
    {
        return Yii::app()->db;
    }
    public function findById($id)
    {
        $db = self::getDbConnection();

        $sql = "SELECT * FROM {$this->_table} WHERE guid=:id LIMIT 1";
        $command=$db->createCommand($sql);
        $command->bindParam(":id", $id);
        $row = $command->queryRow();
 
        $char = new Character;
        $char->setAttributes($row);

        return $char;

    }
    
    public function search()
    {
        $model = new Character();
        if(isset($_GET['Character']))
            $model->setAttributes($_GET['Character']);

        $count = self::getDbConnection()->createCommand("SELECT COUNT(*) FROM {$this->_table}")->queryScalar();
        $sql = "SELECT * FROM {$this->_table} WHERE name LIKE :name";
        $dataProvider = new CModelDataProvider($sql, 'Character', array(
            'totalItemCount'=>$count,
            'sort'=>array(
                'attributes'=>array(
                    'name', 'level',
                ),
            ),
            'pagination'=>array(
                'pageSize'=>10,
            ),
            'params' => array(
                  ':name' => '%'.$model->name.'%',
            ),
            'keyField' => 'guid'
        ));
        return $dataProvider;
    }
} 
Модель:

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

class Character extends CModel
{
    private $_guid;
    private $_account;
    private $_name;
    private $_class;
    private $_race; 
    private $_gender;
    private $_level;
    private $_money;
    private $_playerBytes;
    private $_playerBytes2;

    public function attributeNames()
    {
        return array(
            'guid',
            'account',
            'name',
            'level',
            'class',
            'race',
            'gender',
            'money',
            'playerBytes',
            'playerBytes2',
        );
    }

    public function rules()
    {
        return array(
            array('guid, account, level, class, race, gender, money, playerBytes, playerBytes2', 'numerical', 'integerOnly'=>true),
            array('name', 'length', 'max'=>12),
            array('name', 'safe', 'on'=>'search'),

        );
    }

    public function getPrimaryKey()
    {
      return $this->_guid;
    }
// Далее сеттеры и геттеры
}
 
Обьект, реализующий DataProvider (Смесь SqlDataProvider и ActiveDataProvider):

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

class CModelDataProvider extends CDataProvider
{

    public $db;
    public $sql;
    public $modelClass;
    public $params=array();
    public $keyField='id';

    public function __construct($sql,$modelClass,$config=array())
    {
        if(is_string($modelClass))
            $this->modelClass = $modelClass;
        else if($modelClass instanceof CModel)
            $this->modelClass = get_class($modelClass);
        $this->sql=$sql;
        foreach($config as $key=>$value)
            $this->$key=$value;
    }

    protected function fetchData()
    {
        $sql=$this->sql;
        $db=$this->db===null ? Yii::app()->db : $this->db;
        $db->active=true;

        if(($sort=$this->getSort())!==false)
        {
            $order=$sort->getOrderBy();
            if(!empty($order))
            {
                if(preg_match('/\s+order\s+by\s+[\w\s,]+$/i',$sql))
                    $sql.=', '.$order;
                else
                    $sql.=' ORDER BY '.$order;
            }
        }

        if(($pagination=$this->getPagination())!==false)
        {
            $pagination->setItemCount($this->getTotalItemCount());
            $limit=$pagination->getLimit();
            $offset=$pagination->getOffset();
            $sql=$db->getCommandBuilder()->applyLimit($sql,$limit,$offset);
        }

        $command=$db->createCommand($sql);
        foreach($this->params as $name=>$value)
            $command->bindValue($name,$value);

        $rows = $command->queryAll();
        $data = array();
        foreach($rows as $row)
        {
            $model = new $this->modelClass();
            $model->setAttributes($row);
            $data[] = $model;
        }
        return $data;
    }

    protected function fetchKeys()
    {
        $keys=array();
        foreach($this->getData() as $i=>$data)
            $keys[$i]=$data[$this->keyField];
        return $keys;
    }

    protected function calculateTotalItemCount()
    {
        return 0;
    }
} 
Ну и собственно сам скрипт(только вид, котроллер только вызывает рендеринг):

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

<?php 
$mapper = new CharacterMapper();
$model = new Character();
$this->widget('zii.widgets.grid.CGridView', array(
    'id'=>'characters-grid',
    'dataProvider'=>$mapper->search(),
    'filter'=>$model,
    'columns'=>array(
        'name',
        array(
            'class'=>'CButtonColumn',
        ),
    ),
)); ?>
Hyperion
Сообщения: 4
Зарегистрирован: 2011.03.24, 22:30

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

Сообщение Hyperion »

Вопрос снят, было включено логирование ошибок, и при попытке установить несуществующий атрибут записывался файловый лог. Так как несуществующих атрибутов у меня было порядка 40 штук, отсюда и получался это увеличение скорости. Убрал логирование - работает быстрее чем с AR.
voshum
Сообщения: 40
Зарегистрирован: 2011.03.21, 17:43

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

Сообщение voshum »

Sam Dark писал(а):Через CArrayDataProvider.
А не подсакажите как это сделать. Пишу вот так

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

  
// В модели
         public function getShopList() {
              $mysqlQuery = "SELECT * FROM shops";
              $command = $this->_mysqlConnect->createCommand($mysqlQuery);
              $result = $command->queryAll();
             return $dataProvider = new CArrayDataProvider($result);
         }
 

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

// В представлении
<?php $this->widget('zii.widgets.grid.CGridView', array(
    'id'=>'shop-grid',
    'dataProvider'=>$model->getShopList(),
    'filter'=>$model,
    'columns'=>array(
        'shop_name',
                'shop_description',
        array(
            'class'=>'CButtonColumn',
        ),
    ),
)); ?>
После запуска появляется ошибка: Trying to get property of non-object
Что я не так делаю?
Ответить