CakePHP: Manual/Developing/Models/Saving

Сохранение данных

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

Array

(

    [ModelName] => Array

        (

            [fieldname1] => 'value'

            [fieldname2] => 'value'

        )

)

В большинстве случаев вам даже не надо будет беспокоится о формате: хелперы HtmlHelper, FormHelper, и find-методы все пакуют данные в этот формат. Если вы используете любой из хелперов, данные также удобно доступны в $this->data для быстрого использования.

Здесь пример действия контроллера, который использует модель CakePHP для сохранения данных в таблицу базы данных:

<?

function edit($id) {

    //Есть ли данные из формы для сохранения?

    if(!empty($this->data)) {

        //Если данные из формы прошли валидацию и сохранены ...

        if($this->Recipe->save($this->data)) {

            //Вывести flash-сообщение и перенаправить в '/recipes'.

            $this->Session->setFlash("Рецепт сохранен!");

            $this->redirect('/recipes');

        }

    }

 

    //Если нет данных из формы, то найти рецепт для редактирования

    //и передать его в отображение.

    $this->set('recipe'$this->Recipe->findById($id));

}

?>

Одно дополнительно замечание: когда save() вызван, то данные, переданные в него первым параметром, проходят проверку через механизм валидации CakePHP (смотрите главу «Валидация данных»). Если по каким-то причинам ваши данные не сохраняются, то убедитесь не нарушаются ли правила валидации.

В модели есть несколько методов, касающихся сохранения данных:

save(array $data = null, boolean $validate = true, array $fieldList = array())

Этот метод сохраняет данные из массивов. Второй параметр позволяет вам отклонить валидацию, и с помощью третьего параметра вы можете передать перечень полей модели для сохранения. Для дополнительной безопасности вы можете ограничить сохраняемые поля в этом списке $fieldList.

Метод save также имеет альтернативный синтаксис:

save(array $data = null, array $params = array())

Массив $params может иметь любые, из следующих опций, в качестве индекса массива:

<?

array(

    'validate' => true,

    'fieldList' => array(),

    'callbacks' => true //other possible values are false, 'before', 'after'

)

?>

Дополнительная информация о колбэках модели доступна здесь.

После того, как сохранение завершено, ID для объекта может быть найдено в переменной $id объекта модели – это особенно удобно, когда создаете новые объекты.

<?

$this->Ingredient->save($newData);

$newIngredientId $this->Ingredient->id;

?>

create(array $data = array())

Этот метод возвращает модель в исходное состояние для сохранения новой информации.

Если параметр $data (используя формат массива, приведенный выше) передан, экземпляр модели будет готов сохранить с этим данные (доступные через $this->data).

saveField(string $fieldName, string $fieldValue, $validate = false)

Используется для сохранения значений отдельных полей. Просто установите ID ($this->ModelName->id = $id) перед вызовом saveField(). Когда используете этот метод, то $fieldName должно содержать только имя поля, а не имя модели и поля.

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

<? this->Post->saveField('title''A New Title for a New Day'); ?>

updateAll(array $fields, array $conditions)

Обновляет много записей за один вызов. Записи, которые следует обновить определяются массивом $conditions, а поля для обновления, вместе с их значениями, – массивом $fields .

Например, для поощрения всех пекарей, которые работают больше года, update-вызов может выглядеть так:

<?

$this_year date('Y-m-d h:i:s'strtotime('-1 year'));

$this->Baker->updateAll(

    array('Baker.approved' => true),

    array('Baker.created <=' => "$this_year")

);

?>

Например, для бронирования всех билетов для конкретного заказчика можно написать такой код:

<?

$this->Ticket->updateAll(

    array('Ticket.status' => "'closed'"),

    array('Ticket.customer_id' => 453)

);

?>

saveAll(array $data = null, array $options = array())

Исользуется для сохранения

– многочисленных записей для одной модели

– текущей записи со всеми ассоциированными записями

Следующие опции могут быть использованы:

validate: Установите в false для отключения проверки валидности, true – для проверки валидности каждой записи перед сохранением, 'first' – для проверки *all* записей перед началом сохранения, или 'only' – только для проверки, но без их последующего сохранения.

atomic: Если true (по умолчанию), то попытается сохранить все записи в одной транзакции. Должен быть установлен в false, если база данных/таблица не поддерживает транзакции. Если false, то будет возвращен массив, похожий на переданный массив $data, но значения будут установлены в true/false, в зависимости от того успешно была сохранена данная запись или нет.

