Saving Your Data

CakePHP makes saving model data a snap. Data ready to be saved should be passed to the model’s save() method using the following basic format:

Array
(
    [ModelName] => Array
    (
        [fieldname1] => 'value'
        [fieldname2] => 'value'
    )
)

Most of the time you won’t even need to worry about this format: CakePHP’s FormHelper, and model find methods all package data in this format. If you’re using FormHelper, the data is also conveniently available in $this->request->data for quick usage.

Here’s a quick example of a controller action that uses a CakePHP model to save data to a database table:

public function edit($id) {
    // Has any form data been POSTed?
    if ($this->request->is('post')) {
        // If the form data can be validated and saved...
        if ($this->Recipe->save($this->request->data)) {
            // Set a session flash message and redirect.
            $this->Session->setFlash('Recipe Saved!');
            return $this->redirect('/recipes');
        }
    }

    // If no form data, find the recipe to be edited
    // and hand it to the view.
    $this->set('recipe', $this->Recipe->findById($id));
}

When save is called, the data passed to it in the first parameter is validated using CakePHP’s validation mechanism (see Data Validation chapter for more information). If for some reason your data isn’t saving, be sure to check to see if some validation rules are being broken. You can debug this situation by outputting Model::$validationErrors:

if ($this->Recipe->save($this->request->data)) {
    // handle the success.
}
debug($this->Recipe->validationErrors);

There are a few other save-related methods in the model that you’ll find useful:

Model::set($one, $two = null)

Model::set() can be used to set one or many fields of data to the data array inside a model. This is useful when using models with the ActiveRecord features offered by Model:

$this->Post->read(null, 1);
$this->Post->set('title', 'New title for the article');
$this->Post->save();

Is an example of how you can use set() to update single fields, in an ActiveRecord approach. You can also use set() to assign new values to multiple fields:

$this->Post->read(null, 1);
$this->Post->set(array(
    'title' => 'New title',
    'published' => false
));
$this->Post->save();

The above would update the title and published fields and save the record to the database.

Model::clear()

This method can be used to reset model state and clear out any unsaved data and validation errors.

New in version 2.4.

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

Featured above, this method saves array-formatted data. The second parameter allows you to sidestep validation, and the third allows you to supply a list of model fields to be saved. For added security, you can limit the saved fields to those listed in $fieldList. When using a fieldList the primary key will be included in the fieldList automatically.

Note

If $fieldList is not supplied, a malicious user can add additional fields to the form data (if you are not using SecurityComponent), and by this change fields that were not originally intended to be changed.

The save method also has an alternate syntax:

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

$params array can have any of the following available options as keys:

  • validate Set to true/false to enable/disable validation.

  • fieldList An array of fields you want to allow for saving.

  • callbacks Set to false to disable callbacks. Using ‘before’ or ‘after’ will enable only those callbacks.

  • counterCache (since 2.4) Boolean to control updating of counter caches (if any)

  • atomic (since 2.6) Boolean to indicate you want records saved in a transaction.

More information about model callbacks is available here

Tip

If you don’t want the modified field to be automatically updated when saving some data add 'modified' => false to your $data array

Once a save has been completed, the ID for the object can be found in the $id attribute of the model object - something especially handy when creating new objects.

$this->Ingredient->save($newData);
$newIngredientId = $this->Ingredient->id;

Creating or updating is controlled by the model’s id field. If $Model->id is set, the record with this primary key is updated. Otherwise a new record is created:

// Create: id isn't set or is null
$this->Recipe->create();
$this->Recipe->save($this->request->data);

// Update: id is set to a numerical value
$this->Recipe->id = 2;
$this->Recipe->save($this->request->data);

Tip

When calling save in a loop, don’t forget to call clear().

If you want to update a value, rather than create a new one, make sure you are passing the primary key field into the data array:

$data = array('id' => 10, 'title' => 'My new title');
// This will update Recipe with id 10
$this->Recipe->save($data);

Model::create(array $data = array())

This method resets the model state for saving new information. It does not actually create a record in the database but clears Model::$id and sets Model::$data based on your database field defaults. If you have not defined defaults for your database fields, Model::$data will be set to an empty array.

If the $data parameter (using the array format outlined above) is passed, it will be merged with the database field defaults and the model instance will be ready to save with that data (accessible at $this->data).

