Обновление грида через ajax

Прочитал, но не понимаю, почему у меня работает по другому, а не так как Вы описали в Способ 2. Все через Ajax

Вот action, который выводит список пользователей:

getRequest()->getQuery(‘User’)) $model->attributes=$_GET[‘User’];

if(Yii::app()->getRequest()->getQuery(‘ajax’))//проверка «а есть ли в запросе призрак ajax’a»
{
$this->controller->renderPartial(‘usersListGrid’,array(‘model’=>$model));
}
else
{
$this->controller->render(‘usersList’,array(‘model’=>$model));
}
}
}

* This source code was highlighted with Source Code Highlighter.

Вышеописанный action рендерит представление usersList.php, которое в свою очередь вызывает usersListGrid.php.

Если же потом мы будем переходит по страницам, фильтровать, сортировать, то CGridView подставит в запрос ajax и вышеописанный action будет отдавать только представление usersListGrid.php (чистый html-код, без всяких вызовов jquery.js и остальных скриптов), так как они уже были загружены при первом вызове.

И все будет работать.

Представление: usersList.php

renderPartial(‘usersListGrid’,array(‘model’=>$model),true,false); ?>

* This source code was highlighted with Source Code Highlighter.

Представление: usersListGrid.php

createUrl(«admin/itemsSelected»),’post’,array(‘enctype’=>’multipart/form-data’)); ?>
widget(‘zii.widgets.grid.CGridView’, array(
‘id’=>’usersListGrid’,
‘dataProvider’=>$model->search(),
‘selectableRows’=>3,
‘template’=>»{summary}
{pager}
{items}
{pager}
«,
‘pager’=>array(
‘class’=>’CLinkPager’,
‘header’=>»,
‘firstPageLabel’=>’<<', 'prevPageLabel'=>‘<', 'nextPageLabel'=>‘>’,
‘lastPageLabel’=>’>>’,
),
‘filter’=>$model,
‘columns’=>array(
array(
‘class’=>’CCheckBoxColumn’,
‘id’=>’itemsSelected’,
),
array(
‘name’=>’id’,
‘value’=>’$data->id’,
//’filter’=>»,
‘htmlOptions’ => array(‘style’ => ‘text-align:center;width:40px;’),
),
array(
‘name’=>’datreg’,
‘type’=>’raw’,
‘value’=>’date(«d/m/Y H:i:s»,$data->datreg)’,
‘filter’=>»,
‘htmlOptions’ => array(‘style’ => ‘text-align:center;width:130px;’),
),
array(
‘class’=>’CButtonColumn’,
‘template’=>'{myupdate} {mydelete}’,
‘htmlOptions’ => array(‘style’ => ‘width:30px;’),
‘buttons’=>array(
‘myupdate’=>array(
‘label’=>’Редактировать’,
‘url’=>’array(«admin/usersEdit»,»id»=>$data->id)’,
‘imageUrl’=>Yii::app()->theme->baseUrl.’/img/pencil.png’,
),
‘mydelete’=>array(
‘label’=>’Удалить’,
‘url’=>’array(«admin/usersDelete»,»id»=>$data->id)’,
‘imageUrl’=>Yii::app()->theme->baseUrl.’/img/minus.png’,
‘click’=>’function(){return confirm(«‘.Yii::t(‘lan’,’Удалить ?’).'»);}’,
‘visible’=>’$data->role!=User::ROLE_ADMIN’,
),
),
),

),
));
?>
С отмеченными: WorkItemsSelected,array(’empty’ =>’—‘)); ?>
«return confirm(‘?’);»)); ?>

* This source code was highlighted with Source Code Highlighter.

Yii: Уникальное трио (или проверка на уникальность сочетания трех значений полей) — LOCO.RU

Yii: Уникальное трио (или проверка на уникальность сочетания трех значений полей)

 

 

Yii: Уникальное трио (или проверка на уникальность сочетания трех значений полей) Как проверять несколько полей на уникальность именно как группы, то есть одновременно все поля не должны повторяться вместе группой. Можно назвать это уникальной парой, уникальным трио, квартетом и т.д.

 

Валидатор unique может проверять уникальность одного значения, например name, но чтобы проверить уникальность сочетания пары значений или трёх значений, например name, email и discipline_id придётся создать дополнительный метод. Этот отдельный метод вызывается уже после инициализации полей, поэтому $this->name и $this->email нормально подставляют параметры. До этого, непосредственно в rules(), значения атрибутов пусты и проверку criteria там не осуществить.

Делаем так.

Модель:

public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
...
array('discipline_id','uniqueNameAndEmailAndDiscipline',),
...
);
}
public function uniqueNameAndEmailAndDiscipline($attribute,$params=array())
{
    if(!$this->hasErrors())
    {
        $params['criteria']=array(
            'condition'=>'name=:name AND email=:email',
            'params'=>array(':name'=>$this->name, ':email'=>$this->email),
        );
        $validator=CValidator::createValidator('unique',$this,$attribute,$params);
        $validator->validate($this,array($attribute));
    }
}

 

Источник: Yii: Уникальное трио (или проверка на уникальность сочетания трех значений полей) — LOCO.RU.

Практические советы по Yii-фреймворку

Практические советы по Yii-фреймворку

2 Оценки

Здравствуйте. Этим постом я хочу открыть серию статей посвященных yii фреймворку. В них я, по возможности, буду описывать свой опыт его использования в процессе разработки сайтов. Опишу решения проблем и задач, с которыми приходилось сталкиваться. Таким образом хочу передать свой опыт и помочь тем, кто возможно столкнется с такими же проблемами. Многое приходилось искать по крупицам в интернете, в апи и в самих классах.

 

В начале хочу написать немного о самом фреймворке. Он имеет достаточно много возможностей: готовую mvc-структуру, возможность организации модульной системы, гибкий Activerecord для работы с бд, интернационализацию, широкие возможности для кеширования, генератор кода, генераторы хтмла и яваскрипта, jquery и jquery ui из коробки, систему управления подключаемыми ресурсами(assets), множество готовых расширений и плагинов, и т.д. Очень понравилась встроенная валидация форм. Можно подключить клиентскую(с аякс запросами и без, при изменении поля и при сабмите формы) и серверную валидацию. Так же есть встроенная пагинация и сортировка(которые тоже можно сделать как аяксовые так и обычные). Есть замечательный грид, с фильтрацией/пагинацией/сортировкой, что тоже можно сделать как через аякс-запросы. Все это очень удобно и нигде больше подобных фич из коробки я не видел. Если сравнивать с другими фреймворками, с которыми я сталкивался, то он по классу проще зенда и симфони, но понятнее и проще в использовании.

 

Документация по yii достаточно хорошо написана и переведена на многие языки. Вот несколько ссылок на которых можно почерпнуть много полезного:

http://www.yiiframework.com/doc/guide/ — сама документация

http://www.yiiframework.com/extensions/ — расширения

http://www.yiiframework.com/wiki/ — сборник статей

http://yiiframework.ru/doc/cookbook/ru/index — рецепты

http://rmcreative.ru/ — здесь много статей

http://www.simplecoding.org/category/yii — еще хороший ресурс

http://loco.ru/sections/yii — еще

http://belyakov.su/?tag=yii — и еще один

http://monoray.ru/86-yii — и последнее

