Doctrine + ZF2 tips: Doctrine filters and soft delete

Doctrine 2 filters are a very powerful feature allowing you to easily modify every request sent to the database. This comes in very handy when you need to implement a soft delete filter: a filter which will load only records from database which are not marked as deleted.

With ZF2 dependency injection (DI) support, this is really easy to configure.

Soft delete is when a record is not deleted from the database, but rather just marked as deleted using a boolean field. Doctrine filters provide an easy and secure way to implement this.

As soft delete requires a column to mark records as deleted, you need the relevant entities to implement an interface called SoftDeleteInterface, which defines getters and setters for a field called ‘deleted’. This ensures your entity will be consistent with the database model, and also allows the filter to recognise records with soft delete functionality.

You then create a DeletedFilter, which checks entities to see if they have implemented that interface, and if so, filters out any which have been set to deleted (deleted = 1). You can then add this filter to the configuration, which guarantees that only records with deleted=0 can be loaded anywhere in the system.

Why should you do this? Well, the simple answer is that this can increase your website’s security: if you give the db user a limited set of permissions (select, insert, update), you can be sure that nobody can drop / delete / truncate content, even by accident.

To implement this, add the following to the module.config.php configuration for Doctrine:

'doctrine' => array(
    'configuration' => array(
        'orm_default' => array(
            'filters' => array(
                'soft_delete' => 'Admin\Filter\DeletedFilter'
            )
        )
    ),
    'driver' => array(........

Then define a filter, similar to one which you can find in the Doctrine documentation. This uses reflection to check if the entity implements the SoftDeleteInterface:

<?php
    namespace Admin\Filter;

    use Doctrine\ORM\Query\Filter\SQLFilter;
    use Doctrine\ORM\Mapping\ClassMetadata;
    use Admin\Entity\SoftDeleteInterface;


    class DeletedFilter extends SQLFilter
    {

        /**
        * Gets the SQL query part to add to a query.
        *
        * @return string The constraint SQL if there is available, empty string otherwise
        */
        public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
        {

            // Check if the entity implements the SoftDelete interface
            if (!in_array("Admin\Entity\SoftDeleteInterface", $targetEntity->reflClass->getInterfaceNames())) {
                return "";
            }

            return $targetTableAlias.'.deleted = '.SoftDeleteInterface::STATUS_NOT_DELETED;
        }
    }

Finally, the soft delete interface:

<?php
             
    namespace Admin\Entity;

    /**
    * Interface to support soft delete
    */
    Interface SoftDeleteInterface
    {
        const STATUS_NOT_DELETED = 0;
        const STATUS_DELETED = 1;

        /**
        * @param int $deleted
        */

        public function setDeleted($deleted);

        public function getDeleted();
    }

And last step – you need to enable filter. Add to Module.php:

public function onBootstrap(MvcEvent $e){
    $entityManager = $e->getApplication()->getServiceManager()->get('doctrine.entitymanager.orm_default');
    $filter = $entityManager->getFilters()->enable("soft_delete");    
}

Latest News & Insights

Say connected – get Loft updates straight to your inbox.