Шаг 4 : Модель
Рубрика: Первые шаги
3 Авг. 2009
Модель это класс для работы приложения с базой. Для каждой таблицы с которой предстоит работать — создается своя модель (в Yii это класс наследуемый от CModel или производного от него)...
Вы должны понимать что модель является очень нужной "прокладкой" между вашим приложением и базой данных. Она может включать в себя проверку данных, ассоциации, связи с другими моделями и многое другое.
Наследуя нашу модель от класса CActiveRecord мы получаем большое кол-во функционала для работы (встроенные функции в виде find, findAll, count и все другие прелести ActiveRecord).
Хочу сразу заметить что если вы пропустили "Урок 3 : MVC" - вам будет тяжело вникнуть в курс дела.
Модель в действии
Модели обычно располагаются в папке protected/models и называются аналогично таблице с которой работают. К примеру, модель которая работает с таблицей post может быть названа "Post.php", "Posts.php" и др. Строгого правила как должна называться модель - не существует.
Давайте посмотрим простой пример модели которую я использую в своем блоге. Она имеет стандартный функционал для добавления и обновления записей.
<?php
// Создаем класс Posts и наследуем его от CActiveRecord
class Posts extends CActiveRecord
{
// Переопределение данного метода - обязательно для каждой модели
// не стоит редактировать его если вы не понимаете к чему он
public static function model($className=__CLASS__)
{
return parent::model($className);
}
// Указываем имя таблицы с которой работает данная модель
public function tableName()
{
return 'post'; // таблица "posts"
}
/**
* Добавляем новый пост
*
*/
public function new_posts() {
// ...
}
/**
* Обновляем пост в базе
*
* @param unknown_type $id
*/
public function edit_posts($id) {
// ...
}
}
Первые два метода (tableName, model) являются обязательным для модели. Такие функции как создание, чтение, обновление и удаление (CRUD) — уже встроены в базовый класс модели. Поэтому если вы хотите просто добавить новый пост в базу — совсем не обязательно описывать для этого отдельный метод в модели. (как это сделал я)
Как говорится «не стоит заново изобретать велосипед», поэтому перед тем как что-то добавить в модель — стоит проверить не встроен ли в неё этот функционал разработчиками Yii. К примеру, для добавления/редактирования поста мы можем использовать встроенный метод save():
<?php
// создаем экземпляр класса модели (Posts)
$date = new Posts();
// поле title заполняем нужным нам значением
// предполагаеться что title это существующее поле в таблице post
$date->title = "Заголовок";
// поле date заполняем текущей датой
// предполагаеться что title это существующее поле в таблице post
$date->date = date("Y-m-d");
// добавялем в базу.
$date->save(); // или $date->insert();
Согласитесь, всё очень просто. Про все тонкости ActiveRecord вы можете прочитать в отличной статье из оф. документации - «Actrive Records». Поэтому я советую познакомиться со всеми встроенными методами модели, прежде чем создавать свои.
Пример пользовательских методов
Пример модели Posts в которую были добавлены методы скрытия/отображения постов в блоге:
<?php
class Posts extends CActiveRecord
{
public static function model($className=__CLASS__)
{
return parent::model($className);
}
public function tableName()
{
return 'post';
}
/**
* Устанавливаем значение поля hidden для
* текущего поста - 1. Т.е. скрываем запись.
*/
public function hide() {
if (!empty($this->id)) {
$this->hidden = 1;
$this->save();
return true;
} else {
return false;
}
}
/**
* Устанавливаем значение поля hidden для
* текущего поста - 0. Т.е. показываем запись.
*/
public function unhide() {
if (!empty($this->id)) {
$this->hidden = 0;
$this->save();
return true;
} else {
return false;
}
}
}
Что бы воспользоваться этими методами в контроллере:
// Создаем экземпляр класса модели (Posts)
$my_post = new Posts();
// Заполняем значение поля id что бы знать какую запись мы редактируем
$my_post->id = 55;
// Обращаемся к методу hide
$my_post->hide();
Модель в контроллере
Работать с моделью в контроллере вы можете несколькими способами.
1. Создать экземпляр класса нужной модели и обратиться к её методами обычным способом:
$Posts = new Posts();
$Posts->my_method();
2.Через статический метод model() не создавая перед этим экземпляр модели:
Posts::model()->findAll(...);
Встроенный функционал
Теперь давайте рассмотрим часть популярных методов которые уже встроены в класс CActiveRecord и очень облегчают жизнь. Хочу сразу заметить что здесь описаны не все доступные методы по причине их огромного количества. Полный список доступных функций вы можете прочитать в API на английском языке.
find
- condition
- params
Возвращаем первую запись которая соответствует условию $condition. При этом $condition может быть представлен как WHERE SQL запрос, либо как критерия поиска (экземпляр класса CDbCriteria).
Давайте рассмотрим два примера использования $condition.
1. Как SQL
В качестве $condition используем where запрос с синонимами вместо значений. В $params передаем массив вида : синоним = значение.
MyModel::model()->find("id = :id AND login = :login", array(":id" => 5, ":login" => "test"))
2. Как Criteria
В качестве $condition используем созданный перед этим экземпляр класса CDbCriteria со всеми нужными критериями. Параметр $params в find использовать не надо (параметры задаются через критерию).
$criteria=new CDbCriteria;
$criteria->condition='id=:id AND login=:login';
$criteria->params=array(':id'=>5, ':login' => 'test'); // задаем параметры
$post=MyModel::model()->find($criteria);
findAll
- condition
- params
Возвращает все записи которые соответствуют условию $condition.
MyModel::model()->findAll("created = :created", array(":created" => "2008-01-22"))
Подробное описание параметров (condition, params) смотри в find()
findAllByAttributes
- attributes
- condition
- params
Возвращает все записи которые соответствуют условию в $attributes и $condition. При этом параметр $attributes должен содержать массив вида: «название атрибута» = значение.
# достаем все записи где дата создания (created) равна значению 2008-01-22
MyModel::model()->findByAttributes(array('created' => '2008-01-22'));
Подробное описание параметров (condition, params) смотри в find()
findByAttributes
- attributes
- condition
- params
Возвращает первую запись которая соответствует условию в $attributes и $condition.
Подробное описание параметров смотри в findAllByAttributes()
findByPk
- pk
- condition
- params
Возвращает первую запись которая отвечает запросу $condition и первичному ключу $pk
MyModel::model()->findByPk(1);
Подробное описание параметров (condition, params) смотри в find()
findAllByPk
- pk
- condition
- params
Возвращает все записи которые отвечают запросу $condition и первичному ключу $pk
Подробное описание параметров (condition, params) смотри в find()
findBySql
- sql
- params
Возвращает первую запись которая соответствует $sql.
Подробное описание параметров (params) смотри в find()
findAllBySql
- sql
- params
Возвращает все записи которые соответствую $sql.
Подробное описание параметров (params) смотри в find()
count
- condition
- params
Возвращает количество записей которые соответствуют $condition.
Подробное описание параметров (params) смотри в find()
countBySql
- sql
- params
Возвращает количество записей которые соответствую $sql.
Подробное описание параметров (params) смотри в find()
exists
- condition
- params
Проверяем есть ли хоть одна запись которая удовлетворяет условию $condition
Подробное описание параметров (params) смотри в find()
updateAll
- attributes
- condition
- params
Обновляет все строчки которые отвечают запросу $condition
Подробное описание параметров (condition, params) смотри в find()
updateByPk
- pk
- attributes
- condition
- params
Обновляет все строчки которые отвечают запросу $condition и первичному ключу $pk
Подробное описание параметров (condition, params) смотри в find()
delete
Удаляем текущую запись с которой работаем.
$model=MyModel::model()->findByPk(666);
$model->delete();
deleteAll
- condition
- params
Удалит все записи которые соответствую условию $condition.
Подробное описание параметров (condition, params) смотри в find()
deleteByPk
- pk
- condition
- params
Удалит все записи которые соответствую условию $condition и первичному ключу $pk.
Подробное описание параметров (condition, params) смотри в find()
validate
Проверяет что бы все атрибуты соответствовали правилам указанным в rules().
beforeSave
Метод который будет вызван до сохранения текущей записи с которой работаем (экземпляра AR). Для того чтобы выполнить какие либо действия перед сохранением записи в базу — достаточно просто переопределить данный метод.
afterSave
Метод который будет вызван после сохранения текущей записи с которой работаем (экземпляра AR). Для того чтобы выполнить какие либо действия после сохранения записи в базу — достаточно просто переопределить данный метод.
beforeDelete
Метод который будет вызван до удаления текущей записи с которой работаем (экземпляра AR). Для того чтобы выполнить какие либо действия до удаления записи из базы — достаточно просто переопределить данный метод.
afterDelete
Метод который будет вызван после удаления текущей записи с которой работаем (экземпляра AR). Для того чтобы выполнить какие либо действия после удаления записи из базы — достаточно просто переопределить данный метод.
beforeValidate
Метод который будет вызван перед методом validate(). Может быть использован для того что бы подготовить данные для валидации или других ситуаций.
afterValidate
Метод который будет вызван сразу после методом validate()
afterConstruct
Метод который является некой заменой конструктора модели. Метод вызывается для каждого экземпляра при использовании new.
// создаем экземпляр модели
$a = new MyModel();
// тут же автоматически вызывается метод afterConstruct()
afterFind
Метод который будет вызван для каждого экземпляра модели, который был создан при использовании метода Find. Например:
$a = MyModel::model()->findByPk(3);
tableName
Метод возвращает название таблицы с которой работает модель.
public function tableName()
{
return 'Users';
}
safeAttributes
Метод возвращает массив с безопасными атрибутами. Т.е. теми которые могут быть перезаписаны при массовом присваивании. Если вы хотите использовать safeAttributes() вам необходимо переопределить её в моделе.
Более подробно про safeAttributes() читайте:
- Урок 6 : Регистрация и авторизация. Часть 1. Пункт «Работа с моделью. Правила валидации.»
- «Создание модели»
attributeLabels
Метод возвращает массив соответствий. В нем необходимо перечислить какому атрибуту какое реальное название соответствует.
public function attributeLabels()
{
return array(
'login' => 'это у нас логин',
'passwd' => 'это у нас пароль',
}
Теперь в форме где мы используем Chtml::activeLabel вместо login/passwd будет отображаться текст указанный как соответствие.
save
Метод сохраняет текущие поля экземпляра AR. При этом самостоятельно анализирует факт наличия заполненного первичного ключа (в зависимости от этого делает update или insert)
Если первичный ключ явно не указан, значит save() выполнит insert:
$a = MyModel();
$a->login = «test»;
$a->save();
А вот так будет update:
$a = MyModel::model()->findByPk(3);
$a->login = «test»;
$a->save();
Теперь для записи у которой id равняется трём - логин будет изменен на «test».
Используя атрибут isNewRecord вы всегда можете проверить была вставлена новая запись, или обновлена существующая.
rules
В моделе вы можете указать правила (rules) для атрибутов. Это позволит контролировать какие данные приложение пытается сохранить в базу данных.
Проверка соответствия атрибутов вашим правилам будет происходить при вызове метода validate(). Поэтому перед тем как сохранить данные в базу — сначала делайте валидацию.
if ($my_model->validate()) {
$my_model->save();
}
Для того что бы использовать правила в вашем моделе — вам необходимо переопределить метод rules(). Для того что бы более подробно вникнуть в данный материал — вам помогут следующие статьи:
public function rules()
{
return array(
// поля login,passwd не должны быть пустыми
array('login,passwd', 'required'),
// поле login должно быть больше трёх и меньше 128-и символов
array('login', 'length', 'max'=>128, 'min' => 3),
);
}
Если хотите опубликовать этот материал у себя - пожалуйста, разместите ссылку на страницу откуда вы его взяли.
- Сегодня обнаружил что мой бывший сокурсник написал свой некий мод на Yii Blog. Исходные коды я не смотрел, но ... "Yii blog new [update]"
- Эта статья устарела т.к. была написана для yii версии 1.0.х; Если вы используете более новую версию - у вас могут ... "Урок 6 : Регистрация и авторизация. Часть 1"
- Эта статья устарела т.к. была написана для yii версии 1.0.х; Если вы используете более новую версию - у вас могут ... "Настройка 3d капчи на Yii"

[adm] zolter
Было сказано: Понедельник, 03 Август 2009
Данный вариант является черновым и в ближайшие пару дней будет подвергнут небольшим изменениями.

Bethrezen
Было сказано: Понедельник, 03 Август 2009
Модель используется не только для работы с базой. Думаю надо об этом написать. Нубы могут непроизвольно фантанировать а как мне просто проверить форму или тп.

[adm] zolter
Было сказано: Понедельник, 03 Август 2009
Согласен. Просто для себя не использую модель как форму т.к. в основном любая форма у меня - сохраняет или делает чтото с данными. следовательно - уже как AR работает.

[guest] Maxx
Было сказано: Пятница, 07 Август 2009
Спасибо за еще одну отличную статью! Теперь использую её как справочник

[guest] Aluc
Было сказано: Четверг, 05 Ноябрь 2009
"в основном любая форма у меня - сохраняет или делает чтото с данными. "
Это как?

[adm] zolter
Было сказано: Пятница, 06 Ноябрь 2009
У меня к примеру есть форма регистрации/авторизации. Но я не вижу смысла кидать эту форму как отдельную модель-формы т.к. само действие этой формы у меня связанно с таблицей Users. Т.е. в этой таблице у меня хранятся юзеры и естественно форма авторизации и регистрации работает с этой таблицей. Поэтому я не создают отдельный файл для каждой формы-модели. Всеми сохранениями данных, валидацией и тп - у меня занимается модель (в данном примере) Users. Которая занимается как валидацией и работой с данными юзеров, так и многими другими вещами связанными с пользователями. Т.е. как шампунь, 2в1. И без лишних файлов

[guest] Aluc
Было сказано: Суббота, 07 Ноябрь 2009
а все я понял о чем речь, туплю =)
Ну вот вопрос такой, какие преимущества использования DAO и AR? Помимо того что DAO позволяет легко работать с любыми БД, практически не меняя код.

