Search code examples
zend-frameworkmodels

Run a function when using cascade deletion with zend models


Good afternooon everyone, i'll go straight to the point. I have this code at my model:

protected $_referenceMap = Array (
    "Imagens" => Array(
        "columns"       => Array (
            'paiId'
        ),
        "refTableClass" => "ModeloImagem",
        "refColumns"    => Array(
            "id"
        ),
        "onDelete" => self::CASCADE
    )
);

I must delete the images on the table that's going to be called on cascade, so i wanted to know if is there something like:

protected $_referenceMap = Array (
    "Imagens" => Array(
        "columns"       => Array (
            'paiId'
        ),
        "refTableClass" => "ModeloImagem",
        "refColumns"    => Array(
            "id"
        ),
        "onDelete" => function() {
            foreach($image as $i) {
                unlink($i);
            }
            return self::CASCADE
        }
    )
);

Solution

  • There is no such thing. What you can do is extend Zend_Db_Table's method _cascadeDelete($parentTableClassname, array $primaryKey) to execute the function.

    Here's one way to do it, although it's a terrible amount of code duplication; you might have to specify your own data that you want to be passed to the callback, or modify behavior depending on whether your callback or query fails:

    class My_Db_Table extends Zend_Db_Table
    {
        const ON_DELETE_CALLBACK = 'onDeleteCallback';
    
        /**
         * Called by parent table's class during delete() method.
         *
         * @param  string $parentTableClassname
         * @param  array  $primaryKey
         * @return int    Number of affected rows
         */
        public function _cascadeDelete($parentTableClassname, array $primaryKey)
        {
            $rowsAffected = 0;
            foreach ($this->_getReferenceMapNormalized() as $map) {
                if ($map[self::REF_TABLE_CLASS] == $parentTableClassname && isset($map[self::ON_DELETE])) {
                    switch ($map[self::ON_DELETE]) {
                        case self::CASCADE:
                            for ($i = 0; $i < count($map[self::COLUMNS]); ++$i) {
                                $col = $this->_db->foldCase($map[self::COLUMNS][$i]);
                                $refCol = $this->_db->foldCase($map[self::REF_COLUMNS][$i]);
                                $type = $this->_metadata[$col]['DATA_TYPE'];
                                $where[] = $this->_db->quoteInto(
                                    $this->_db->quoteIdentifier($col, true) . ' = ?',
                                    $primaryKey[$refCol], $type);
                            }
                            $this->_executeCallback($map, $where);
                            $rowsAffected += $this->delete($where);
                            break;
                        default:
                            // no action
                            break;
                    }
                }
            }
            return $rowsAffected;
        }
    
        private function _executeCallback($map, $where) {
            if (isset($map[self::ON_DELETE_CALLBACK]) && is_callable($map[self::ON_DELETE_CALLBACK])) {
                call_user_func($map[self::ON_DELETE_CALLBACK], $where);
            }
        }
    }
    

    You then use this to define the callback function. You can't define cascade in callback function though; else all your images get deleted every time Zend_Db_Table tries to figure out whether to delete entries from images table.

    protected $_referenceMap = Array (
        "Images" => Array(
            "columns"       => Array (
                'paiId'
            ),
            "refTableClass" => "ModeloImagem",
            "refColumns"    => Array(
                "id"
            ),
            "onDelete" => self::CASCADE,
            "onDeleteCallback" => function($where) {
                // Determine which image to unlink
                unlink($image);
            }
        )
    );