afterSave: A Great Callback Method for Soft Deleting Related Model Data( hasOne, hasMany, belongsTo)

Published on : March 1, 2012

Author:

Category: Our Blog


I never like to “Hard Delete” data. Some one will make mistake deleting something. Then they will tell you retrive it. So “Soft Delete” can save you time telling you clients/boss why the data can not be retrieved. BTW “Soft Delete” means you keep a field in table name is_delete/delete. Setting it’s value 1(or any value you may use but using boolean 1 seems logical) means the item is deleted. “Hard Delete” means you delete the row from the table. Poof gone. When i say delete on this post means “Soft Delete”.

Now lest discuss the power of afterSave. Suppose you Relational database for products. You have categories, sub-categories and sub-categories. Their relation looks like this

Category hasMany SubCategories
SubCategory hasMany SubSubCategories
SubSubCategory hasMany Products

Category -> SubCategories -> SubSubCategories -> Products

As CakePHP naming convention your Category, SubCategory, SubSubCategory Model should have these fields


CREATE TABLE `categories ` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(550) NOT NULL,
  `is_delete` tinyint(1) NOT NULL DEFAULT '0',
  `created` datetime DEFAULT NULL,
  `modified` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB ;

CREATE TABLE `sub_categories` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `category_id` int(11) NOT NULL,
  `name` varchar(550) NOT NULL,
  `is_delete` tinyint(1) NOT NULL DEFAULT '0',
  `created` datetime DEFAULT NULL,
  `modified` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

CREATE TABLE `sub_sub_categories` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `sub_category_id` int(11) NOT NULL,
  `name` varchar(550) NOT NULL,
  `is_delete` tinyint(1) NOT NULL DEFAULT '0',
  `created` datetime DEFAULT NULL,
  `modified` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

CREATE TABLE `products` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` int(11) NOT NULL,
  `sub_sub_category_id` int(11) NOT NULL,
  `is_delete` tinyint(1) NOT NULL DEFAULT '0',
  `created` datetime DEFAULT NULL,
  `modified` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

created, modified will be automatically updated by CakePHP. Its nice little feature. So on to our relation between tables. categories table is connected with sub_categories category_id. Its model will look like this


class Catagory extends AppModel {
    var $name = 'Catagory';
    var $displayField = 'name';
    //The Associations below have been created with all possible keys, those that are not needed can be removed
    var $hasMany = array(
        'SubCatagory' => array(
            'className' => 'SubCatagory',
            'foreignKey' => 'catagory_id',
            'dependent' => false,
        )
    );
}

Same with sub_categories, sub_sub_categories and products table.


class SubCatagory extends AppModel {
    var $name = 'SubCatagory';
    var $displayField = 'name';
    //The Associations below have been created with all possible keys, those that are not needed can be removed
    var $belongsTo = array(
        'Catagory' => array(
            'className' => 'Catagory',
            'foreignKey' => 'catagory_id',
        )
    );

    var $hasMany = array(
        'SubSubCatagory' => array(
            'className' => 'SubSubCatagory',
            'foreignKey' => 'sub_catagory_id',
            'dependent' => false,
        )
    );
}

class SubSubCatagory extends AppModel {
    var $name = 'SubSubCatagory';
    var $displayField = 'name';
    //The Associations below have been created with all possible keys, those that are not needed can be removed
    var $belongsTo = array(
        'SubCatagory' => array(
            'className' => 'SubCatagory',
            'foreignKey' => 'sub_catagory_id',
        )
    );

    var $hasMany = array(
        'Product' => array(
            'className' => 'Product',
            'foreignKey' => 'sub_sub_catagory_id',
            'dependent' => false,
        )
    );
}

class Product extends AppModel {
    var $name = 'Product';
    var $displayField = 'name';
    //The Associations below have been created with all possible keys, those that are not needed can be removed

    var $belongsTo = array(
        'SubSubCatagory' => array(
            'className' => 'SubSubCatagory',
            'foreignKey' => 'sub_sub_catagory_id',
            'conditions' => '',
            'fields' => '',
            'order' => ''
        )
    );
}

Now we will delete on category – so the row on the categories table will updated with 1. But all the rows on the sub_categories, sub_sub_categories, products for that category will not be deleted. Now comes CakePHP to rescue. It is actually quite beautiful. Lets add afterSave() function to Category model -category.php


    function afterSave()
    {
        if($this->data['Catagory']['is_delete']==1)
        {
            $this->SubCatagory->recursive=0;
            $sub_catagories = $this->SubCatagory->find('all', array(
                                'conditions' => array('SubCatagory.catagory_id'=>$this->data['Catagory']['id'])
                            ));
            foreach($sub_catagories as $sub_catagory):
                unset($this->data['SubCatagory']);
                $this->data=$sub_catagory;
                $this->data['SubCatagory']['is_delete']=1;
            endforeach;
        }
    }

We first check for delete. The we found out all the sub_catagories. Then we delete it. But what about the sub_sub_categories and products. Lets first delete the sub_sub_categories.


    function afterSave()
    {
        if($this->data['SubCatagory']['is_delete']==1 )
        {
            $this->SubSubCatagory->recursive=0;
            $sub_sub_catagories = $this->SubSubCatagory->find('all', array(
                                'conditions' => array('SubCatagory.catagory_id'=>$this->data['Catagory']['id'],'SubSubCatagory.sub_catagory_id'=>$this->data['SubCatagory']['id'])
                            ));
            foreach($sub_sub_catagories as $sub_sub_catagory):
                unset($this->data['SubSubCatagory']);
                $this->data=$sub_sub_catagory;
                $this->data['SubSubCatagory']['is_delete']=1;
                $this->SubSubCatagory->save($this->data);
            endforeach;
        }
    }

Wow all the sub_sub_catagories are deleted. Now delete all the products


    function afterSave($created)
    {
        if($this->data['SubSubCatagory']['is_delete']==1 or $this->data['SubSubCatagory']['is_hide']==1)
        {
            $this->Product->recursive=0;
            $products = $this->Product->find('all', array(
                                'conditions' => array('Product.catagory_id'=>$this->data['SubCatagory']['catagory_id'], 'Product.sub_catagory_id'=>$this->data['SubCatagory']['id'], 'Product.sub_sub_catagory_id'=>$this->data['SubSubCatagory']['id'])
                            ));
            foreach($products as $product):
                unset($this->data['Product']);
                $this->data=$product;
                $this->data['Product']['is_delete']=1;
                $this->Product->save($this->data);
            endforeach;
        }
    }

Now all the records have been deleted. Now you can ask why i did not add the codes on the Category model because then deleting any sub_categories entry will not delete sub_sub_categories and products.


Leave a Reply

Your email address will not be published. Required fields are marked *