[adm] zolter
Было сказано: Суббота, 07 Ноябрь 2009
DAO при выборке к примеру, возвращает массив результатов нужных полей. AR при выборке возвращает массив [обьектов] в нутри которых уже можно получить данные. При этом в AR все связи так же идут как обьекты.
Поэтому AR намного удобнее DAO в плане работы, но она возвращает кучу ненужных данных. Поэтому DAO работает быстрее. Представьте чего только стоит выбрать 1 000 000 обьектов с помощью AR и получить 1 000 000 объектов

[guest] Aluc
Было сказано: Суббота, 07 Ноябрь 2009
ээммм не получается использовать методы, созданные в моделе, использовать в контроллере =(
Использую Yii framework 1.1, может в нем как то по другому нужно?
Вылетает ошибка :
Articles does not have a method named "show".

[adm] zolter
Было сказано: Суббота, 07 Ноябрь 2009
А покажи как ты пробуешь его вызвать в контроллере.
Надо следующим образом:
$new = MyModel();
$new->my_new_method();
скорее всего ты пробуешь как MyModel::model()->my_new_method()

[guest] Гость
Было сказано: Суббота, 14 Ноябрь 2009
Я уже писал про проект кинотеатра.
У меня такой вопрос.
Есть запрос вида:
SELECT filmAll LEFT JOIN film ON filmAll.filmID=film.filmID WHERE filmAll.mktime_end>='".$mktime."' AND filmAll.cityID='".$cityID."'";
Понятно что 2 таблицы можно соеденить 'filmArr' => array(self::BELONGS_TO, 'film', 'filmID').
Но что-то я не могу понять как еще приделать к этому WHERE, т.е что нужно написать помимо этого для WHERE filmAll.mktime_end>='".$mktime."' AND filmAll.cityID='".$cityID."'";
Для не связаных таблиц я понимаю:
$criteria=new CDbCriteria;
$criteria->condition='id=:id AND login=:login';
$criteria->params=array(':id'=>5, ':login' => 'test'); // задаем параметры
$post=MyModel::model()->find($criteria);
А что писать и где для связанных

[adm] zolter
Было сказано: Суббота, 14 Ноябрь 2009
Вот пример из документации:
class User extends CActiveRecord
{
public function relations()
{
return array(
'posts'=>array(self::HAS_MANY, 'Post', 'authorID',
'order'=>'??.createTime DESC',
'with'=>'categories'),
'profile'=>array(self::HAS_ONE, 'Profile', 'ownerID'),
);
}
}

[adm] zolter
Было сказано: Суббота, 14 Ноябрь 2009
И примерно таким же образом вы можете использовать тут:
#
condition: соответствует оператору WHERE, по умолчанию значение параметра пустое. Имейте в виду, что ссылки на поля должны быть указаны однозначно посредством aliasToken (например, ??.id=10);
#
params: параметры для связывания в генерируемом SQL-выражении. Параметры передаются как массив пар имя-значение. Параметр доступен, начиная с версии 1.0.3;

[guest] Client
Было сказано: Вторник, 15 Декабрь 2009
Возник вопрос, а как подключить и работать с двумя Базами данных?

[adm] zolter
Было сказано: Вторник, 15 Декабрь 2009
Вот тут обсуждали - http://www.yiiframework.com/forum/index.php?/topic/5180-работа-с-несколькими-базами-одновременно/

Kros
Было сказано: Среда, 17 Март 2010
zolter, большое спасибо за первые шаги)), создал на фреймворке простенький тестовый сайт-визитку без каких-либо заторов, с бд и чисто на понимании, оказывается никакой магии и всё просто)), ваш блог лучший в теме обучения yii имхо :)
один вопрос, просто интересен технически, при обращении к моделе
$model = Pages::model()->findByAttributes(array('name' => $page));
$model = new Pages();
$model = $model->findByAttributes(array('name' => $page));
почему в 1ом случае мы обязательно вызываем model(), а во втором можно и без? для чего оно?