Вот несколько советов на сегодняшнюю статью:

  • по умолчанию yii не разделен на бэкенд(админку)/фронтенд(лицевую) части сайта.
    Чтобы не смешивать код в одну большую кучу, можно разделить код по папкам, создав удобную структуру. В этой статье описан пример как это лучше сделать http://www.yiiframework.com/wiki/63/organize-directories-for-applications-with-front-end-and-back-end-using-webapplicationend-behavior . Но лучше воспользоваться готовыми структурами, которые к тому же имеют разделенные конфиги для дев- и продакшн-версий сайта. Вот ссылки на эти проекты: https://github.com/clevertech/YiiBoilerplate и https://github.com/neo-classic/yii-structure4big

  • особенности работы с аяксом:

    • совет по работе со встроенными функциями для работы с аяксом.
      Допустим у нас есть на странице аякс-пагинация или сортировка. По умолчанию, в результате запроса, будет возвращаться полностью вся страница. Это плохо с нескольких сторон: полностью заменяется вся страница, в результате чего могут исчезнуть введенные пользователем данные, увеличивается время обработки такого запроса, совершается лишняя нагрузка на сервер. Что нам нужно — отдавать лишь часть контента страницы. В этом случае нам надо использовать метод renderPartial() в экшне контроллера. При этом нам нужно возвращать ответ в виде строки, поэтому второй параметр этого метода должен быть false. Например:
if(Yii::app()->request->isAjaxRequest){
       $this->renderPartial('content',array(...),false);
       Yii::app()->end();
} else
       $this->render('blog',array(...));

где контент может быть ClistView, CGridView или другим виджетом отображения контента;

  • при нажатии на следующую страницу пейджера контент вдруг со страницы исчез
    Проверив ответ аякс-запроса в фаербаге мы видим, что контент вернулся нормально, но почему-то не отображается. Тут все довольно просто. Где-то на странице есть элемент с таким же айди, который имеет контейнер куда рендерится контент. Надо всего лишь у виджета задать свой уникальный айди, например, так ‘id’=>’unique-content’;
  • типовая проблема при рендере через renderPartial().
    Например, мы хотим возвращать форму редактирования в попап окошке с помощью аякс-запроса. В этом случае нам может понадобится яваскриптовая валидация. Для корректной работы нам нужны будут яваскриптовые библиотеки, которые отвечают за валидацию. Но поскольку у нас их может не быть на странице, их надо вернуть вместе с формой. Для этого в методе renderPartial() существует третий параметр processOutput, который нужно поставить в true. Вы увидите что вместе с контентом сервер вернул все подключаемые js и css файлы. Нам возвращать все не надо, поэтому есть функция исключающая эти файлы из ответа, так она выглядит:

 

Yii::app()->clientScript->scriptMap['jquery.js'] = false

В общем код будет выглядить примерно так:

if(Yii::app()->request->isAjaxRequest){
    Yii::app()->clientScript->scriptMap['jquery.js'] = false;
    Yii::app()->clientScript->scriptMap['styles.css'] = false;
    $this->renderPartial('content',array(...),false,true);
    Yii::app()->end();
} else 
    $this->render('blog',array(...));
  • загрузка формы или поля с ckeditor через аякс-запрос. Подгрузка библиотеки ckeditor.js через аякс-запрос не решает проблему — он не инициализируется и выдаются ошибки. В силу специфики работы этого редактора единственное решение, которое я нашел — предварительно включать файл ckeditor.js на страницу;
  • использование ckeditor с яваскриптовой валидацией.
    Валидация этого поля срабатывает только после второго нажатия на сабмит. Чтобы это заработало с первого нажатия, нужно вызвать функцию updateElement() экземпляра созданного объекта редактора. Нужно повесить эту функцию на событие onclick кнопки. Пример кода для модели Blog и атрибута text:

    'htmlOptions'=>array('onclick'=>'CKEDITOR.instances.Blog_text.updateElement()');
  • не выводится сообщение об ошибке валидации.
    Если по какой-нибудь причине Вы изменили айди поля формы и при этом используете яваскриптовую валидацию, то столкнетесь с тем, что при ошибке валидации сообщение об ошибке не выведется на экране. Чтобы пофиксить эту проблему нужно у метода вывода ошибки $form->error() указать ‘inputID’=>’наш айди’:

    $form->error($model,'text',array('inputID'->'custom_id'));
  • использование datepicker для фильтрации данных в CGridView.
    Чтобы приаттачить к полю фильтра календарь datepicker нужно передать виджет в параметр filter. Пример кода:

    'filter'=>$this->widget('zii.widgets.jui.CJuiDatePicker',
                        array(
                              'model'=>$model,
                              'attribute'=>'date_create',
                              'language'=>''.Yii::app()->language.'',
                              'options'=>array(
                                       'showAnim'=>'fold',
                                       'dateFormat'=>'yy-mm-dd',
                                       'changeMonth' => 'true',
                                       'changeYear'=>'true',
                                       'showButtonPanel' => 'true',
                               ),
                         ),
                         true)

    При перезагрузке грида после аякс запроса, календарь не сработает, поэтому его надо переинициализировать. Для этого нужно в опции afterAjaxUpdate добавить яваскрипт:

    'afterAjaxUpdate'=>"function() {  jQuery('#Model_date_create').datepicker(jQuery.extend(jQuery.datepicker.regional['".Yii::app()->language."'], {'showAnim':'fold','dateFormat':'yy-mm-dd','changeMonth':'true','showButtonPanel':'true','changeYear':'true'}));}"
  • работа со связями(relations).
    По умолчанию вывод связанных данных происходит с помощью паттерна проектирования «lazy load», то есть данные выбираются из бд только когда мы вызываем связанную модель. Но это может привести к выполнению множества sql-запросов, когда мы выводим множество данных и связанных к ним моделям. Для того чтобы связанные данные выбрались за один запрос, нужно в экшне контроллера в вызываемой моделе указать в опции ‘with’ нужные нам связи. Например, так:

    $blogs = new CactiveDataProvider('Blog',array('with'=>array('author','comments'),'together'=>true));

    или так:

    $model = Blog::model()->with('author','comments')->findAll();
  • вызов связанных моделей у связанной модели.
    Например, у нас есть блог со связанной моделью комментариев, которые, в свою очередь, связаны с авторами комментариев. Чтобы вызвать вложенную связь правильно, нужно сделать так:

    'with'=>array('comments'=>array('with'=>array('author')))

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

    'comments' =>array(self::HAS_MANY, 'Comment', array('blog_id'=>'id'),'with'=>'author');
  • ошибка mysql при использовании связей.
    При использовании связей также может вылезти ошибка такого плана «Integrity constraint violation: 1052 Column ‘id’ in group statement is ambiguous. » или похожая. Суть в том, что в таблицах могут быть одинаковые названия колонок, например, тот же id и когда мы выбираем с них что-то одним запросом, то происходит такая ошибка. Чтобы не было таких ошибок yii автоматически проставляет уникальные псевдонимы таблицам и их колонкам. Но когда мы в контроллере хотим указать, например, ‘order’=>’id desc’, у модели со связями, то получим ошибку. В таком случае нужно указывать префикс таблицы. Для главной модели это t, то есть нужно всего лишь переписать ‘order’=>’t.id desc’ или, если мы используем поле из связанной таблицы, то префиксом будет название связи.

На сегодняшнюю статью пока хватит) Комментарии/исправления/дополнения моих советов приветствую 🙂 Продолжение следует…

Оригинал

http://ukrdev.com.ua/ru/blog/52/prakticheskie-soveti-po-yii-freimvorku.html

 

 

 

Загрузка urlRules из модулей. Автоматизация модульности в Yii

Очень «продвинутый» программист. Прекрасно разбирается в теме, а самое главное — понимает, что делает, что редко встречается.

