Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Doctrine blameable extension 'on change' doesn't work

I'm on symfony 2.6.3 with stof Doctrine extension.

TimeStampable and SoftDeletable work well.

Also Blameable "on create" and "on update" are working well too:

/**
 * @var User $createdBy
 *
 * @Gedmo\Blameable(on="create")
 * @ORM\ManyToOne(targetEntity="my\TestBundle\Entity\User")
 * @ORM\JoinColumn(name="createdBy", referencedColumnName="id")
 */
protected $createdBy;

/**
 * @var User $updatedBy
 *
 * @Gedmo\Blameable(on="update")
 * @ORM\ManyToOne(targetEntity="my\TestBundle\Entity\User")
 * @ORM\JoinColumn(name="updatedBy", referencedColumnName="id")
 */
protected $updatedBy;

But "on change" seems not to be working.

/**
 * @var User $deletedBy
 *
 * @Gedmo\Blameable(on="change", field="deletedAt")
 * @ORM\ManyToOne(targetEntity="my\UserBundle\Entity\User")
 * @ORM\JoinColumn(name="deletedBy", referencedColumnName="id")
 */
protected $deletedBy;

I've got SoftDeletable configured on "deletedAt" field. SoftDeletable works fine, but deletedBy is never filled.

How can I manage to make it work? I just want to set user id who deleted the entity.

like image 767
Chuck Norris Avatar asked Feb 02 '15 10:02

Chuck Norris


Video Answer


3 Answers

Here my solution :

mybundle.soft_delete:
 class: Listener\SoftDeleteListener
 arguments:
    - @security.token_storage
 tags:
    - { name: doctrine_mongodb.odm.event_listener, event: preSoftDelete }
class SoftDeleteListener
{
/**
 * @var TokenStorageInterface
 */
private $tokenStorage;

public function __construct(TokenStorageInterface $tokenStorage)
{
    $this->tokenStorage = $tokenStorage;
}

/**
 * Method called before "soft delete" system happened.
 *
 * @param LifecycleEventArgs $lifeCycleEvent Event details.
 */
public function preSoftDelete(LifecycleEventArgs $lifeCycleEvent)
{
    $document = $lifeCycleEvent->getDocument();
    if ($document instanceof SoftDeletedByInterface) {
        $token = $this->tokenStorage->getToken();

        if (is_object($token)) {
            $oldValue = $document->getDeletedBy();
            $user = $token->getUser();

            $document->setDeletedBy($user);
            $uow = $lifeCycleEvent->getObjectManager()->getUnitOfWork();
            $uow->propertyChanged($document, 'deletedBy', $oldValue, $user);
            $uow->scheduleExtraUpdate($document, array('deletedBy' => array($oldValue, $user)));
        }
    }
}

}

like image 114
blutch27 Avatar answered Oct 19 '22 13:10

blutch27


The problem is you want to update entity (set user) when you call remove method on it.

Currently there may not be a perfect solution for registering user who soft-deleted an object using Softdeleteable + Blameable extensions.

Some idea might be to overwrite SoftDeleteableListener (https://github.com/Atlantic18/DoctrineExtensions/blob/master/lib/Gedmo/SoftDeleteable/SoftDeleteableListener.php) but I had a problem doing it.

My current working solution is to use Entity Listener Resolver.

MyEntity.php

/**
* @ORM\EntityListeners({„Acme\MyBundle\Entity\Listener\MyEntityListener" })
*/

class MyEntity {

/**
* @ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\User")
* @ORM\JoinColumn(name="deleted_by", referencedColumnName="id")
*/
private $deletedBy;

public function getDeletedBy()
{
    return $this->deletedBy;
}

public function setDeletedBy($deletedBy)
{
    $this->deletedBy = $deletedBy;
}

MyEntityListener.php

use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Acme\MyBundle\Entity\MyEntity;

class MyEntityListener
{
/**
 * @var TokenStorageInterface
 */
private $token_storage;

public function __construct(TokenStorageInterface $token_storage)
{
    $this->token_storage = $token_storage;
}

public function preRemove(MyEntity $myentity, LifecycleEventArgs $event)
{
    $token = $this->token_storage->getToken();
    if (null !== $token) {
        $entityManager = $event->getObjectManager();
        $myentity->setDeletedBy($token->getUser());
        $entityManager->persist($myentity);
        $entityManager->flush();
    }
}

}

An imperfection here is calling flush method.

Register service:

services:
    myentity.listener.resolver:
        class: Acme\MyBundle\Entity\Listener\MyEntityListener
        arguments:
            - @security.token_storage
        tags:
            - { name: doctrine.orm.entity_listener, event: preRemove }

Update doctrine/doctrine-bundle in composer.json:

"doctrine/doctrine-bundle": "1.3.x-dev"

If you have any other solutions, especially if it is about SoftDeleteableListener, please post it here.

like image 34
nataliastanko Avatar answered Oct 19 '22 14:10

nataliastanko


This is my solution, I use preSoftDelete event:

app.event.entity_delete:
    class: AppBundle\EventListener\EntityDeleteListener
    arguments:
        - @security.token_storage
    tags:
        - { name: doctrine.event_listener, event: preSoftDelete, connection: default }

and service:

<?php

namespace AppBundle\EventListener;

use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

class EntityDeleteListener
{
    /**
     * @var TokenStorageInterface
     */
    private $tokenStorage;

    public function __construct(TokenStorageInterface $tokenStorage)
    {
        $this->tokenStorage = $tokenStorage;
    }

    public function preSoftDelete(LifecycleEventArgs $args)
    {
        $token  = $this->tokenStorage->getToken();
        $object = $args->getEntity();
        $om     = $args->getEntityManager();
        $uow    = $om->getUnitOfWork();

        if (!method_exists($object, 'setDeletedBy')) {
            return;
        }

        if (null == $token) {
            throw new AccessDeniedException('Only authorized users can delete entities');
        }

        $meta = $om->getClassMetadata(get_class($object));
        $reflProp = $meta->getReflectionProperty('deletedBy');
        $oldValue = $reflProp->getValue($object);
        $reflProp->setValue($object, $token->getUser()->getUsername());

        $om->persist($object);
        $uow->propertyChanged($object, 'deletedBy', $oldValue, $token->getUser()->getUsername());
        $uow->scheduleExtraUpdate($object, array(
            'deletedBy' => array($oldValue, $token->getUser()->getUsername()),
        ));
    }
}

It's not consistence because I check setDeletedBy method exists and set deletedBy property, but it work for me, and you can upgrade this code for your needs

like image 1
Serhii Polishchuk Avatar answered Oct 19 '22 14:10

Serhii Polishchuk