[guest] zolter
Было сказано: Среда, 17 Март 2010
Спасибо за хорошие слова :) Я был очень восхищен фреймворком когда это все создавал :)
По поводу вопроса, все очень просто.
В первом случае мы обращаемся к статическому методу модели минуя явно создание объекта. Это как бы краткая форма работы с данными модели когда надо получить список чего либо 1 раз.
Если вы много раз используете одну и туже модель в контроллере - то будет лучше один раз создать объект в $model к примеру, а затем уже обращаться к нему столько раз сколько надо.
+++
Плюс сюда же. Второй случай еще используется когда с модели не только надо получить данные, но и записать к примеру. Т.е:
$model = new Pages();
$model->pole1 = "test";
$model->pole2 = "test";
$model->save();
в случае с первым примером - такое сделать было бы не получилось.

Kros
Было сказано: Четверг, 18 Март 2010
тоже сейчас восхищён)))), почти всё свободное время с yii)), по теме всё оч понятно и логично, спасибо за исчерпывающий ответ :)

[guest] grizzly
Было сказано: Пятница, 07 Май 2010
А можете написать пост с более подробным описанием как работать с несколькими таблицами в моделе?

[guest] Гость
Было сказано: Пятница, 22 Июль 2011
Можно ли как-то указать в модели(или в контролере при вызове модели) в какой именно кодировке работать с конкретной таблицей? А то так по-дурацки вышло что в одной базе 1 таблица написана в другой кодировке и уже заполнена.

