Данная статься рассказывает о работа с базой данных в Yii через Active Record. Писал я ее скорее длясебя, для того, чтобы систематизировать материал из мануала, где-то упросить, где-то дополнить, так, чтобы этим было удобно для меня пользоваться как шпаргалкой. Надеюсь сей труд будет полезен кому-то еще.
Что такое Active Record
Yii предоставляет разработчику следующие инструменты для работы с базой данных:
Yii DAO - это надстройка над расширением PHP Data Objects (PDO), позволяющая работать с различными СУБД через единый интерфейс. Приложения, разработанные с использованием DAO, могут легко переключаться с одной СУБД на другую без необходимости исправления кода, отвечающего за доступ к данным. Отличие от Query Builder - работа с полным sql - запросом.Это самый быстрый, но и самый небезопасный способ работы с БД. Естественно, данные не проходят валидацию, как в AR.
Yii Query Builder - система использования объектов для создания и выполнения SQL комманд. В отличие от AR данные не проходят валидацию. Медленне, чем Yii DAO, но гораздо быстрее AR.
Yii Active Record (AR) реализует переработанный подход объектно-реляционного проецирования (ORM) и еще более упрощает работу с базами данных. Представляя таблицу базы данных как класс, а строки таблицы как экземпляры класса, Yii AR избавляет от необходимости написания SQL-выражений, связанных с операциями CRUD (создание, чтение, обновление и удаление).
Здесь мы будем рассматривать Yii Active Record (AR).
Соединение с базой данных
Для работы с AR требуется подключиться к базе данных. По умолчанию предполагается, что компонент приложения db предоставляет необходимый экземпляр класса CDbConnection, который отвечает за подключение к базе. Подключение происходит в файле main.php, который лежит по следующем пути /protected/config/. Все что нам нужно - расскоментировать нужные строки кода в зависимости от того, какую базу мы будем использовать. Я использую sqlite:
'db'=>array(
'connectionString' => 'sqlite:'.dirname(__FILE__).'/../data/blog.db',
'tablePrefix'=>'tbl_',
),
Определение AR-класса в модели
Для доступа к таблице БД, прежде всего, требуется определить класс AR путём наследования класса CActiveRecord. Ниже приведён минимальный код, требуемый для определения класса AR, представляющего таблицу tbl_post.
// Модель работы с постами
class Post extends CActiveRecord
{
public static function model($className=__CLASS__)
{
return parent::model($className);
}
public function tableName()
{
return 'tbl_post';
}
}
По умолчанию имя AR-класса совпадает с названием таблицы в базе данных.В случае использования префиксов таблиц метод tableName() AR-класса может быть переопределён следующим образом:
public function tableName()
{
return '{{post}}';
}
Создание записи
Тепрь в контроллере мы можем взаимодействоватьс БД. Для добавления новой строки в таблицу БД нам необходимо создать новый экземпляр соответствующего класса, одноименный модели.
// В контроллере создаем экземпляр соответствующего класса, одноименный модели. $post->title - ображение к полю таблице 'title'
$post=new Post;
$post->title='тестовая запись';
$post->content='содержимое тестовой записи';
$post->create_time=time();
$post->save();
Если первичный ключ таблицы автоинкрементный, то после добавления экземпляр AR будет содержать обновленное значение первичного ключа.
До сохранения записи (добавления или обновления) атрибуту может быть присвоено значение типаCDbExpression. Например, для сохранения текущей даты, возвращаемой функцией MySQL NOW(), можно использовать следующий код:
$post=new Post;
$post->create_time=new CDbExpression('NOW()');
$post->save();
Обновление записи
$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($criteria);
// обновим строки, удовлетворяющие заданному условию и значению первичного ключа (или нескольким значениям ключей)
Post::model()->updateByPk($id);
Сохранение одного или несколько счетчиков столбца для текущего объекта AR.
Метод доступен, начиная с версии 1.1.8
$postRecord=Post::model()->findByPk($postID);
$postRecord->saveCounters(array('view_count'=>1));
[B]Удаление записи
$post=Post::model()->findByPk(10); // предполагаем, что запись с ID=10 существует
$post->delete(); // удаляем строку из таблицы[/PHP]
Обратите внимание, что после удаления экземпляр AR не меняется, но соответствующей записи в таблице уже нет.
Следующие методы используются для удаления строк без их предварительной загрузки:
// удалим строки, соответствующие указанному условию
// Post::model()->deleteAll($criteria);
Post::model()->deleteAll($condition,$params);
// удалим строки, соответствующие указанному условию и первичному ключу (или нескольким ключам)
// Post::model()->deleteByPk($id);
Post::model()->deleteByPk($id,$condition,$params);
Проверка данных
// Происходит предварительная валидация в модели;
// чтобы отключить валидацию, нужно передать false $post->save(false)
if($post->save())
{
// данные корректны и успешно добавлены/обновлены
}
else
{
// данные некорректны, сообщения об ошибках могут быть получены путём вызова метода getErrors()
}
Чтение записей
Для чтения данных из таблицы базы данных можно использовать методы find:
//возвращает объект из базы по id;
$a = Book::model()->findByPk($id);
//массив объектов. На входе массив тех id о кот. нужна информация
$a = Book::model()->findAllByPk(array(1,2,4,5));
foreach ($a as $v){
echo $v->title;
}
// найти ПЕРВУЮ строку, удовлетворяющую условию
// $post=Post::model()->find($condition,$params);
// чтобы найти ВСЕ строки $post=Post::model()->findAll($condition,$params);
// ПРИМЕР1 найдём строку, для которой postID равен 10
$post=Post::model()->find('postID=:postID', array(':postID'=>10));
// ПРИМЕР2 составной запрос
$password = Rule::model()->find('password_id=:password_id AND user_name=:user_name', array(':password_id'=>$site_id, 'user_name'=>$user));
// ПРИМЕР3
$post=Post::model()->find(array(
'select'=>'title',
'condition'=>'postID=:postID',
'params'=>array(':postID'=>10),
));
// ПРИМЕР3 найдем все строки, где postID равен 10
$post=Post::model()->findAll(array(
'select'=>'title',
'condition'=>'postID=:postID',
'params'=>array(':postID'=>10),
));
/*Кроме того, можно использовать $condition для указания более сложных условий запроса. Вместо строки параметр $condition может быть экземпляром класса CDbCriteria*/
$criteria=new CDbCriteria;
$criteria->select='title'; // выбираем только поле 'title'
$criteria->condition='postID=:postID';
$criteria->params=array(':postID'=>10);
$post=Post::model()->find($criteria); // $params не требуется
//ПРИМЕР1 найти первую строку, используя некоторое выражение SQL
//$post=Post::model()->findBySql($sql,$params);
$post=Post::model()->findBySql('SELECT title FROM {{book}} WHERE year = :num',array(':num' => $num));
//ПРИМЕР2 найти все строки, используя некоторое выражение SQL
$post=Post::model()->findAllBySql('SELECT title FROM {{book}} WHERE year = :num',array(':num' => $num));
Подсчет количества строк, удовлетворяющих условию
// определим количество строк, удовлетворяющих условию
$n=Post::model()->count($condition,$params);
// определим количество строк, используя указанное SQL-выражение
$n=Post::model()->countBySql($sql,$params);
// определим, есть ли хотя бы одна строка, удовлетворяющая условию
$exists=Post::model()->exists($condition,$params);
// ПРИМЕР использования
$count = self::model()->count(array(
'select'=>'id',
'condition'=>'user_id=:user_id',
'params'=>array(':user_id'=>1),
));
// в $count число строк
Сортировка
Сортировка в экшене контроллера
// Сортировка в CDbCriteria
$criteria = new CDbCriteria;
$criteria->order = 'id DESC';
$reports = Report::model()->findAll($criteria);
// Сортировка в методе findAll
$users_emails = Report::model()->findAll(array(
'select'=>'*',
'condition'=>'user_id=:user_id',
'params'=>array(':user_id'=>Yii::app()->user->id),
'order'=> 'id DESC',
));
Сортировка по умолчанию в модели в методе search()
public function search()
{
$criteria = new CDbCriteria;
$criteria->compare('id', $this->id, true);
return new CActiveDataProvider($this, array(
'criteria' => $criteria,
'sort' => array (
'defaultOrder'=>array(
'id'=>CSort::SORT_ASC,
)
)
));
}
Пример использовани CActiveDataProvider
public function actionIndex()
{
$dataProvider=new CActiveDataProvider('Message', array(
'criteria'=>array(
'condition'=>'user_id=:user_id',
// сортировка сначала по полю read, потом id
'order'=>'read ASC, id DESC',
'params'=>array(':user_id'=>1),
),
// количество итемов на стр
'pagination'=>array(
'pageSize'=>10,
),
));
// отправляем в шаблон
$this->render('index',array(
'dataProvider'=>$dataProvider,
));
}
Итератор для провайдеров данных
$dataProvider = new CActiveDataProvider("User");
$iterator = new CDataProviderIterator($dataProvider);
foreach($iterator as $user) {
echo $user->name."\n";
}
Реляционные запросы
Связывание таблиц через relations (ленивая загрузка)
Данный способ более медленный, чем "нетерпеливая загрузка", т.к. идет несколько запросов к связываемым таблица, без участия join.
BELONGS_TO: многие к одному (статьи принадлежат категории);
HAS_MANY: один ко многим (категории принадлежат статьи);
HAS_ONE: это частный случай HAS_MANY, где А может иметь максимум одно В (например, у User есть только один Profile);
MANY_MANY: это отношение соответствует типу отношения многие-ко-многим в БД.
//'имя связи' => array(ТИП_СВЯЗИ, 'имя табл. к кот. привязываемся', 'имя поля для связи у привязанной табл.')
//находимся в модели Page. Задача:определить категорию для страниц (многие к одному)
'category'=>array(self::BELONGS_TO, 'category', 'category_id'),
или
//находимся в модели Category. Задача:определить страницы, относящиеся к категории (один ко многим)
'page' => array(self::HAS_MANY, 'page', 'category_id')
Нетерпелива загрузка
// используется LEFT JOIN
// вернуться все посты с принадлежащими им авторами. Если автор не задана - null
$posts=Post::model()->with('author')->findAll();
C использованием criteria
$criteria=new CDbCriteria;
$criteria->with=array(
'author.profile',
'author.posts',
'categories',
);
$posts=Post::model()->findAll($criteria);
Устранение конфликта имён столбцов
При совпадении имён столбцов в двух и более соединяемых таблицах, приходится разрешать конфликт имён. Это делается при помощи добавления псевдонима таблицы к имени столбца.
В реляционном запросе псевдоним главной таблицы всегда равен t, а псевдоним связанной таблицы по умолчанию равен имени связи. В приведённом ниже коде псевдонимы таблиц для моделей Post иComment будут соответственно t и comments:
$posts=Post::model()->with('comments')->findAll(array(
'order'=>'t.create_time, comments.create_time'
));