Загрузка urlRules из модулей. Автоматизация модульности в Yii.

Работа с БД: Реляционная Active Record | The Definitive Guide to Yii | Yii PHP Framework

Работа с БД: Реляционная Active Record | The Definitive Guide to Yii | Yii PHP Framework.

Реляционная Active Record

Мы уже рассмотрели использование Active Record (AR) для выбора данных из одной таблицы базы данных. В этом разделе мы расскажем, как использовать AR для соединения нескольких связанных таблиц и получения набора связанных данных.

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

Для наглядности примеров в этом разделе мы будем использовать схему базы данных, представленную на следующей диаграмме сущность-связь (ER).

Диаграмма ER

Диаграмма ER

Информация: Поддержка ограничений внешних ключей различается в разных СУБД. SQLite 3.6.19 и более ранние версии не поддерживает ограничений, но вы, тем не менее, можете их объявить при создании таблиц. Движок MySQL MyISAM не поддерживает внешние ключи.

1. Установка связей между AR-классами

Перед тем как использовать AR для выполнения реляционных запросов, нам необходимо установить связи между AR-классами.

Связь между двумя AR-классами напрямую зависит от связей между соответствующими таблицами базы данных. С точки зрения БД, связь между таблицами A и В может быть трёх типов: один-ко-многим (например, tbl_user и tbl_post), один-к-одному (например, tbl_user и tbl_profile) и многие-ко-многим (например, tbl_category и tbl_post). В AR существует четыре типа связей:

  • BELONGS_TO: если связь между А и В один-ко-многим, значит В принадлежит А (например, Post принадлежит User);
  • HAS_MANY: если связь между таблицами А и В один-ко-многим, значит у А есть много В (например, у User есть много Post);
  • HAS_ONE: это частный случай HAS_MANY, где А может иметь максимум одно В (например, у User есть только один Profile);
  • MANY_MANY: эта связь соответствует типу связи многие-ко-многим в БД. Поскольку многие СУБД не поддерживают непосредственно этот тип связи, требуется ассоциативная таблица для преобразования связи многие-ко-многим в связи один-ко-многим. В нашей схеме базы данных этой цели служит таблица tbl_post_category. В терминологии AR связь MANY_MANY можно описать как комбинацию BELONGS_TO и HAS_MANY. Например, Post принадлежит многим Category, а у Category есть много Post.

Существует пятый, специальный тип связи, который предназначен для статистических запросов над связанными записями (запросы агрегирования) — называется он STAT. Более подробно с ним можно ознакомиться в разделе Статистический запрос.

Установка связей производится внутри метода relations() класса CActiveRecord. Этот метод возвращает массив с конфигурацией связей. Каждый элемент массива представляет одну связь в следующем формате:

'VarName'=>array('RelationType', 'ClassName', 'ForeignKey', …дополнительные параметры)

где VarName — имя связи, RelationType указывает на один из четырёх типов связей, ClassName — имя AR-класса, связанного с данным классом, а ForeignKey обозначает один или несколько внешних ключей, используемых для связи. Кроме того, можно указать ряд дополнительных параметров, о которых будет рассказано позже.

В приведённом ниже коде показано, как установить связь между классами User и Post.

class Post extends CActiveRecord
{public function relations()
    {
        return array(
            'author'=>array(self::BELONGS_TO, 'User', 'author_id'),
            'categories'=>array(self::MANY_MANY, 'Category',
                'tbl_post_category(post_id, category_id)'),
        );
    }
}

class User extends CActiveRecord
{public function relations()
    {
        return array(
            'posts'=>array(self::HAS_MANY, 'Post', 'author_id'),
            'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'),
        );
    }
}

Информация: Внешний ключ может быть составным, то есть состоять из двух и более столбцов таблицы. В этом случае имена столбцов следует разделить запятыми и передать их либо в качестве строки, либо в виде массива array('key1','key2'). Задать свою связь первичного ключа с внешним можно в виде массива array('fk'=>'pk'). Для составных ключей это будет array('fk_c1'=>'pk_c1','fk_c2'=>'pk_c2'). Для типа связи MANY_MANY имя ассоциативной таблицы также должно быть указано во внешнем ключе. Например, связи categories модели Post соответствует внешний ключ tbl_post_category(post_id, category_id). Для каждой добавленной связи неявно создаётся свойство класса. После выполнения реляционного запроса соответствующее свойство будет содержать связанный экземпляр класса AR (или массив экземпляров для связей типа один-ко-многим и многие-ко-многим). Например, если $author является экземпляром AR-класса User, то можно использовать $author->posts для доступа к связанным экземплярам Post.

2. Выполнение реляционного запроса

Самый простой способ выполнить реляционный запрос — использовать реляционное свойство AR-класса. Если обращение к этому свойству производится впервые, то будет выполнен реляционный запрос, который соединит связанные таблицы и оставит только данные, соответствующие первичному ключу текущего экземпляра AR. Результат запроса будет сохранён в свойстве как экземпляр (или массив экземпляров) связанного класса. Этот подход также известен как «отложенная загрузка» (lazy loading), при которой непосредственный запрос выполняется только в момент первого обращения к связанным объектам. Ниже приведён пример использования этого подхода:

// получаем запись с ID=10
$post=Post::model()->findByPk(10);
// Получаем автора записи. Здесь будет выполнен реляционный запрос.
$author=$post->author;

Информация: Если связанные данные не найдены, то соответствующее свойство примет значение null для связей BELONGS_TO и HAS_ONE или будет являться пустым массивом для HAS_MANY и MANY_MANY. Стоит отметить, что связи HAS_MANY и MANY_MANY возвращают массивы объектов, и обращаться к их свойствам необходимо в цикле, иначе можно получить ошибку «Trying to get property of non-object».

Способ отложенной загрузки удобен, но не всегда эффективен. Например, если нам потребуется получить информацию об авторах N записей, то использование отложенной загрузки потребует выполнения N дополнительных запросов к базе данных. В данной ситуации разумно использовать метод «жадной загрузки» (eager loading).

Этот метод заключается в загрузке всех связанных данных вместе с основным экземпляром AR. Реализуется этот подход путем использования метода with() вместе с методом find или findAll. Например:

$posts=Post::model()->with('author')->findAll();

Приведённый код вернёт массив экземпляров Post. В отличие от отложенной загрузки, свойство author каждой записи будет заполнено связанным экземпляром User ещё до обращения к этому свойству. Таким образом, вместо выполнения отдельного запроса для каждой записи, жадная загрузка получит все записи вместе с их авторами в одном запросе!

В методе with() можно указать несколько связей, и жадная загрузка вернёт их за один раз. Например, следующий код вернёт записи вместе с их авторами и категориями:

$posts=Post::model()->with('author','categories')->findAll();

Кроме того, можно осуществлять вложенную жадную загрузку. Для этого вместо простого списка имён связей, мы передаем методу with() имена связей, упорядоченных иерархически, как в следующем примере:

$posts=Post::model()->with(
    'author.profile',
    'author.posts',
    'categories')->findAll();

Пример выше вернёт нам все записи вместе с их авторами и категориями, а также профиль каждого автора и все его записи.

Жадная загрузка может быть выполнена путём указания свойства CDbCriteria::with:

$criteria=new CDbCriteria;
$criteria->with=array(
    'author.profile',
    'author.posts',
    'categories',
);
$posts=Post::model()->findAll($criteria);

или

$posts=Post::model()->findAll(array(
    'with'=>array(
        'author.profile',
        'author.posts',
        'categories',
    )
));