If false or null are passed for the $data parameter, Model::$data will be set to an empty array.

Tip

If you want to insert a new row instead of updating an existing one you should always call create() first. This avoids conflicts with possible prior save calls in callbacks or other places.

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

Used to save a single field value. Set the ID of the model ($this->ModelName->id = $id) just before calling saveField(). When using this method, $fieldName should only contain the name of the field, not the name of the model and field.

For example, to update the title of a blog post, the call to saveField from a controller might look something like this:

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

Warning

You can’t stop the modified field being updated with this method, you need to use the save() method.

The saveField method also has an alternate syntax:

saveField(string $fieldName, string $fieldValue, array $params = array())

$params array can have any of the following available options as keys:

  • validate Set to true/false to enable/disable validation.

  • callbacks Set to false to disable callbacks. Using ‘before’ or ‘after’ will enable only those callbacks.

  • counterCache (since 2.4) Boolean to control updating of counter caches (if any)

Model::updateAll(array $fields, mixed $conditions)

Updates one or more records in a single call. Fields to be updated, along with their values, are identified by the $fields array. Records to be updated are identified by the $conditions array. If $conditions argument is not supplied or it is set to true, all records will be updated.

For example, to approve all bakers who have been members for over a year, the update call might look something like:

$thisYear = date('Y-m-d H:i:s', strtotime('-1 year'));

$this->Baker->updateAll(
    array('Baker.approved' => true),
    array('Baker.created <=' => $thisYear)
);

The $fields array accepts SQL expressions. Literal values should be quoted manually using DboSource::value(). For example if one of your model methods was calling updateAll() you would do the following:

$db = $this->getDataSource();
$value = $db->value($value, 'string');
$this->updateAll(
    array('Baker.status' => $value),
    array('Baker.status' => 'old')
);

Note

Even if the modified field exists for the model being updated, it is not going to be updated automatically by the ORM. Just add it manually to the array if you need it to be updated.

For example, to close all tickets that belong to a certain customer:

$this->Ticket->updateAll(
    array('Ticket.status' => "'closed'"),
    array('Ticket.customer_id' => 453)
);

By default, updateAll() will automatically join any belongsTo association for databases that support joins. To prevent this, temporarily unbind the associations.

Model::saveMany(array $data = null, array $options = array())

Method used to save multiple rows of the same model at once. The following options may be used:

  • validate: Set to false to disable validation, true to validate each record before saving, ‘first’ to validate all records before any are saved (default),

  • atomic: If true (default), will attempt to save all records in a single transaction. Should be set to false if database/table does not support transactions.

  • fieldList: Equivalent to the $fieldList parameter in Model::save()

  • deep: (since 2.1) If set to true, also associated data is saved; see also saveAssociated()

  • callbacks Set to false to disable callbacks. Using ‘before’ or ‘after’ will enable only those callbacks.

  • counterCache (since 2.4) Boolean to control updating of counter caches (if any)

For saving multiple records of single model, $data needs to be a numerically indexed array of records like this:

$data = array(
    array('title' => 'title 1'),
    array('title' => 'title 2'),
);

Note

Note that we are passing numerical indexes instead of usual $data containing the Article key. When saving multiple records of same model the records arrays should be just numerically indexed without the model key.

It is also acceptable to have the data in the following format:

$data = array(
    array('Article' => array('title' => 'title 1')),
    array('Article' => array('title' => 'title 2')),
);

To save also associated data with $options['deep'] = true (since 2.1), the two above examples would look like:

$data = array(
    array('title' => 'title 1', 'Assoc' => array('field' => 'value')),
    array('title' => 'title 2'),
);
$data = array(
    array(
        'Article' => array('title' => 'title 1'),
        'Assoc' => array('field' => 'value')
    ),
    array('Article' => array('title' => 'title 2')),
);
$Model->saveMany($data, array('deep' => true));

Keep in mind that if you want to update a record instead of creating a new one you just need to add the primary key index to the data row:

$data = array(
    array(
        // This creates a new row
        'Article' => array('title' => 'New article')),
    array(
        // This updates an existing row
        'Article' => array('id' => 2, 'title' => 'title 2')),
);

Model::saveAssociated(array $data = null, array $options = array())