[guest] Гость
Было сказано: Суббота, 19 Ноябрь 2011
Спасибо автор!!!
Ваши статьи очень помогают вникнуть в суть yii.
Почему-то после такого внятного изложения начинаешь только понимать официальную доку. )

[guest] Бу
Было сказано: Пятница, 30 Декабрь 2011
Ребята, я туплю...
Экспериментирую тут. В моделе Post указал метод test:
public function test()
{
return array(
Post::model()->find("id = :id", array(":id" => "2")),
);
}
В контроллере PostController пытаюсь его вызвать:
$postTest = new Post();
$postTest->test();
Но безуспешно. Только не кидайте тапками в меня) Понимаю, простейшая задача. Но пока что плохо понимаю суть работы.

[guest] zolter
Было сказано: Понедельник, 02 Январь 2012
Угу, проблема в том что ты данные не сохранял в какую то переменную для дальнейшей обработки с ретурна.
$a = $postTest->test();
и дальше уже работаем с $a.
Кстати вместо конструкции:
Post::model()->find("id = :id", array(":id" => "2")),
Лично я предпочитаю использовать более понятный вариант:
Post::model()->findByAttributes(array('id' => 2))

[guest] Гость
Было сказано: Пятница, 13 Январь 2012
предыдущие статьи были гораздо лучше - подробнее и понятнее, и гораздо меньше грамматических ошибок.
Видимо,он так и остается черновым и по сей день