3. Реляционный запрос без получения связанных моделей

Иногда требуется выполнить запрос с использованием связи, но при этом не требуются данные из связанной модели. Допустим, есть пользователи (User), которые публикуют множество записей (Post). Запись может быть опубликована, а может быть черновиком. Этот факт определяется значением поля published модели Post. Пусть нам необходимо получить всех пользователей, которые опубликовали хотя бы одну запись, при этом сами записи нам не интересны. Сделать это можно следующим образом:

$users=User::model()->with(array(
    'posts'=>array(
        // записи нам не нужны
        'select'=>false,
        // но нужно выбрать только пользователей с опубликованными записями
        'joinType'=>'INNER JOIN',
        'condition'=>'posts.published=1',
    ),
))->findAll();

4. Параметры реляционного запроса

Выше мы упоминали о том, что в реляционном запросе можно указать дополнительные параметры. Эти параметры — пары имя-значение — используются для тонкой настройки реляционного запроса. Список параметров представлен ниже.

  • select: список выбираемых полей для связанного AR-класса. По умолчанию значение параметра равно ‘*’, что соответствует всем полям таблицы. Для используемых столбцов должны быть разрешены конфликты имён.
  • condition: соответствует SQL оператору WHERE, по умолчанию значение параметра пустое. Для используемых столбцов должны быть разрешены конфликты имён.
  • params: параметры для связывания в генерируемом SQL-выражении. Параметры передаются как массив пар имя-значение.
  • on: соответствует SQL оператору ON. Условие, указанное в этом параметре, будет добавлено к основному условию соединения при помощи SQL оператора AND. Для используемых столбцов должны быть разрешены конфликты имён. Данный параметр неприменим для связей типа MANY_MANY.
  • order: соответствует SQL оператору ORDER BY, по умолчанию значение параметра пустое. Для используемых столбцов должны быть разрешены конфликты имён.
  • with: список дочерних связанных объектов, которые должны быть загружены с самим объектом. Неправильное использование данной возможности может привести к бесконечному циклу.
  • joinType: тип соединения таблиц. По умолчанию значение параметра равно LEFT OUTER JOIN;
  • alias: псевдоним таблицы, ассоциированной со связью. По умолчанию значение параметра равняется null, что означает, что псевдоним соответствует имени связи.
  • together: параметр, устанавливающий необходимость принудительного соединения таблицы, ассоциированной с этой связью, с другими таблицами. Этот параметр имеет смысл только для связей типов HAS_MANY и MANY_MANY. Если параметр не установлен или равен false, тогда каждая связь HAS_MANY или MANY_MANY будет использовать отдельный SQL-запрос для связанных данных, что может улучшить скорость выполнения запроса, т.к. уменьшается количество выбираемых данных. Если параметр равен true, то зависимая таблица при выполнении запроса всегда будет соединяться с основной, то есть будет выполнен один SQL-запрос даже в том случае, если к основной таблице применяется постраничная разбивка. Если данный параметр не задан, зависимая таблица будет соединена с основной только в случае, когда к основной таблице не применяется постраничная разбивка. Более подробное описание можно найти в разделе «производительность реляционного запроса».
  • join: дополнительный оператор JOIN. По умолчанию пуст. Этот параметр доступен с версии 1.1.3.
  • group: соответствует SQL оператору GROUP BY, по умолчанию значение параметра пустое. Для используемых столбцов должны быть разрешены конфликты имён.
  • having: соответствует SQL оператору HAVING, по умолчанию значение параметра пустое. Для используемых столбцов должны быть разрешены конфликты имён.
  • index: имя столбца таблицы, значения которого должны быть использованы в качестве ключей массива, хранящего связанные объекты. Без установки этого параметра массив связанных объектов использует целочисленный индекс, начинающийся с нуля. Параметр может быть установлен только для связей типа HAS_MANY и MANY_MANY.
  • scopes: группы условий, которые необходимо применить. В случае одной группы может задаваться в виде строки 'scopes'=>'scopeName'. Если же групп несколько, то их необходимо перечислить в массиве 'scopes'=>array('scopeName1','scopeName2'). Этот параметр доступен с версии 1.1.9.

Кроме того, для отложенной загрузки некоторых типов связей доступен ряд дополнительных параметров:

  • limit: параметр для ограничения количества строк в выборке. Параметр неприменим для связей BELONGS_TO;
  • offset: параметр для указания начальной строки выборки. Параметр неприменим для связей BELONGS_TO.
  • through: имя связи модели, которое при получении данных будет использоваться как мост. Параметр может быть установлен только для связей HAS_ONE и HAS_MANY. Этот параметр доступен с версии 1.1.7, в которой можно применять его к HAS_ONE и HAS_MANY. Начиная с версии 1.1.14 он может использоваться с BELONGS_TO.

Ниже мы изменим определение связи posts в модели User, добавив несколько вышеприведенных параметров:

class User extends CActiveRecord
{
    public function relations()
    {
        return array(
            'posts'=>array(self::HAS_MANY, 'Post', 'author_id',
                            'order'=>'posts.create_time DESC',
                            'with'=>'categories'),
            'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'),
        );
    }
}

Теперь при обращении к $author->posts, мы получим записи автора, отсортированные в обратном порядке по времени их создания. Для каждой записи будут загружены её категории.

5. Устранение конфликта имён столбцов

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

В реляционном запросе псевдоним главной таблицы всегда равен t, а псевдоним связанной таблицы по умолчанию равен имени связи. В приведённом ниже коде псевдонимы таблиц для моделей Post и Comment будут соответственно t и comments:

$posts=Post::model()->with('comments')->findAll();

Допустим, что и в Post, и в Comment есть столбец create_time, в котором хранится время создания записи или комментария, и нам необходимо получить записи вместе с комментариями к ним, отсортированные сначала по времени создания записи, а затем по времени написания комментария. Для этого нам понадобится устранить конфликт столбцов create_time следующим образом:

$posts=Post::model()->with('comments')->findAll(array(
    'order'=>'t.create_time, comments.create_time'
));

Подсказка: Псевдоним таблицы связи по умолчанию равен названию самой связи. Имейте ввиду, что при использовании одной связи внутри другой будет использовано название последней из них. При этом название родительской связи не будет использовано в качестве префикса. Например, псевдонимом связи ‘author.group’ является ‘group’, а не ‘author.group’.

$posts=Post::model()->with('author', 'author.group')->findAll(array(
  'order'=>'group.name, author.name, t.title'
));

Вы можете избежать конфликта псевдонимов таблиц задав свойство связи alias.

$comments=Comment::model()->with(
  'author',
  'post',
  'post.author'=>array('alias'=>'p_author'))->findAll(array(
  'order'=>'author.name, p_author.name, post.title'
));

6. Динамические параметры реляционного запроса

Мы можем использовать динамические параметры как для метода with(), так и для параметра with. Динамические параметры переопределяют существующие параметры в соответствии с описанием метода relations(). К примеру, если для модели User, приведённой выше, мы хотим воспользоваться жадной загрузкой для получения записей автора в порядке возрастания (параметр order в определении связи задает убывающий порядок), можно сделать это следующим образом:

User::model()->with(array(
    'posts'=>array('order'=>'posts.create_time ASC'),
    'profile',
))->findAll();

Динамические параметры в реляционных запросах можно использовать вместе с отложенной загрузкой. Для этого необходимо вызвать метод с тем же именем, что и имя связи, и передать параметры в качестве аргументов. К примеру, следующий код вернёт публикации пользователя, у которых status равен 1:

