I made an improved SoftDelete

The idea is that it allows the models to have protected event methods while minimizing disruptive changes. If you do not want to have update events trigger then call $this->isSoftDeleting() within the event to return early. The SoftDelete checks before calling a public beforeSoftDelete and afterSoftDelete to trigger these events. The base model class implements these methods which then call the standard ones.

Additionally this performs a optional (disabled by default) cascading delete on hasManyRelations. In the future I'll add other types of relations as I explore that this for other relations.

I'm interested to know if there are any ways to make the publically accessible beforeSoftDelete and afterSoftDelete methods protected but still callable from the SoftDelete behavior.

<?php
namespace Project\Models;

use Project\Mvc\Model;

class UmbrellaGazebos extends Model
{
    public $id;
    public $active;
    public $name;

    protected function afterUpdate()
    {
        if ($this->isSoftDeleting()) {
            return true;
        }

        // SKIP this code if doing a soft delete

        return true;
    }


    protected function initialize()
    {
        $this->addSoftDeleteBehavior('active');
    }
}
<?php
namespace Project\Mvc;

use Phalcon\Mvc\Model as PhModel,
    Project\Mvc\Model\Behavior\SoftDelete;

class Model extends PhModel
{
    const ACTIVE = 'A';
    const DELETED = 'D';

    private $_isSoftDeleting = false;

    protected function isSoftDeleting()
    {
        return $this->_isSoftDeleting;
    }



    protected function addSoftDeleteBehavior($field)
    {
        $this->addBehavior(new SoftDelete([
            'field'         => $field,
            'value'         => self::DELETED,
            'cascadeDelete' => true
        ]));
    }


    public function beforeSoftDelete()
    {
        $this->_isSoftDeleting = true;

        if (method_exists($this, 'beforeDelete')) {
            $this->beforeDelete();
        }
    }


    public function afterSoftDelete()
    {
        // Force the event to fire since the delete operation is being skipped over
        if (method_exists($this, 'afterDelete')) {
            $this->afterDelete();
        }

        $this->_isSoftDeleting = false;
    }

}
<?php
namespace Project\Mvc\Model\Behavior;

use Phalcon\Mvc\Model\Behavior,
    Phalcon\Mvc\Model\BehaviorInterface,
    Phalcon\Mvc\ModelInterface,
    Phalcon\Mvc\Model\Message;

/**
 * Project\Mvc\Model\Behavior\SoftDelete
 */
class SoftDelete extends Behavior implements BehaviorInterface
{

    /**
     * Class constructor.
     *
     * @param array $options
     */
    public function __construct($options = [])
    {
        if (!array_key_exists('cascadeDelete', $options)) {
            $options['cascadeDelete'] = false;
        }

        parent::__construct($options);
    }

    /**
     *  [email protected]}
     *
     * @param string                      $eventType
     * @param \Phalcon\Mvc\ModelInterface $model
     */
    public function notify($eventType, $model)
    {
        if ($eventType == 'beforeDelete') {
            $options = $this->getOptions();

            $field = $options['field'];
            $value = $options['value'];

            $model->skipOperation(true);

            if ($model->readAttribute($field) === $value) {
                $model->appendMessage(new Message('Model was already deleted'));
                return false;
            }

            // Force the event to fire since the delete operation is being skipped over
            if (method_exists($model, 'beforeSoftDelete') {
                $model->beforeSoftDelete();
            }

            $updateModel = clone $model;
            $updateModel->writeAttribute($field, $value);

            if (!$updateModel->update()) {
                foreach ($updateModel->getMessages() as $message) {
                    $model->appendMessage($message);
                }
                return false;
            }

            $model->writeAttribute($field, $value);

            // FIXME: Check that cascade delete is enabled on the relationship itself.
            if ($options['cascadeDelete']) {
                $modelsManager = $model->getModelsManager();
                $hasManyRelations = $modelsManager->getHasMany($model);
                foreach ($hasManyRelations as $relation) {
                    $alias = $relation->getOptions()['alias'];
                    $relatedModels = $model->{"get{$alias}"}();
                    foreach ($relatedModels as $relModel) {
                        $relModel->delete();
                    }
                }
            }

            // Force the event to fire since the delete operation is being skipped over
            if (method_exists($model, 'afterSoftDelete')) {
                $model->afterSoftDelete();
            }
        }
    }
}

[edit] I changed iscallable to methodexists.



42.6k

Again all of the value added stuff in the base model is optional. I'd like to get more of it into the SoftDelete behavior but I don't know if it is possible because of scope issues.



42.6k
edited Jan '15

I've never used traits and I think that this might be a good use it instead of the putting all of the extra support code [necessarily] in the base model class. I'd kind of like to see this worked into the Phalcon 2.0 tree since I ported it from there, but as a trait it wouldn't be accepted.