Method used to save multiple model associations at once. The following options may be used:

  • validate: Set to false to disable validation, true to validate each record before saving, ‘first’ to validate all records before any are saved (default),

  • atomic: If true (default), will attempt to save all records in a single transaction. Should be set to false if database/table does not support transactions.

  • fieldList: Equivalent to the $fieldList parameter in Model::save()

  • deep: (since 2.1) If set to true, not only directly associated data is saved, but deeper nested associated data as well. Defaults to false.

  • counterCache (since 2.4) Boolean to control updating of counter caches (if any)

For saving a record along with its related record having a hasOne or belongsTo association, the data array should be like this:

$data = array(
    'User' => array('username' => 'billy'),
    'Profile' => array('sex' => 'Male', 'occupation' => 'Programmer'),
);

For saving a record along with its related records having hasMany association, the data array should be like this:

$data = array(
    'Article' => array('title' => 'My first article'),
    'Comment' => array(
        array('body' => 'Comment 1', 'user_id' => 1),
        array('body' => 'Comment 2', 'user_id' => 12),
        array('body' => 'Comment 3', 'user_id' => 40),
    ),
);

And for saving a record along with its related records having hasMany with more than two levels deep associations, the data array should be as follow:

$data = array(
    'User' => array('email' => '[email protected]'),
    'Cart' => array(
        array(
            'payment_status_id' => 2,
            'total_cost' => 250,
            'CartItem' => array(
                array(
                    'cart_product_id' => 3,
                    'quantity' => 1,
                    'cost' => 100,
                ),
                array(
                    'cart_product_id' => 5,
                    'quantity' => 1,
                    'cost' => 150,
                )
            )
        )
    )
);

Note

If successful, the foreign key of the main model will be stored in the related models’ id field, i.e. $this->RelatedModel->id.

For saving a record along with its related records having hasMany association and deeper associated Comment belongsTo User data as well, the data array should be like this:

$data = array(
    'Article' => array('title' => 'My first article'),
    'Comment' => array(
        array('body' => 'Comment 1', 'user_id' => 1),
        array(
            'body' => 'Save a new user as well',
            'User' => array('first' => 'mad', 'last' => 'coder')
        ),
    ),
);

And save this data with:

$Article->saveAssociated($data, array('deep' => true));

Warning

Be careful when checking saveAssociated calls with atomic option set to false. It returns an array instead of boolean.

Example of using fieldList with multiple models:

$this->SomeModel->saveAll($data, array(
    'fieldList' => array(
        'SomeModel' => array('field_1'),
        'AssociatedModel' => array('field_2', 'field_3')
    )
));

The fieldList will be an array of model aliases as keys and arrays with fields as values. The model names are not nested like in the data to be saved.

Changed in version 2.1: Model::saveAll() and friends now support passing the fieldList for multiple models.

You can now save deeper associated data as well with setting $options['deep'] = true;

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

The saveAll function is just a wrapper around the saveMany and saveAssociated methods. it will inspect the data and determine what type of save it should perform. If data is formatted in a numerical indexed array, saveMany will be called, otherwise saveAssociated is used.

This function receives the same options as the former two, and is generally a backwards compatible function. It is recommended using either saveMany or saveAssociated depending on the case.

Saving hasMany through data

Let’s see how data stored in a join table for two models is saved. As shown in the hasMany through (The Join Model) section, the join table is associated to each model using a hasMany type of relationship. Our example involves the Head of Cake School asking us to write an application that allows him to log a student’s attendance on a course with days attended and grade. Take a look at the following code.

// Controller/CourseMembershipController.php
class CourseMembershipsController extends AppController {
    public $uses = array('CourseMembership');

    public function index() {
        $this->set(
             'courseMembershipsList',
             $this->CourseMembership->find('all')
         );
    }

    public function add() {
        if ($this->request->is('post')) {
            if ($this->CourseMembership->saveAssociated($this->request->data)) {
                return $this->redirect(array('action' => 'index'));
            }
        }
    }
}

// View/CourseMemberships/add.ctp

<?php echo $this->Form->create('CourseMembership'); ?>
    <?php echo $this->Form->input('Student.first_name'); ?>
    <?php echo $this->Form->input('Student.last_name'); ?>
    <?php echo $this->Form->input('Course.name'); ?>
    <?php echo $this->Form->input('CourseMembership.days_attended'); ?>
    <?php echo $this->Form->input('CourseMembership.grade'); ?>
    <button type="submit">Save</button>