$user=User::model()->findByPk(1);
$posts=$user->posts(array('condition'=>'status=1'));

7. Производительность реляционного запроса

Как было сказано выше, жадная загрузка используется, главным образом, когда требуется получить множество связанных объектов. В этом случае при соединении всех таблиц генерируется сложный SQL-запрос. Такой запрос во многих случаях является предпочтительным, т.к. упрощает фильтрацию по значению столбца связанной таблицы. Тем не менее, в некоторых случаях такие запросы не являются эффективными.

Рассмотрим пример, в котором нам необходимо найти последние добавленные записи вместе с их комментариями. Предположив, что у каждой записи 10 комментариев, при использовании одного большого SQL-запроса мы получим множество лишних данных, так как каждая запись будет повторно выбираться с каждым её комментарием. Теперь попробуем по-другому: сначала выберем последние записи, а затем комментарии к ним. В этом случае нам необходимо выполнить два SQL-запроса. Плюс в том, что полученные данные не будут содержать дублирующую информацию.

Какой же подход более эффективен? Однозначного ответа на этот вопрос нет. Выполнение одного большого SQL-запроса может оказаться более эффективным, так как СУБД не приходится лишний раз разбирать и выполнять дополнительные запросы. С другой стороны, используя один SQL-запрос, мы получаем лишние данные, а значит нам требуется больше времени на их передачу и обработку. По умолчанию Yii использует жадную загрузку, то есть генерирует один SQL-запрос за исключением случая, когда к главной модели применяется LIMIT. Если выставить опцию together в описании связи в true, то мы получим единственный SQL-запрос даже если используется LIMIT. Если использовать false, то выборка из некоторых таблиц будет производиться отдельными запросами. Например, для того чтобы использовать отдельные SQL-запросы для выборки последних записей и комментариев к ним, связь comments модели Post следует описать следующим образом:

public function relations()
{
    return array(
        'comments' => array(self::HAS_MANY, 'Comment', 'post_id', 'together'=>false),
    );
}

Для жадной загрузки мы можем задать этот параметр динамически:

$posts = Post::model()->with(array('comments'=>array('together'=>false)))->findAll();

8. Статистический запрос

Помимо реляционных запросов, описанных выше, Yii также поддерживает так называемые статистические запросы (или запросы агрегирования). Этот тип запросов используется для получения агрегированных данных, относящихся к связанным объектам (количество комментариев к каждой записи, средний рейтинг для каждого наименования продукции и т.д.). Статистические запросы могут быть использованы только для связей типа HAS_MANY (например, у записи есть много комментариев) или MANY_MANY (например, запись принадлежит многим категориям, а категориия может относиться ко множеству записей).

Выполнение статистического запроса аналогично выполнению реляционного запроса. Первым делом необходимо объявить статистический запрос в методе relations() класса CActiveRecord.

class Post extends CActiveRecord
{
    public function relations()
    {
        return array(
            'commentCount'=>array(self::STAT, 'Comment', 'post_id'),
            'categoryCount'=>array(self::STAT, 'Category', 'post_category(post_id, category_id)'),
        );
    }
}

Выше мы объявили два статистических запроса: commentCount подсчитывает количество комментариев к записи, а categoryCount считает количество категорий, к которым относится запись. Обратите внимание, что связь между Post и Comment — типа HAS_MANY, а связь между Post и Category — типа MANY_MANY (с использованием преобразующей таблицы post_category). Как можно видеть, способ объявления похож на объявление связей, описанных выше. Единственное различие состоит в том, что в данном случае тип связи равен STAT.

За счёт объявленных связей мы можем получить количество комментариев к записи, используя выражение $post->commentCount. В момент первого обращения к данному свойству для получения соответствующего результата неявным образом выполняется SQL-запрос. Как мы уже говорили, это называется подходом отложенной загрузки. Можно также использовать жадный вариант загрузки, если необходимо получить количество комментариев к нескольким записям:

$posts=Post::model()->with('commentCount', 'categoryCount')->findAll();

Выражение выше выполняет три SQL-запроса для получения всех записей вместе с количеством комментариев к ним и числом категорий. В случае отложенной загрузки нам бы понадобилось выполнить 2*N+1 SQL-запросов для N записей.

По умолчанию статистический запрос считает количество с использованием выражения COUNT. Его можно уточнить путём указания дополнительных параметров в момент объявления в методе relations(). Доступные параметры перечислены ниже:

  • select: статистическое выражение, по умолчанию равно COUNT(*), что соответствует количеству связанных объектов;
  • defaultValue: значение, которое присваивается в случае, если результат статистического запроса пуст. Например, если запись не имеет ни одного комментария, то свойству commentCount будет присвоено это значение. По умолчанию значение данного параметра равно 0;
  • condition: соответствует SQL оператору WHERE, по умолчанию значение параметра пустое;
  • params: параметры для связывания в генерируемом SQL-выражении. Параметры передаются как массив пар имя-значение;
  • order: соответствует SQL оператору ORDER BY, по умолчанию значение параметра пустое;
  • group: соответствует SQL оператору GROUP BY, по умолчанию значение параметра пустое;
  • having: соответствует SQL оператору HAVING, по умолчанию значение параметра пустое.

9. Реляционные запросы с именованными группами условий

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

Следующий код иллюстрирует случай их применения к основной модели:

$posts=Post::model()->published()->recently()->with('comments')->findAll();

Данный код очень похож на нереляционный запрос. Единственное отличие состоит в том, что присутствует вызов with() после вызовов групп условий. Данный запрос вернёт недавно опубликованные записи вместе с комментариями к ним.

В следующем примере показано, как применить группы условий к связанным моделям:

$posts=Post::model()->with('comments:recently:approved')->findAll();
// или, начиная с версии 1.1.7
$posts=Post::model()->with(array(
    'comments'=>array(
        'scopes'=>array('recently','approved')
    ),
))->findAll();
// или, начиная с версии 1.1.7
$posts=Post::model()->findAll(array(
    'with'=>array(
        'comments'=>array(
            'scopes'=>array('recently','approved')
        ),
    ),
));

Этот запрос вернёт все записи вместе с одобренными комментариями. Здесь comments соответствует имени связи. recently и approved — именованные группы, описанные в модели Comment. Имя связи и группы условий разделяются двоеточием.

Вам может понадобится использовать вместо «жадной» выборки «отложенную» для связи с группой условий. Синтаксис для этого такой:

// имя связи comments повторяется два раза
$approvedComments = $post->comments('comments:approved');

Именованные группы могут быть использованы при описании связей модели в методе CActiveRecord::relations() в параметре with. В следующем примере при обращении к $user->posts вместе с публикациями будут получены все одобренные комментарии.

class User extends CActiveRecord
{
    public function relations()
    {
        return array(
            'posts'=>array(self::HAS_MANY, 'Post', 'author_id',
                'with'=>'comments:approved'),
        );
    }
}
// или, начиная с версии 1.1.7
class User extends CActiveRecord
{
    public function relations()
    {
        return array(
            'posts'=>array(self::HAS_MANY, 'Post', 'author_id',
                'with'=>array(
                    'comments'=>array(
                        'scopes'=>'approved'
                    ),
                ),
            ),
        );
    }
}

В версии 1.1.7 появилась возможность передавать параметры именованным группам условий связи. К примеру, если в Post есть именованная группа условий rated, принимающая минимальный рейтинг записи, использовать её в User можно следующим образом:

