Write operations

Index

Create new model

After insert, auto-update timestamps of the instance will not be updated automatically. But auto-incremented values will be updated. If you want to read timestamp values, use refresh.

create

Create a model from attributes. create method creates a new instance and insert it to database.

$attr = ['id' => 0, 'name' => 'abc'];
$customer = Customer::create($attr);

firstOrCreate

Search the object in the database with the specified attributes, and return it. If it can not be found, the new instance will be created and inserted to the database.

$attr = ['id' => 1, 'name' => 'abc'];
// Use primary key if there is no index number specified.
$customer = Customer::firstOrCreate($attr);

firstOrNew

Search the object in the database with the specified attributes, and return it. If it can not be found, the new instance will be created (without inserting it to database).

$attr = ['id' => 1, 'name' => 'abc'];
// Use primary key if there is no index number specified.
$customer = Customer::firstOrNew($attr);

Check attributes

Using values received from HTTP requests as the parameters of create method may causes passing invalid values. This can be a serious security hole.

Therefore, we must use only predetermined attributes. We can use either the white-list or the black-list to predetermine them.

If the white-list is specified, attributes not in white-list will be ignored.

class Customer extends Model
{
    protected static $fillable = ['id', 'name'];
}

If the black-list is specified, attributes in black-list will be ignored.

class Customer extends Model
{
    protected static $guarded = ['note'];
}

Save and delete models

Save

Save a model to the database. Auto-update timestamps of the model will not be updated automatically. But auto-incremented values will be updated.

$attr = ['id' => 1, 'name' => 'abc'];
$customer = Customer::firstOrNew($attr);
$customer->save();

save tries to update the record by primary key first, then insert it if it has failed.

Set belongs-to values by object

To associate the belongs-to object, use associate method of Relation objects.

$group = Group::find(1);
$customer = new Customer();
$customer->id = 0;
$customer->name = 'John';
$customer->group()->associate($group); // Copy $group->id to $customer->group_id.
$customer->save();

Delete

Delete the model from the database. An instance of the model will not be unset.

$customer = Customer::find(1);
$customer->delete();

The primary key will be used to delete.

Save with the associated models

Normally, save or delete does not affect the associated models. By specifying Model::SAVE_WITH_RELATIONS for $option parameter of save or delete method, you can save object with the associated models.

Note:

save is done in order, main object first, then associated objects.

$customer = Customer::find(1);
$customer->name = 'Akio';
$customer->address->street = '3-1-5 igawazyo';
// Update both of customer and address.
$customer->save(Model::SAVE_WITH_RELATIONS);

Collections

You can store models collectively with save method of Collection.

$grp = new Group();
$grp->id = 0;
$grp->name = 'Tokyo';

$customer1 = new Customer();
$customer1->id = 0;
$customer1->name = 'John';
$grp->customers->add($customer1);

$customer2 = new Customer();
$customer2->id = 0;
$customer2->name = 'Mike';
$grp->customers->add($customer2);

// Save $grp and customers in collection.
$grp->save(Model::SAVE_WITH_RELATIONS);

many-to-many Relation Models

To save only the added relation objects, you can use save method of Relation objects.

$customer = Customer::find(1);
$tag = Tag::find(1);
$customer->tags()->save($tag);

When you save the collection, related intermediate objects will be saved in the intermediate table automatically.

$customer = Customer::find(1);
$tag = Tag::find(1);
$tags = $customer->tags;
// Currentry $tags has 2 tags
$tags->add($tag);
DB::beginTransaction();
$tags->save(); // Saved 3 tags.
DB::commit();

If another user adds a tag to the same customer after you had read the tags of the customer, it may cause problems.

Collection::save method can delete items from the database before saving the collection, according to the conditions of the relationship. You can save perfectly with this way.

However, if you do not need to delete them, specify SAVE_BEFOERE_NO_DELETE for $saveOprions. It improves performance.

$tags->saveOprions = Collection::SAVE_BEFOERE_NO_DELETE;
$tags->save();

Update Conflict Check (UCC)

If there is a timestamp field that is updated automatically on updating, Transactd PHP ORM can detect update conflicts by multiple users.

$customer = Customer::find(1);
if ($customer->setUpdateConflictCheck(true) === false) {
    echo 'This table does not have timestamp fields.';
} else {
    // (omitted)
    // If another user updated the same customer (id = 1)...
    try {
        $customer->delete(); // IOException will be thrown.
    }
    catch(Transactd\IOException $e) {
        echo $e->getCode() . ':' . $e->getMessage() . PHP_EOL;
    }
}

Note: The automatic update timestamp of MySQL/MariaDB is recorded up to microsecond like 2016-12-06 11:49:26.466757. The times of two or more updates serialized by locks almost never become the same, so it is possible to detect updating.

However, this is not a 100% sure way. The time stamp is based on the system time, but the system time is changed by the user, NTP, etc. It also depends on OS and machine resolution.

If 100% certainty is required, add unsigned integer column such as named version, increment it every updating, and compare version instead of update_at. There is an example in Detect change conflict section in sample application.

ServerCursor

Get the current server cursor from a model.

serverCursor method in queryExecuter requires keyValue, but the method in the model does not require it because the model holds key and value. It is necessary to specify index, but it is added to the first parameter of serverCursor, not the method. If the default value null is specified, it is automatically set to primary key.

The prototype and usage examples are as follows:

serverCursor($index = null, $op = QueryExecuter::SEEK_EQUAL, $lockBias = Transactd::LOCK_BIAS_DEFAULT, $forword = true)
$scr = $customer->serverCursor();
$customer->name = 'akio';
$scr->update($customer);

See QueryExecuter - ServerCursor for more detail.

Use Transaction

It is required that open all tables which will be used before transaction. To open these tables, use prepareTable method.

$grp = new Group();
$grp->id = 0;
$grp->name = 'Tokyo';

$customer1 = new Customer();
$customer1->id = 0;
$customer1->name = 'John';
$grp->customers->add($customer1);

$customer2 = new Customer();
$customer2->id = 0;
$customer2->name = 'Mike';
$grp->customers->add($customer2);

// Prepare tables.
Customer::prepareTable();
Group::prepareTable();

DB::beginTransaction();
$grp->save(Model::SAVE_WITH_RELATIONS);
DB::commit();

Note: Transactions are valid only on one connection.

Events of write operations

In the writing process, you can handle two events, before writing and after writing. An event handler is a static function with following name:

save operation does not fire create or update events. It fires save events only.

Define event handlers

Define creating and created.

class Customer extends Model
{
    public static function creating($customer)
    {
        echo 'Creating a customer id = ' . $customer->id . PHP_EOL;
        return true; // continue
    }

    public static function created($customer)
    {
        echo 'Created a customer id = ' . $customer->id . PHP_EOL;
    }
}

If the pre-processing handler returns false, processing will be aborted.