<?php echo  $this->Form->end(); ?>

The data array will look like this when submitted.

Array
(
    [Student] => Array
    (
        [first_name] => Joe
        [last_name] => Bloggs
    )

    [Course] => Array
    (
        [name] => Cake
    )

    [CourseMembership] => Array
    (
        [days_attended] => 5
        [grade] => A
    )

)

CakePHP will happily be able to save the lot together and assign the foreign keys of the Student and Course into CourseMembership with a saveAssociated call with this data structure. If we run the index action of our CourseMembershipsController the data structure received now from a find(‘all’) is:

Array
(
    [0] => Array
    (
        [CourseMembership] => Array
        (
            [id] => 1
            [student_id] => 1
            [course_id] => 1
            [days_attended] => 5
            [grade] => A
        )

        [Student] => Array
        (
            [id] => 1
            [first_name] => Joe
            [last_name] => Bloggs
        )

        [Course] => Array
        (
            [id] => 1
            [name] => Cake
        )
    )
)

There are of course many ways to work with a join model. The version above assumes you want to save everything at-once. There will be cases where you want to create the Student and Course independently and at a later point associate the two together with a CourseMembership. So you might have a form that allows selection of existing students and courses from pick lists or ID entry and then the two meta-fields for the CourseMembership, e.g.

// View/CourseMemberships/add.ctp

<?php echo $this->Form->create('CourseMembership'); ?>
    <?php
        echo $this->Form->input(
            'Student.id',
            array(
                'type' => 'text',
                'label' => 'Student ID',
                'default' => 1
            )
        );
    ?>
    <?php
        echo $this->Form->input(
            'Course.id',
            array(
                'type' => 'text',
                'label' => 'Course ID',
                'default' => 1
            )
        );
    ?>
    <?php echo $this->Form->input('CourseMembership.days_attended'); ?>
    <?php echo $this->Form->input('CourseMembership.grade'); ?>
    <button type="submit">Save</button>
<?php echo $this->Form->end(); ?>

And the resultant POST:

Array
(
    [Student] => Array
    (
        [id] => 1
    )

    [Course] => Array
    (
        [id] => 1
    )

    [CourseMembership] => Array
    (
        [days_attended] => 10
        [grade] => 5
    )
)

Again CakePHP is good to us and pulls the Student id and Course id into the CourseMembership with the saveAssociated.

Datatables

While CakePHP can have datasources that aren’t database driven, most of the time, they are. CakePHP is designed to be agnostic and will work with MySQL, Microsoft SQL Server, PostgreSQL and others. You can create your database tables as you normally would. When you create your Model classes, they’ll automatically map to the tables that you’ve created. Table names are by convention lowercase and pluralized with multi-word table names separated by underscores. For example, a Model name of Ingredient expects the table name ingredients. A Model name of EventRegistration would expect a table name of event_registrations. CakePHP will inspect your tables to determine the data type of each field and uses this information to automate various features such as outputting form fields in the view. Field names are by convention lowercase and separated by underscores.

Using created and modified

By defining a created and/or modified field in your database table as datetime fields (default null), CakePHP will recognize those fields and populate them automatically whenever a record is created or saved to the database (unless the data being saved already contains a value for these fields).

The created and modified fields will be set to the current date and time when the record is initially added. The modified field will be updated with the current date and time whenever the existing record is saved.

If you have created or modified data in your $this->data (e.g. from a Model::read or Model::set) before a Model::save() then the values will be taken from $this->data and not automagically updated. If you don’t want that you can use unset($this->data['Model']['modified']), etc. Alternatively you can override the Model::save() to always do it for you:

class AppModel extends Model {

    public function save($data = null, $validate = true, $fieldList = array()) {
        // Clear modified field value before each save
        $this->set($data);
        if (isset($this->data[$this->alias]['modified'])) {
            unset($this->data[$this->alias]['modified']);
        }
        return parent::save($this->data, $validate, $fieldList);
    }

}

If you are saving data with a fieldList and the created and modified fields are not present in the whitelist, the fields will continue to have the values automatically assigned. When included in the fieldList, the created and modified fields work like any other field.