Auto Ignore Soft Delete Rows

I am wondering how you go about automatically making the ORM ignore "is_deleted" fields.

I have a trait in a base model beforeCreate and beforeUpdate, there is no beforeSelect or beforeFind.

So I have this behavior implemented in my model:

$this->addBehavior(new SoftDelete([
            'field' => 'is_deleted',
            'value' => 1
]));

This works fine, but is there a way to exclude all soft-deleted items from every query? Must I make a custom behavior? If so does anyone have any pointers on how I can integrate that custom behavior with the ORM?



81.3k

You might want to override find() to add that condition to all your queries:

class MyModel extends Model
{
    public static function find($parameters = null)
    {
        if (is_array($parameters)) {
            if (isset($parameters[0])) {
                $parameters[0] .= ' AND is_deleted = 1';
            } else {
                if (isset($parameters['conditions'])) {
                    $parameters['conditions'] .= ' AND is_deleted = 1';
                }
            }
        }
        return parent::find($parameters);
    }
}
edited Aug '14

That could work, however what about Model::FindByFieldName()?



81.3k

You may need to override __callStatic to do that



235

Jesse Boyer

I'm using this ModelBase class for auto-ignoring of deleted rows.

/**
 * Base model class
 *
 * @link http://docs.phalconphp.com/en/latest/api/Phalcon_Mvc_Model.html
 */
abstract class ModelBase extends Model
{
    /**
     * @inheritdoc
     *
     * @access public
     * @static
     * @param array|string $parameters Query parameters
     * @return Phalcon\Mvc\Model\ResultsetInterface
     */
    public static function find($parameters = null)
    {
        $parameters = self::softDeleteFetch($parameters);

        return parent::find($parameters);
    }

    /**
     * @inheritdoc
     *
     * @access public
     * @static
     * @param array|string $parameters Query parameters
     * @return Phalcon\Mvc\Model
     */
    public static function findFirst($parameters = null)
    {
        $parameters = self::softDeleteFetch($parameters);

        return parent::findFirst($parameters);
    }

    /**
     * @inheritdoc
     *
     * @access public
     * @static
     * @param array|string $parameters Query parameters
     * @return mixed
     */
    public static function count($parameters = null)
    {
        $parameters = self::softDeleteFetch($parameters);

        return parent::count($parameters);
    }

    /**
     * @access protected
     * @static
     * @param array|string $parameters Query parameters
     * @return mixed
     */
    public static function softDeleteFetch($parameters = null)
    {
        if (method_exists(get_called_class(), 'getDeleted') === false) {
            return $parameters;
        }

        $deletedField = call_user_func([get_called_class(), 'getDeleted']);

        if ($parameters === null) {
            $parameters = $deletedField . ' = 0';
        } elseif (
            is_array($parameters) === false &&
            strpos($parameters, $deletedField) === false
        ) {
            $parameters .= ' AND ' . $deletedField . ' = 0';
        } elseif (is_array($parameters) === true) {
            if (
                isset($parameters[0]) === true &&
                strpos($parameters[0], $deletedField) === false
            ) {
                $parameters[0] .= ' AND ' . $deletedField . ' = 0';
            } elseif (
                isset($parameters['conditions']) === true &&
                strpos($parameters['conditions'], $deletedField) === false
            ) {
                $parameters['conditions'] .= ' AND ' . $deletedField . ' = 0';
            }
        }

        return $parameters;
    }
}

If you'll use it, your model must extend ModelBase class, e.g.

class Robot extends ModelBase
{
    /* your code here */
}

and also you need to define getDeleted() method, e.g.

class Robot extends ModelBase
{
    /* your code here */
    public function getDeleted()
    {
        return 'robot_deleted';
    }
    /* your code here */
}

This base class give you ability to use soft-delete not on all models, e.g. I don't use it in some parts of website, so i just don't define getDeleted() method. And don't forgot to use SoftDelete behavior from Phalcon, because this base class doesn't override delete() method.

P.S. You don't need to override __callStatic() method because it only calls findFirst(), find() and count() methods.



235

Oops, old topic, sorry. :)



2.6k

Seems, like findFirst is breaking If I pass the "Id" alone . Model::findFirst(300) . I guess this is the

existing behaviour . Can you suggest me where do I add a patch ?

Raja K



116

Old topic I know but we're using Andrey's suggested abstract class at my work right now.

As Raja has noted, using Phalcon's 'default' functionality to be able to send a single ID to the find or findFirst methods doesn't work when using this class. This is because the softDeleteFetch above method will receive a single integer, and output something like this...

'1 AND deleted_date IS NULL'

...which is incorrect as it will return everything (ignoring the primary key).

My patch assums that when a single integer is passed to softDeleteFetch, it was destined to be used as a constraint on the table's primary key - in our case all primary keys are consistently called 'id'. See the second else if statement.

protected static function softDeleteFetch($parameters = null)
{
    if (method_exists(get_called_class(), 'getDeleted') === false) {
        return $parameters;
    }

    $deletedField = call_user_func([get_called_class(), 'getDeleted']);

    if ($parameters === null) {
        $parameters = $deletedField . ' IS NULL';
    } else if (is_int($parameters)) {
        $parameters = 'id = ' . $parameters . ' AND ' . $deletedField . ' IS NULL';
    } else if (is_array($parameters) === false && strpos($parameters, $deletedField) === false) {
        $parameters .= ' AND ' . $deletedField . ' IS NULL';
    } else if (is_array($parameters) === true) {
        if (isset($parameters[0]) === true && strpos($parameters[0], $deletedField) === false) {
            $parameters[0] .= ' AND ' . $deletedField . ' IS NULL';
        } else if (isset($parameters['conditions']) === true && strpos($parameters['conditions'], $deletedField) === false) {
            $parameters['conditions'] .= ' AND ' . $deletedField . ' IS NULL';
        }
    }

    return $parameters;
}

Hope someone finds this useful!

If anyone needs uptime monitoring, at my work (RapidSpike) we're in our Beta phase until the end of this summer so get signed up for some free monitoring!



8.8k

I know this is kind of an old-ish topic, but will this be addressed in a future release of Phalcon?

It seems to me that the default behavior, when using the soft delete trait, should be to ignore the deleted rows. I think there should be a specific method to get the deleted rows... Something like:

    ModelName::findWithDeleted();

if you want to retrieve the deleted rows. It seems that most of the time you would not want to retrieve the deleted rows.



116

I know this is kind of an old-ish topic, but will this be addressed in a future release of Phalcon?

It seems to me that the default behavior, when using the soft delete trait, should be to ignore the deleted rows. I think there should be a specific method to get the deleted rows... Something like:

```php ModelName::findWithDeleted(); ```

if you want to retrieve the deleted rows. It seems that most of the time you would not want to retrieve the deleted rows.

I can't find where now, but I'm sure I've read somewhere that this will be addressed in a future release of Phalcon.