3

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?

Derick F
  • 2,749
  • 3
  • 20
  • 30

2 Answers2

4

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.

VisioN
  • 143,310
  • 32
  • 282
  • 281
  • FYI: in more recent symfony versions the controller's `get` method is protected and can't be called in the listener's `ignoreSoftDeleteAnnotation` method. You can pass the EntityManager in the constructor, just like the Reader and rely on dependency injection. Also FilterControllerEvent is deprecated since symfony 4.3 and one should use ControllerEvent instead. – larscm Nov 13 '21 at 12:58
0

You can use @Entity for this, customizing a repository method like this:

    use Symfony\Component\Routing\Annotation\Route;
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;

    /**
     * @Route("/{id}")
     * @Entity("post", expr="repository.findDisableFilter(id)")
     */
     public function disable(Post $post): JsonResponse
    {
        ...
    }

and then in your repository class:

    public function findDisableFilter(mixed $id): mixed
    {
        $filterName = 'your-filter-name';
        $filters = $this->getEntityManager()->getFilters();
        if ($filters->has($filterName) && $filters->isEnabled($filterName)) {
            $filters->disable($filterName);
        }

        return $this->find($id);
    }


omrqs
  • 21
  • 3