fieldList: Эквивалент параметра $fieldList в Model::save()

Для сохранения множества записей одной модели, $data должен быть численно проиндексирован:

Array

(

    [0] => Array

        (

            [title] => title 1

        )

    [1] => Array

        (

            [title] => title 2

        )

)

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

<? $this->Article->saveAll($data['Article']); ?>

Для сохранения записи вместе со связанной записью, имеющей ассоциации hasOne или belongsTo, массив данных должен выглядеть так:

Array

(

    [User] => Array

        (

            [username] => billy

        )

    [Profile] => Array

        (

            [sex] => Male

        [occupation] => Programmer

        )

)

Команда для сохранения этих данных будет выглядеть так:

<? $this->Article->saveAll($data); ?>

Для сохранения записи вместе с связанными записями, имеющих hasMany ассоциацию, массив данных должен выглядеть так:

Array

(

    [Article] => Array

        (

            [title] => My first article

        )

    [Comment] => Array

        (

            [0] => Array

                (

                    [comment] => Comment 1

            [user_id] => 1

                )

        [1] => Array

                (

                    [comment] => Comment 2

            [user_id] => 2

                )

        )

)

Команда для сохранения этого массива $data будет выглядеть так:

<? $this->Article->saveAll($data); ?>

Сохранение данных ассоциированных моделей (hasOne, hasMany, belongsTo)

Когда работаете с ассоциированными моделями, то важно осознавать, что сохранение данных модели должно быть сделано соответствующей моделью. Если вы сохраняете новый пост (Post) и ассоциированные ему комментарии (Comments), то вы будете использовать обе модели Post и Comment.

Если ни одна запись ассоциированных моделей еще не существует (например, вы хотите сохранить нового пользователя (User) и его записи профиля (Profile) в одно и то же время), то вам надо сначала сохранить первичную, или родительскую модель.

Для того, чтобы представить, как это работает, предположим, что у нас есть действие в нашем UsersController, которое управляет сохранением нового пользователя (User) и относящегося к нему профиля (Profile). Пример действия, представленный ниже, предполагает, что вы внесли достаточно данных (используюя FormHelper) для создания пользователя и профиля.

<?php

function add() {

    if (!empty($this->data)) {

        // Мы можем сохранить данные о пользователе:

        // они будут в $this->data['User']

 

        $user $this->User->save($this->data);

        // Если пользователь был сохранен, то мы добавляем эту информацию

        // в данные и сохраняем профиль.

      

        if (!empty($user)) {

            // ID только что созданного пользователя хранится

            // в $this->User->id.

            $this->data['Profile']['user_id'] = $this->User->id;

            // Поскольку наш пользователь имеет один (hasOne) профиль , то 

            // у нас есть доступ к модели Profile через модель User:

            $this->User->Profile->save($this->data);

        }

    }

}

?>

Основная идея при сохранении данных ассоциированных моделей заключается в том, чтобы получить значение первичного ключа одной модели и поместить его в качестве значения вторичного (внешнего) ключа в другую модель. Иногда, это включает использование переменной модели $id после save(). В других случаях можно получить ID из скрытого инпута в форме, значение которого передается в действие контроллера.

В дополнение к базовому подходу CakePHP также предлагает очень удобный метод saveAll(), который позволяет вам проверить валидность и сохранить множество моделей за раз. Также, saveAll() поддерживает транзакции для обеспечения целостности данных в 

вашей БД (например, если одна модель не сохранилась, то другие модели тоже не будут сохраняться.)

Для того, чтобы транзакции корректно работали в MySQL, 

ваши таблицы должны использовать механизм InnoDB. 

Помните, что MyISAM таблицы не поддерживают транзакции.

Давайте посмотрим, как мы можем использовать saveAll() для сохранения моделей Company и Account одновременно.

Во-первых, вам необходимо создать формы для этих моделей (мы предполагаем, что Company hasMany Account).

<?

echo $form->create('Company', array('action'=>'add'));

echo $form->input('Company.name', array('label'=>'Имя компании'));

echo $form->input('Company.description');

echo $form->input('Company.location');

echo $form->input('Account.0.name', array('label'=>'Имя счета'));

echo $form->input('Account.0.username');