[guest] zolter
Было сказано: Пятница, 13 Январь 2012
Эта статья писалась больше чем 2 года назад, почти 3 :) С тех пор много изменилось и появилось нового в оф.документации

[guest] Гость
Было сказано: Суббота, 28 Январь 2012
После прочтения, написал простенький код:
//Вот есть у меня класс:
class Download extends CActiveRecord{
public function f() {
$this->count = 77;
$this->save();
}}
// Еще есть контроллер:
class DownloadController extends Controller{
public function actionDownload(){
$download = new Download();
$download->id = 3;
$download->f();
}}
Когда я вызываю action: Download, у меня создается запись с id=3 и count=77 если такой записи нет, но если запись уже есть, компилятор ругается: "PRIMARY KEY must be unique". Судя по статье, я ожидал, что в записи будет отредактированы указанные в функции f() поля, т.е. count=77.
Надо ли загружать полностью запись чтоб потом сохранить каждое ее поле?

[guest] Гость
Было сказано: Суббота, 28 Январь 2012
Разобрался, в доках http://www.yiiframework.ru/doc/guide/ru/database.ar написано: Если экземпляр ActiveRecord (AR) создан с использованием оператора new, то вызов метода save() приведет к добавлению новой строки в базу данных, в случае же, если экземпляр AR создан как результат вызова методов find и findAll, вызов метода save() обновит данные существующей строки в таблице. На самом деле, можно использовать свойство CActiveRecord::isNewRecord для указания, является экземпляр AR новым или нет.

[guest] zolter
Было сказано: Суббота, 28 Январь 2012
Все верно!
А еще если вы точно уверены в данных, то можно испльзовать $this->insert и $this->update, только предварительно пройдя их через validate()