Примечание: до версии 1.1.7 именованные группы условий, применяемые к реляционным моделям, должны быть описаны в CActiveRecord::scopes. Поэтому они не могут быть параметризованы.

$users=User::model()->findAll(array(
    'with'=>array(
        'posts'=>array(
            'scopes'=>array(
                'rated'=>5,
            ),
        ),
    ),
));

class Post extends CActiveRecord
{
    ......

    public function rated($rating)
    {
        $this->getDbCriteria()->mergeWith(array(
            'condition'=>'rating=:rating',
            'params'=>array(':rating'=>$rating),
        ));
        return $this;
    }

    ......
}

10. Реляционные запросы с through

При использовании through определение связи должно выглядеть следующим образом:

'comments'=>array(self::HAS_MANY,'Comment',array('key1'=>'key2'),'through'=>'posts'),

В коде выше, а именно в array('key1'=>'key2'):

  • key1 — ключ, определённый в связи, на которую указывает through (в нашем случае posts).
  • key2 — ключ, определённый в модели, на которую указывает связь (в нашем случае Comment).

through может использоваться с HAS_ONE, BELONGS_TO и HAS_MANY.

HAS_MANY through

HAS_MANY through ER

HAS_MANY through ER

Пример использования HAS_MANY с through — получение пользователей, состоящих в определённой группе, если они записаны в группу через роли.

Более сложным примером является получение всех комментариев для всех пользователей определённой группы. В этом случае необходимо использовать несколько связей с through в одной модели:

class Group extends CActiveRecord
{
   ...
   public function relations()
   {
       return array(
           'roles'=>array(self::HAS_MANY,'Role','group_id'),
           'users'=>array(self::HAS_MANY,'User',array('user_id'=>'id'),'through'=>'roles'),
           'comments'=>array(self::HAS_MANY,'Comment',array('id'=>'user_id'),'through'=>'users'),
       );
   }
}

Примеры

// получаем все группы с соответствующими им пользователями
$groups=Group::model()->with('users')->findAll();

// получаем все группы с соответствующими им пользователями и ролями
$groups=Group::model()->with('roles','users')->findAll();

// получаем всех пользователей и роли для группы с ID, равным 1
$group=Group::model()->findByPk(1);
$users=$group->users;
$roles=$group->roles;

// получаем все комментарии для группы с ID, равным 1
$group=Group::model()->findByPk(1);
$comments=$group->comments;

HAS_ONE through

HAS_ONE through ER

HAS_ONE through ER

Пример использования HAS_ONE с through — получение адреса пользователя в случае, если пользователь связан с адресом через профиль. Все задействованные сущности (пользователь, профиль и адрес) имеют соответствующие им модели:

class User extends CActiveRecord
{
   ...
   public function relations()
   {
       return array(
           'profile'=>array(self::HAS_ONE,'Profile','user_id'),
           'address'=>array(self::HAS_ONE,'Address',array('id'=>'profile_id'),'through'=>'profile'),
       );
   }
}

Примеры

// получаем адрес пользователя с ID, равным 1
$user=User::model()->findByPk(1);
$address=$user->address;

through с собой

through можно использовать для модели, связанной с собой через мост. В нашем случае это пользователь, обучающий других пользователей:

through self ER

through self ER

Связи для данного случая определяются следующим образом:

class User extends CActiveRecord
{
   ...
   public function relations()
   {
       return array(
           'mentorships'=>array(self::HAS_MANY,'Mentorship','teacher_id','joinType'=>'INNER JOIN'),
           'students'=>array(self::HAS_MANY,'User',array('student_id'=>'id'),'through'=>'mentorships','joinType'=>'INNER JOIN'),
       );
   }
}

Примеры

// получаем всех студентов учителя с ID, равным 1
$teacher=User::model()->findByPk(1);
$students=$teacher->students;

Active Record — Полное руководство по Yii — YiiFramework.ru

Active Record — Полное руководство по Yii — YiiFramework.ru.

Active Record

Хотя DAO Yii справляется практически с любыми задачами, касающимися работы с БД, почти наверняка 90% времени уйдёт на написание SQL-запросов, реализующих общие операции CRUD (создание, чтение, обновление и удаление). Кроме того, код, перемешанный с SQL-выражениями, поддерживать проблематично. Для решения этих проблем мы можем воспользоваться Active Record.

Active Record реализует популярный подход объектно-реляционного проецирования (ORM). Каждый класс AR отражает таблицу (или представление) базы данных, экземпляр AR — строку в этой таблице, а общие операции CRUD реализованы как методы AR. В результате мы можем использовать более объектно-ориентированный подход доступа к данным. Например, используя следующий код, можно вставить новую строку в таблицу tbl_post:

$post=new Post;
$post->title='тестовая запись';
$post->content='содержимое записи';
$post->save();

Ниже мы покажем, как настроить и использовать AR для реализации CRUD-операций, а в следующем разделе — как использовать AR для работы со связанными таблицами. Для примеров в этом разделе мы будем использовать следующую таблицу. Обратите внимание, что при использовании БД MySQL в SQL-выражении ниже AUTOINCREMENT следует заменить на AUTO_INCREMENT.

CREATE TABLE tbl_post (
    id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    title VARCHAR(128) NOT NULL,
    content TEXT NOT NULL,
    create_time INTEGER NOT NULL
);

Примечание: AR не предоставляет решения всех задач, касающихся работы с базами данных. Лучше всего использовать AR для моделирования таблиц в конструкциях PHP и для несложных SQL-запросов. В сложных случаях следует использовать Yii DAO.

Соединение с базой данных

Для работы AR требуется подключение к базе данных. По умолчанию предполагается, что компонент приложения db предоставляет необходимый экземпляр класса CDbConnection, который отвечает за подключение к базе. Ниже приведён пример конфигурации приложения:

return array(
    'components'=>array(
        'db'=>array(
            'class'=>'system.db.CDbConnection',
            'connectionString'=>'sqlite:path/to/dbfile',
            // включить кэширование схем для улучшения производительности
            // 'schemaCachingDuration'=>3600,
        ),
    ),
);

Подсказка: Поскольку для получения информации о полях таблицы AR использует метаданные, требуется некоторое время для их чтения и анализа. Если не предполагается, что схема базы данных будет меняться, то следует включить кэширование схемы, установив для атрибута CDbConnection::schemaCachingDuration любое значение больше нуля.

В настоящий момент AR поддерживает следующие СУБД:

Если вы хотите использовать другой компонент вместо db или предполагаете, используя AR, работать с несколькими БД, то следует переопределить метод CActiveRecord::getDbConnection(). Класс CActiveRecord является базовым классом для всех классов AR.

Подсказка: Есть несколько способов для работы AR с несколькими БД. Если схемы используемых баз различаются, то можно создать разные базовые классы AR с различной реализацией метода getDbConnection(). В противном случае проще будет динамически менять статическую переменную CActiveRecord::db.

Определение AR-класса

Для доступа к таблице БД, прежде всего, требуется определить класс AR путём наследования класса CActiveRecord. Каждый класс AR представляет одну таблицу базы данных, а экземпляр класса — строку в этой таблице. Ниже приведён минимальный код, требуемый для определения класса AR, представляющего таблицу tbl_post.

class Post extends CActiveRecord
{
    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }

    public function tableName()
    {
        return 'tbl_post';
    }
}

Подсказка: Поскольку классы AR часто появляются во многих местах кода, мы можем вместо включения классов по одному, подключить всю директорию с AR-классами. Например, если AR-классы находятся в директории protected/models, мы можем сконфигурировать приложение следующим образом:

return array(
  'import'=>array(
      'application.models.*',
  ),
);

По умолчанию имя AR-класса совпадает с названием таблицы в базе данных. Если они различаются, потребуется переопределить метод tableName(). Метод model() объявляется для каждого AR-класса.

Информация: В случае использования префиксов таблиц метод tableName() AR-класса может быть переопределён следующим образом:

public function tableName()
{
    return '{{post}}';
}

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

Значения полей в строке таблицы доступны как атрибуты соответствующего экземпляра AR-класса. Например, код ниже устанавливает значение для атрибута title:

$post=new Post;
$post->title='тестовая запись';

Хотя мы никогда не объявляем заранее свойство title класса Post, мы, тем не менее, можем обратиться к нему как в коде выше. Это возможно потому, что title является полем таблицы tbl_post, и CActiveRecord делает его доступным в качестве свойства благодаря магическому методу PHP __get(). Если аналогичным образом обратиться к несуществующему полю, будет выброшено исключение.

Информация: В данном руководстве мы именуем столбцы и таблицы в нижнем регистре, так как различные СУБД работают с регистрозависимыми именами по-разному. Например, PostgreSQL считает имена столбцов регистронезависимыми по умолчанию, и мы должны заключать их в кавычки в условиях запроса, если имена содержат заглавные буквы. Использование нижнего регистра помогает избежать подобных проблем.

AR опирается на правильно определённые первичные ключи таблиц БД. Если в таблице нет первичного ключа, то требуется указать в соответствующем классе AR столбцы, которые будут использоваться как первичный ключ. Сделать это можно путём перекрытия метода primaryKey():

public function primaryKey()
{
    return 'id';
    // Для составного первичного ключа следует использовать массив:
    // return array('pk1', 'pk2');
}

Создание записи

Для добавления новой строки в таблицу БД нам необходимо создать новый экземпляр соответствующего класса, присвоить значения атрибутам, ассоциированным с полями таблицы, и вызвать метод save() для завершения добавления.

$post=new Post;
$post->title='тестовая запись';
$post->content='содержимое тестовой записи';
$post->create_time=time();
$post->save();

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

Если поле задано в схеме таблицы с некоторым статическим значением по умолчанию (например, строка или число), то после создания экземпляра соответствующее свойство экземпляра AR будет автоматически содержать это значение. Один из способов поменять это значение — прописать его в AR-классе.

class Post extends CActiveRecord
{
    public $title='пожалуйста, введите заголовок';
    …
}

$post=new Post;
echo $post->title;  // отобразится: пожалуйста, введите заголовок

До сохранения записи (добавления или обновления) атрибуту может быть присвоено значение типа CDbExpression. Например, для сохранения текущей даты, возвращаемой функцией MySQL NOW(), можно использовать следующий код:

$post=new Post;
$post->create_time=new CDbExpression('NOW()');
// $post->create_time='NOW()'; этот вариант работать не будет
// т.к. значение 'NOW()' будет воспринято как строка
$post->save();

Подсказка: Несмотря на то что AR позволяет производить различные операции без написания громоздкого SQL, часто необходимо знать, какой SQL выполняется на самом деле. Для этого необходимо включить журналирование. Например, чтобы выводить выполненные SQL-запросы в конце каждой страницы, мы можем включить CWebLogRoute в настройках приложения. Можно установить значение параметра CDbConnection::enableParamLogging в true для отображения значений параметров запросов.

Чтение записи

Для чтения данных из таблицы базы данных можно использовать методы find:

// найти первую строку, удовлетворяющую условию
$post=Post::model()->find($condition,$params);
// найти строку с указанным значением первичного ключа
$post=Post::model()->findByPk($postID,$condition,$params);
// найти строку с указанными значениями атрибутов
$post=Post::model()->findByAttributes($attributes,$condition,$params);
// найти первую строку, используя некоторое выражение SQL
$post=Post::model()->findBySql($sql,$params);

Выше мы вызываем метод find через Post::model(). Запомните, что статический метод model() обязателен для каждого AR-класса. Этот метод возвращает экземпляр AR, используемый для доступа к методам уровня класса (что-то схожее со статическими методами класса) в контексте объекта.

Если метод find находит строку, соответствующую условиям запроса, он возвращает экземпляр класса Post, свойства которого содержат значения соответствующих полей строки таблицы. Далее мы можем использовать загруженные значения аналогично обычным свойствам объектов, например, echo $post->title;.

В случае если в базе нет данных, соответствующих условиям запроса, метод find вернет значение null.

Параметры $condition и $params используются для уточнения запроса. В данном случае $condition может быть строкой, соответствующей оператору WHERE в SQL-выражении, а $params — массивом параметров, значения которых должны быть привязаны к маркерам, указанным в $condition. Например:

// найдём строку, для которой postID равен 10
$post=Post::model()->find('postID=:postID', array(':postID'=>10));

Примечание: В примере выше нам может понадобиться заключить в кавычки обращение к столбцу postID для некоторых СУБД. Например, если мы используем СУБД PostgreSQL, нам следует писать условие как "postID"=:postID, потому что PostgreSQL по умолчанию считает имя столбца регистронезависимым.

Кроме того, можно использовать $condition для указания более сложных условий запроса. Вместо строки параметр $condition может быть экземпляром класса CDbCriteria, который позволяет указать иные условия помимо выражения WHERE. Например:

$criteria=new CDbCriteria;
$criteria->select='title';  // выбираем только поле 'title'
$criteria->condition='postID=:postID';
$criteria->params=array(':postID'=>10);
$post=Post::model()->find($criteria); // $params не требуется

Обратите внимание, если в качестве условия запроса используется CDbCriteria, то параметр $params уже не нужен, поскольку его можно указать непосредственно в CDbCriteria, как показано выше.

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

$post=Post::model()->find(array(
    'select'=>'title',
    'condition'=>'postID=:postID',
    'params'=>array(':postID'=>10),
));

Информация: В случае когда условие заключается в соответствии значениям некоторых полей, можно воспользоваться методом findByAttributes(), где параметр $attributes представляет собой массив значений, проиндексированных по имени поля. В некоторых фреймворках эта задача решается путём использования методов типа findByNameAndTitle. Хотя такой способ и выглядит привлекательно, часто он вызывает путаницу и проблемы, связанные с чувствительностью имён полей к регистру.

В случае если условию запроса отвечает множество строк, мы можем получить их все, используя методы findAll, приведённые ниже. Как мы отметили ранее, каждый из этих методов имеет find аналог.

// найдём все строки, удовлетворяющие условию
$posts=Post::model()->findAll($condition,$params);
// найдём все строки с указанными значениями первичного ключа
$posts=Post::model()->findAllByPk($postIDs,$condition,$params);
// найдём все строки с указанными значениями атрибутов
$posts=Post::model()->findAllByAttributes($attributes,$condition,$params);
// найдём все строки, используя SQL-выражение
$posts=Post::model()->findAllBySql($sql,$params);

В отличие от find, методы findAll, в случае если нет ни одной строки, удовлетворяющей запросу, возвращают не null, а пустой массив.

Помимо методов find и findAll, описанных выше, для удобства также доступны следующие методы:

// определим количество строк, удовлетворяющих условию
$n=Post::model()->count($condition,$params);
// определим количество строк, используя указанное SQL-выражение
$n=Post::model()->countBySql($sql,$params);
// определим, есть ли хотя бы одна строка, удовлетворяющая условию
$exists=Post::model()->exists($condition,$params);

Обновление записи

Заполнив экземпляр AR значениями полей, мы можем изменить их и сохранить обратно в БД.