echo $form->input('Account.0.email');

echo $form->end('Add');

?>

Обратите внимание на способ названия полей формы для модели Account. Если Company – это наша главная модель, то saveAll() будет ожидать данные связанной модели в особом формате. И Account.0.fieldName – это то, что нужно.

Именование полей, приведенное выше, требуется для ассоциаций hasMany. 

Если ассоциация между моделями - hasOne, то вы должны использовать 

нотацию ModelName.fieldName для этих моделей.

Теперь в нашем файле companies_controller мы можем создать действие add():

<?

function add() {

   if(!empty($this->data)) {

      $this->Company->saveAll($this->data, array('validate'=>'first'));

   }

}

?>

Это все. Теперь наши модели Company и Account будут проверены на валидность данных и одновременно сохранены. Опция array('validate'=>'first') гарантирует, что обе модели проверены на валидность перед началом сохранения.


counterCache – Кэширование count()

Эта функция помогает вам кэшировать count связанных данных. Вместо подсчета количества записей вручную с помощью find('count'), модель сама отслеживает все вставки/удаления в ассоциированной через $hasMany модели и увеличивает/уменьшает значение специально выделенного поля типа integer.

Название поля состоит из имени модели в единственном числе, символа подчеркивания и слова “count”.

Допустим, у вас есть модель ImageAlbum и модель Image, тогда вам надо добавить INT-поле в таблицу “image_album” и назвать его “image_count”. Или, если ваши имена более сложные, тогда – другой пример: модели BlogEntry и BlogEntryComment, имя поля будет “blog_entry_comment_count” и должно быть добавлено в таблицу “blog_entries”.

После того, как вы добавили поле для подсчета записей, вы можете активировать эту функциональность, добавив индекс “counterCache” в ассоциативный массив "$belongsTo”, и присвоив ему значение “true”.

<?

class ImageAlbum extends AppModel {

    var $hasMany = array(

        'Image'

    );

}

class Image extends AppModel {

    var $belongsTo = array(

        'ImageAlbum' => array('counterCache' => true)

    );

}

?>

Теперь каждый раз, когда вы будете добавлять новое изображение Image в ImageAlbum число “image_count” будет увеличиваться (или уменьшаться, если вы делаете удаление).


Сохранение данных ассоциированных моделей (HABTM)

Сохранить модели, которые ассоциированы с помощью hasOne, belongsTo, и hasMany довольно просто: вы просто заполняете значение внешнего ключа значением ID ассоциированной модели и вызываете метод модели save(), и все корректно связывается.

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

Самая простая форма может выглядеть так (мы предполагаем, что $recipe_id уже имеет какое-то значение):

<?

<?php echo $form->create('Tag');?>

    <?php echo $form->input(

        'Recipe.id'

        array('type'=>'hidden''value' => $recipe_id)); ?>

    <?php echo $form->input('Tag.name'); ?>

    <?php echo $form->end('Add Tag'); ?>

?>

В этом примере вы можете увидеть скрытое поле Recipe.id, значение которого равно ID рецепта, с которым мы хотим связать тег.

Когда метод save() будет вызван из контроллера, то он автоматически сохранит данные HABTM в базу данных.

<?

function add() {

    

    //Сохранение ассоциации

    if ($this->Tag->save($this->data)) {

        //действия в случае успешного сохранения            

    }

}

?>

В предыдущем коде наш новый Tag создан и ассоциирован с рецептом Recipe, ID которого находится в $this->data['Recipe']['id'].

В других случаях, для отображения наших ассоциированных данных мы можем применить выпадающий список. Данные могут быть извлечены из модели с помощью метода find('list') и переданы в переменную отображения. Input с таким же именем автоматически поместит данные в <select>.

<?

// в контроллере:

$this->set('tags'$this->Recipe->Tag->find('list'));

// в отображении:

$form->input('tags');

?>

Более желательный вариант со связью HABTM будет включать <select>, позволяющий делать множественный выбор. Например, рецепт может иметь много тегов. В этом случае данные из модели извлекаются тем же путем, но форма input определяется по другому. Имя тега записывается в виде ModelName.ModelName.

<?

// в контроллере:

$this->set('tags'$this->Recipe->Tag->find('list'));

// в отображении:

$form->input('Tag.Tag');

?>

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

<< Получение данных | Удаление данных >>