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

SoftDelete & soft deleting related records

SoftDelete behaviour is awesome and working as intended. However, I couldn't find a way to also soft delete related models. Any tips? Ideally there should be an event triggered for all related models

Is not the same way (SoftDelete in their model)?



5.7k

So after typing up a very long response with examples, I came across this?

https://docs.phalcon.io/en/latest/reference/models.html#cascade-restrict-actions

  <?php

  namespace Store\Models;

  use Phalcon\Mvc\Model;
  use Phalcon\Mvc\Model\Relation;

  class Robots extends Model
  {

      public $id;

      public $name;

      public function initialize()
      {
          $this->hasMany('id', 'Store\\Models\Parts', 'robots_id', array(
              'foreignKey' => array(
                  'action' => Relation::ACTION_CASCADE
              )
          ));
      }
  }

Before I found that, here was my original message:

Just to clarify with an example, is this what you're looking for?

Example:

Table1 : table_1_id, status
Table2 : table_2_id, table_1_id, status
Table3: table_3_id, table_1_id, status

Step 1. [Soft]Delete a record in Table1 (Change "status" to "Deleted")
Step 2. Find all reacords in Table2 and Table3 that belong to Table1 via table_1_id
Step 3. Update the "status" of all records found in Step2 as "Deleted" as well

If thats the case, you should be able to easily implement this by adding the SoftDelete behavior to all related Models. Then in Table1, delete related records in afterDelete. I haven't done thie before but here is the idea.


/**
 * Table1
 **/
class Table1 extends \Phalcon\Mvc\Model {

    public $table_1_id;

    public $status;

    public function initialize()
    {
        $this->addBehavior(new \Phalcon\Mvc\Model\Behavior\SoftDelete(
            [
                'field' => 'status',
                'value' => "Deleted"
            ]
        ));

        $this->hasMany('table_1_id','Table2','table_1_id');
        $this->hasMany('table_1_id','Table3','table_1_id');
    }

    public function afterDelete (){
        $this->getTable2()->delete();
        $this->getTable3()->delete();
    }

}

/**
 * Table2
 **/
class Table2 extends \Phalcon\Mvc\Model {

    public $table_2_id;

    public $table_1_id;

    public $status;

    public function initialize()
    {
        $this->addBehavior(new \Phalcon\Mvc\Model\Behavior\SoftDelete(
            [
                'field' => 'status',
                'value' => "Deleted"
            ]
        ));

        $this->belongsTo('table_1_id', 'Table1','table_1_id',[
            'foreignKey' => true
        ]);     
    }

}

/**
 * Table3
 **/
class Table2 extends \Phalcon\Mvc\Model {

    public $table_3_id;

    public $table_1_id;

    public $status;

    public function initialize()
    {
        $this->addBehavior(new \Phalcon\Mvc\Model\Behavior\SoftDelete(
            [
                'field' => 'status',
                'value' => "Deleted"
            ]
        ));

        $this->belongsTo('table_1_id', 'Table1','table_1_id',[
            'foreignKey' => true
        ]);     
    }

}

I thought about the idea of creating the hasMany references in Table1 to be foreign keys but decided against that because of the documentation:

https://docs.phalcon.io/en/latest/reference/models.html#virtual-foreign-keys

If you alter a belongsTo() relationship to act as foreign key, it will validate that the values inserted/updated on those fields have a valid value on the referenced model. Similarly, if a hasMany()/hasOne() is altered it will validate that the records cannot be deleted if that record is used on a referenced model.

Thank you for your suggestions. I will verify this, but my impression is that afterDelete() is never executed for SoftDelete. At least this is how I understand the code of SoftDelete.zep



5.7k
edited Jul '15

When using SoftDelete, afterDelete() is still executed. This is how I remove items from my ElasticSearch indexes when they're deleted in the application.

Steven, thank you! I will try to use afterDelete() to soft delete related models. However, I think it would be great if SoftDelete would work this way by default (as a simple Delete does).

Well, my experience is that afterDelete() is not executed with SoftDelete. Also, see this forum topic where the same problem is being discussed.



5.7k

Well, my experience is that afterDelete() is not executed with SoftDelete. Also, see this forum topic where the same problem is being discussed.

That was my bad. I believe you are correct. It turns out I actually use an afterUpdate to check on the status of the item to remove it from the search index. My bad!

Any tips regarding to how cascading soft delete can be implemented are welcome, I'm a bit stuck with this.

edited Aug '15

You need to extend SoftDelete class or re-implement if necessary. Here is something close to what am saying, but for sure its not tested nor accurate:

class SoftDelete extends \Phalcon\Mvc\Model\Behavior\SoftDelete {

    public function notify($type, \Phalcon\Mvc\ModelInterface $model) {
        if ($type == 'beforeDelete') {
            parent::notify($type, $model);
            $hasMany = Di::getDefault()->getModelsManager()->getHasMany($model);
//            $hasOne = Di::getDefault()->getModelsManager()->getHasOne($model);
//            $hasManyToMany = Di::getDefault()->getModelsManager()->getHasManyToMany($model);

            foreach ($hasMany as $manyRelation) {
                $class = $manyRelation->getReferencedModel();
                $referencedField = $manyRelation->getReferencedFields();
                $modelField = $manyRelation->getFields();
                $this->removeRef($class, $referencedField, $model->$modelField);
            }
        }
    }

    private function removeRef($class, $field, $value) {
        $records = $class::query()
            ->where("{$field} = :field:", ['field' => $value])
            ->andWhere("isDeleted = :notDeleted:", ['notDeleted' => $class::NOT_DELETED])
            ->execute();
        foreach($records as $record) {
            $record->delete();
        }
    }
}