Delete model with relations

To delete in Yii a model with some relations we usually have at least three options.

Depending on many factors (RDBMS type, table type, situation, context etc.) most of them have advantages and disadvantages. Good developer already knows these options, so intention of this post is not to discuss them, but to show you an interesting, new (at least for me) fourth approach to solve this problem, that I found recently.

First, let's do the quick check list. Options, you have for deleting model with relations are:

  1. On database side, using cascade deletes.
  2. On code side, using beforeDelete event in Yii.
  3. On code side, using pure SQL.

Tip: No matter, which solution you choose, you must wrap it in SQL transaction so relational models' deletes will be rolled-back upon any error in deleteing them or master record.

Option two is the simplest and quickest one and involves writing something like this:

public function beforeDelete()
{
    foreach($this->relatedModels as $relatedModel) $relatedModel->delete();

    return parent::beforeDelete();
}

However, the biggest doubt here (and most often risen counterargument), is that your using a loop and deleting each relational model in separate SQL query, which kills anything about performance theory. This solution could (should) be used for operations, that are issued very rarely, for example, when deleting some page content, like blog entry or menu.

Now, let's see that new thing, that I found recently. It is a conclusion or extension to above solution, but wrapped in more generic approach.

We have relations() method in each Yii model, which defines... relations. Author of spotted generic solutions proposes adding new method to model, called deletableRelations:

function deletableRelations()
{
    return array('blog_comments', 'items');
}

And define some generic function:

function deleteRelationships($model)
{
    foreach($model->deletableRelations() as $relation)
    {
        if($model->$relation == null) continue;

        if(is_array($model->$relation))
        {
            foreach($model->$relation as $relatedRecord) $relatedRecord->delete();
        }
        else $model->$relation->delete();
    }
}

Again, we need to do this in transaction plus, we need to make sure, that recursion does not happen here. For example, when master is deleting relations, while one or more delation is attempting to delete master as well. With if($model->$relation == null) continue; checking, such recursion shouldn't kill our script, but is bad even so and should be avoided.

Let me just add, that implementing this in own base model (ActiveRecord), and in form of beforeDelete event, from which (not from CActiveRecord) all models will extend should be a good idea for making this solution really generic.

Leave a Reply