We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

Save related records onCreate in transaction-like way

I have a related has-many table. And there is a record needed to be created for any new record for the main table. As I read, this is the transaction-like way:

<?php
$acc = new Account();
$acc->login = 'blablabla';
$data = new Data();
$data->var = 'somevar';
$data->value = someval'';
$acc->data = [$data]; // php 5.4 array syntax
$acc->create();

and it works.

But why does don't work this?

<?php
class Account extends Phalcon\Mvc\Model
{
// ...
    public function beforeCreate()
    // public function beforeValidationOnCreate() // -- also have been tried
    {
        $data = new Data();
        $data->var = 'somevar';
        $data->value = someval'';
        $this->data = [$data]; // php 5.4 array syntax
    }

There is just nothing saved to the 'data' table. Logically I expected it would work like the fist one.

There just must always be a such record creation for any account creation and It has to be saved in transaction-like way for safety. Because the data is calculated from an account's fields, it is much easier to create an account like this "$account->create($_POST)" and data would be created automatically.

Seems like I just doing something wrong, because the second way is really very useful and simple to has the right to exist.



98.9k

A bit lengthly reason. Related records can't be added to the model in the before* callbacks, this happens because the ORM doesn't know if the record exists or not in order to call beforeCreate or beforeUpdate.

A related record could be used to fill the primary key, consider the following example:

CREATE TABLE comments (
  uuid char(36) NOT NULL,
  posts_id int(10) unsigned NOT NULL,
  comment text,
  PRIMARY KEY (uuid,posts_id)
);
CREATE TABLE posts (
  id int(10) unsigned NOT NULL AUTO_INCREMENT,
  title varchar(64) NOT NULL,
  content text NOT NULL,
  PRIMARY KEY (id)
);

The posts_id field is part of the primary key in the table "comments". This field is filled with an auto_numeric field generated right after save a "posts" row.

$post = new Posts();
$post->title = 'Some cool title';
$post->content = 'This is the content';

$comment->uuid = SomeUUIdGenerator();
$comment->comment = 'Hey, this is a comment';
$comment->post = $post;
$comment->save();

The $post must be saved first to fill the primary key of $comment (posts_id). But $comment is the parent save(), so $comment->save() must take a decision, what should I call? beforeCreate or beforeUpdate? The unique way to know that is having completely filled its primary key (both uuid and posts_id), so the ORM must know if the record exists to call beforeUpdate or beforeCreate with confidence/consistency.

When $comment->save() is called the ORM designs a plan to save the parent record + its related records in the correct order, in a safe way, calling the right callbacks, notifying the right behaviors and according to the type of relations used, so changing that plan on the run is not allowed.

Here, you can add a method that adds the $post before save() is called:

<?php

class Comments extends Phalcon\Mvc\Model
{
     public function prependPost()
     {
         $post = new Posts();
         $post->title = 'Some cool title';
         $post->content = 'This is the content';
         $this->post = $post;
     }
}

Then call it before save:

$comment->uuid = SomeUUIdGenerator();
$comment->comment = 'Hey, this is a comment';
$comment->prependPost();
$comment->save();

Or override the save method to assign the related record:

<?php

class Comments extends Phalcon\Mvc\Model
{
     public function save($data=null)
     {
           if (!$this->posts_id) {
                $post->title = 'Some cool title';
                $post->content = 'This is the content';
                $this->post = $post;
           }
           return parent::save($data);
     }
}


32.4k

Thanлs, was very cognitive in terms of understanding framework work. Think, overriding may become solution.

That reason should be described in documentation.

A bit lengthly reason. Related records can't be added to the model in the before* callbacks, this happens because the ORM doesn't know if the record exists or not in order to call beforeCreate or beforeUpdate.

A related record could be used to fill the primary key, consider the following example:

CREATE TABLE comments (
 uuid char(36) NOT NULL,
 posts_id int(10) unsigned NOT NULL,
 comment text,
 PRIMARY KEY (uuid,posts_id)
);
CREATE TABLE posts (
 id int(10) unsigned NOT NULL AUTO_INCREMENT,
 title varchar(64) NOT NULL,
 content text NOT NULL,
 PRIMARY KEY (id)
);

The posts_id field is part of the primary key in the table "comments". This field is filled with an auto_numeric field generated right after save a "posts" row.

$post = new Posts();
$post->title = 'Some cool title';
$post->content = 'This is the content';

$comment->uuid = SomeUUIdGenerator();
$comment->comment = 'Hey, this is a comment';
$comment->post = $post;
$comment->save();

The $post must be saved first to fill the primary key of $comment (posts_id). But $comment is the parent save(), so $comment->save() must take a decision, what should I call? beforeCreate or beforeUpdate? The unique way to know that is having completely filled its primary key (both uuid and posts_id), so the ORM must know if the record exists to call beforeUpdate or beforeCreate with confidence/consistency.

When $comment->save() is called the ORM designs a plan to save the parent record + its related records in the correct order, in a safe way, calling the right callbacks, notifying the right behaviors and according to the type of relations used, so changing that plan on the run is not allowed.

Here, you can add a method that adds the $post before save() is called:

<?php

class Comments extends Phalcon\Mvc\Model
{
    public function prependPost()
    {
        $post = new Posts();
        $post->title = 'Some cool title';
        $post->content = 'This is the content';
        $this->post = $post;
    }
}

Then call it before save:

$comment->uuid = SomeUUIdGenerator();
$comment->comment = 'Hey, this is a comment';
$comment->prependPost();
$comment->save();

Or override the save method to assign the related record:

<?php

class Comments extends Phalcon\Mvc\Model
{
    public function save($data=null)
    {
          if (!$this->posts_id) {
               $post->title = 'Some cool title';
               $post->content = 'This is the content';
               $this->post = $post;
          }
          return parent::save($data);
    }
}