$post=Post::model()->findByPk(10);
$post->title='new post title';
$post->save(); // сохраняем изменения

Как можно было заметить, мы используем метод save() как для добавления, так и для обновления записей. Если экземпляр AR создан с использованием оператора new, то вызов метода save() приведёт к добавлению новой строки в базу данных. Если же экземпляр AR создан как результат вызова методов find и findAll, вызов метода save() обновит данные существующей строки в таблице. На самом деле, можно использовать свойство CActiveRecord::isNewRecord для указания, является экземпляр AR новым или нет.

Кроме того, можно обновить одну или несколько строк в таблице без их предварительной загрузки. Для этого в AR существуют следующие методы уровня класса:

// обновим строки, отвечающие заданному условию
Post::model()->updateAll($attributes,$condition,$params);
// обновим строки, удовлетворяющие заданному условию и значению первичного ключа (или нескольким значениям ключей)
Post::model()->updateByPk($pk,$attributes,$condition,$params);
// обновим поля-счётчики в строках, удовлетворяющих заданным условиям
Post::model()->updateCounters($counters,$condition,$params);

Здесь $attributes — это массив значений полей, проиндексированных по имени поля, $counters — массив инкрементных значений, проиндексированных по имени поля, $condition и $params аналогичны описанным выше.

Удаление записи

Мы можем удалить строку, если экземпляр AR был заполнен значениями этой строки.

$post=Post::model()->findByPk(10); // предполагаем, что запись с ID=10 существует
$post->delete(); // удаляем строку из таблицы

Обратите внимание, что после удаления экземпляр AR не меняется, но соответствующей записи в таблице уже нет.

Следующие методы используются для удаления строк без их предварительной загрузки:

// удалим строки, соответствующие указанному условию
Post::model()->deleteAll($condition,$params);
// удалим строки, соответствующие указанному условию и первичному ключу (или нескольким ключам)
Post::model()->deleteByPk($pk,$condition,$params);

Проверка данных

Часто во время добавления или обновления строки нам требуется проверить, соответствуют ли значения полей некоторым правилам. Особенно это важно в случае, если данные поступают со стороны клиента. В подавляющем большинстве случаев таким данным доверять нельзя.

AR осуществляет проверку данных автоматически в момент вызова метода save(). Проверка основана на правилах, заданных в методе AR-класса rules(). Детально ознакомиться с тем, как задаются правила проверки, можно в разделе Определение правил проверки. Ниже приведён типичный порядок обработки в момент сохранения записи:

if($post->save())
{
    // данные корректны и успешно добавлены/обновлены
}
else
{
    // данные некорректны, сообщения об ошибках могут быть получены путём вызова метода getErrors()
}

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

$post->title=$_POST['title'];
$post->content=$_POST['content'];
$post->save();

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

// предполагаем, что $_POST['Post'] является массивом значений полей, проиндексированных по имени поля
$post->attributes=$_POST['Post'];
$post->save();

Сравнение записей

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

Информация: В отличие от реализации AR в других фреймворках, Yii поддерживает в AR составные первичные ключи. Составной первичный ключ состоит из двух и более полей таблицы. Соответственно, первичный ключ в Yii представлен как массив, а свойство primaryKey содержит значение первичного ключа для экземпляра AR.

Тонкая настройка

Класс CActiveRecord предоставляет несколько методов, которые могут быть переопределены в дочерних классах для тонкой настройки работы AR.

  • beforeValidate и afterValidate: методы вызываются до и после осуществления проверки;
  • beforeSave и afterSave: методы вызываются до и после сохранения экземпляра AR;
  • beforeDelete и afterDelete: методы вызываются до и после удаления экземпляра AR;
  • afterConstruct: метод вызывается для каждого экземпляра AR, созданного с использованием оператора new;
  • beforeFind: метод вызывается перед тем, как будет выполнен поисковый запрос (например, find(), findAll()).
  • afterFind: метод вызывается для каждого экземпляра AR, созданного в результате выполнения запроса.

Использование транзакций с AR

Каждый экземпляр AR содержит свойство dbConnection, которое является экземпляром класса CDbConnection. Поэтому при необходимости можно использовать транзакции, предоставляемые Yii DAO:

$model=Post::model();
$transaction=$model->dbConnection->beginTransaction();
try
{
    // поиск и сохранение — шаги, между которыми могут быть выполнены другие запросы,
    // поэтому мы используем транзакцию, чтобы удостовериться в целостности данных
    $post=$model->findByPk(10);
    $post->title='new post title';
    if($post->save())
        $transaction->commit();
    else
        $transaction->rollback();
}
catch(Exception $e)
{
    $transaction->rollback();
    throw $e;
}

Именованные группы условий

Информация: Идея групп условий позаимствована у Ruby on Rails.

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

Именованные группы чаще всего описываются в методе CActiveRecord::scopes() в виде пар имя-условие. Приведённый ниже код описывает две именованные группы условий для модели Post: published и recently:

class Post extends CActiveRecord
{public function scopes()
    {
        return array(
            'published'=>array(
                'condition'=>'status=1',
            ),
            'recently'=>array(
                'order'=>'create_time DESC',
                'limit'=>5,
            ),
        );
    }
}

Каждая группа описывается массивом, который используется для инициализации экземпляра CDbCriteria. К примеру, recently устанавливает значение order равным create_time DESC, а limit равным 5. Вместе эти условия означают, что будут выбраны пять последних публикаций.

Именованные группы условий обычно используются в качестве модификаторов для метода find. Можно использовать несколько групп одновременно для получения более точного результата. Например, чтобы найти последние опубликованные записи, можно использовать следующий код:

$posts=Post::model()->published()->recently()->findAll();

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

Примечание: Именованные группы могут быть использованы только совместно с методами уровня класса. Таким образом, метод должен вызываться при помощи ClassName::model().

Именованные группы условий с параметрами

Именованные группы условий могут быть параметризованы. Например, если нам необходимо изменять число выбираемых публикаций для группы recently, то вместо описания группы в методе CActiveRecord::scopes, мы должны создать новый метод с именем, совпадающим с названием группы условий:

public function recently($limit=5)
{
    $this->getDbCriteria()->mergeWith(array(
        'order'=>'create_time DESC',
        'limit'=>$limit,
    ));
    return $this;
}

Теперь, чтобы получить 3 последних опубликованных записи, можно использовать следующий код:

$posts=Post::model()->published()->recently(3)->findAll();

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

Группа условий по умолчанию

Класс модели может содержать группу условий по умолчанию, которая будет применяться ко всем запросам (включая реляционные). К примеру, на сайте реализована поддержка нескольких языков, и содержимое отображается на языке, выбранном пользователем. Так как запросов, связанных с получением данных, скорее всего, будет достаточно много, для решения этой задачи мы можем определить группу условий по умолчанию. Для этого необходимо переопределить метод CActiveRecord::defaultScope следующим образом:

class Content extends CActiveRecord
{
    public function defaultScope()
    {
        return array(
            'condition'=>"language='".Yii::app()->language."'",
        );
    }
}

Теперь к приведённому ниже вызову метода findAll будут автоматически применены наши условия:

$contents=Content::model()->findAll();

Примечание: Как группа условий по умолчанию, так и именованная группа условий применяются только к запросам типа SELECT и игнорируются при запросах вида INSERT, UPDATE или DELETE. Кроме того, нельзя использовать AR-модель для выполнения запросов внутри методов, объявляющих группы условий (как именованные, так и группы условий по умолчанию).