symfonydoctrine-ormdoctrine-extensions

How to disable a doctrine filter in a param converter


I'm using the doctrine softdeleteable extension on a project and have my controller action set up as such.

/**
 * @Route("address/{id}/")
 * @Method("GET")
 * @ParamConverter("address", class="MyBundle:Address")
 * @Security("is_granted('view', address)")
 */
public function getAddressAction(Address $address)
{

This works great as it returns NotFound if the object is deleted, however I want to grant access to users with ROLE_ADMIN to be able to see soft deleted content.

Does there already exist a way to get the param converter to disable the filter or am I going to have to create my own custom param converter?


Solution

  • There are no existing ways to do it, but I've solved this problem by creating my own annotation, that disables softdeleteable filter before ParamConverter does its job.

    AcmeBundle/Annotation/IgnoreSoftDelete.php:

    namespace AcmeBundle\Annotation;
    
    use Doctrine\Common\Annotations\Annotation;
    
    /**
     * @Annotation
     * @Target({"CLASS", "METHOD"})
     */
    class IgnoreSoftDelete extends Annotation { }
    

    AcmeBundle/EventListener/AnnotationListener.php:

    namespace AcmeBundle\EventListener;
    
    use Doctrine\Common\Util\ClassUtils;
    use Doctrine\Common\Annotations\Reader;
    use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
    
    class AnnotationListener {
    
        protected $reader;
    
        public function __construct(Reader $reader) {
            $this->reader = $reader;
        }
    
        public function onKernelController(FilterControllerEvent $event) {
            if (!is_array($controller = $event->getController())) {
                return;
            }
    
            list($controller, $method, ) = $controller;
    
            $this->ignoreSoftDeleteAnnotation($controller, $method);
        }
    
        private function readAnnotation($controller, $method, $annotation) {
            $classReflection = new \ReflectionClass(ClassUtils::getClass($controller));
            $classAnnotation = $this->reader->getClassAnnotation($classReflection, $annotation);
    
            $objectReflection = new \ReflectionObject($controller);
            $methodReflection = $objectReflection->getMethod($method);
            $methodAnnotation = $this->reader->getMethodAnnotation($methodReflection, $annotation);
    
            if (!$classAnnotation && !$methodAnnotation) {
                return false;
            }
    
            return [$classAnnotation, $classReflection, $methodAnnotation, $methodReflection];
        }
    
        private function ignoreSoftDeleteAnnotation($controller, $method) {
            static $class = 'AcmeBundle\Annotation\IgnoreSoftDelete';
    
            if ($this->readAnnotation($controller, $method, $class)) {
                $em = $controller->get('doctrine.orm.entity_manager');
                $em->getFilters()->disable('softdeleteable');
            }
        }
    
    }
    

    AcmeBundle/Resources/config/services.yml:

    services:
        acme.annotation_listener:
            class: AcmeBundle\EventListener\AnnotationListener
            arguments: [@annotation_reader]
            tags:
                - { name: kernel.event_listener, event: kernel.controller }
    

    AcmeBundle/Controller/DefaultController.php:

    namespace AcmeBundle\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
    use AcmeBundle\Annotation\IgnoreSoftDelete;
    use AcmeBundle\Entity\User;
    
    class DefaultController extends Controller {
    
        /**
         * @Route("/{id}")
         * @IgnoreSoftDelete
         * @Template
         */
        public function indexAction(User $user) {
            return ['user' => $user];
        }
    
    }
    

    Annotation can be applied to individual action methods and to entire controller classes.