Aldo Bassanini
Aldo Bassanini

Reputation: 485

Using Active Record, how can I save child's detail information through its parent?

I'm using parent->child (master->detail) relation in Yii2 Active Record

When I want to create a child, I have to manually fill its parent info like this:

Relation: Client (1) ---> (n) Comments

class ClientController extends \yii\web\Controller
{

    public function actionAddComment() {
        $comment = new Comment;
        if ($comment->load(Yii::$app->request->post())) {
            $comment->client = $this->id;  // Client id
            $comment->save();
        }
        return $this->render('view', ['comment'=>$comment]);

    }
}

I've optimized it, creating a Comment method to do that:

class Comment extends ActiveRecord {

    public function newComment($client) {
        $comment = new Comment;
        $comment->client = $client;  // Client id
        return $comment;
    }
}

And I have gone through beforeSave in the Comment model, but still not sure if there is a better way.

Is there anything like:

$comment = new Comment(Yii::$app->request->post());
$client->save($comment);  // Here the parent is writing his information to the child

Or one-liner shortcut:

$client->save(new Comment(Yii::$app->request->post());

Without having to create this logic in beforeSave?

Upvotes: 1

Views: 343

Answers (1)

Salem Ouerdani
Salem Ouerdani

Reputation: 7886

Yes, I recommend to use the built in link() and unlink() methods provided by Active Record which you can use in your controller to relate or unrelate 2 models either they share many-to-many or one-to-many relationship.

It even has an optional $extraColumns attribute for additional column values to be saved into a junction table if using it link( $name, $model, $extraColumns = [] )

So your code may look like this :

$comment = new Comment;
if ($comment->load(Yii::$app->request->post())) {
    $comment->link('client', $this);
}

check docs for more info.

Now about where to use this code to relate models, it depend on how your app is structured. I'm not sure if doing that through a triggered event would be a good practice, you need to remember that errors may happens and you may need to evaluate certain scenarios or logic before throwing exceptions. So in my case, I prefer to use that code into my Controllers.

Sometimes you need to build a specific action like you did actionAddComment(), In certain other cases like when your Post request is meant to update the Parent model and also update its related child models at once, the Parent's Update Action ClientController::actionUpdate() may be a good place to do so, maybe something like this will do the job :

$params = Yii::$app->request->post();

$client->load($this->params, '');
if ($client->save() === false && !$client->hasErrors()) {
    throw new ServerErrorHttpException('Failed to update the object for unknown reason.');
}

foreach ($params["comments"] as $comment) {
  // We may be sure that both models exists before linking them.
  // In this case I'm retrieving the child model from db so I don't
  // have to validate it while i just need its id from the Post Request
  $comment = Comment::findOne($comment['id']);
  if (!$comment) throw new ServerErrorHttpException('Failed to update due to unknown related objects.');
  // according to its documentation, link() method will throw an exception if unable to link the two models.
  $comment->link('client', $client);     
  ...

Upvotes: 1

Related Questions