11

can anyone provide a complete example of the Blameable Gedmo extension, and especially the configuration of the Blameable Listener ?

I am using the code provided by the documentation:

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

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

BUT the createdBy and updatedBy database columns are always NULL.

The documentation provides example to configure the other listeners (e.g. timestampable which I got working) but I find no example or documentation for the blameable listener.

Thanks for any help!!

===============================================================

EDIT to answer Jean:

yes I added the use which is:

use Gedmo\Mapping\Annotation as Gedmo;

I also use the Timestampable with the provided trait:

use Gedmo\Timestampable\Traits\TimestampableEntity;

// doctrine comments removed
class Document
{
    use TimestampableEntity;
...
}

and the timestampable configuration is:

services:
    gedmo.listener.timestampable:
        class: Gedmo\Timestampable\TimestampableListener
        tags:
            - { name: doctrine.event_subscriber, connection: default }
        calls:
            - [ setAnnotationReader, [ @annotation_reader ] ]

Timespambable works just fine. I tried a similar configuration for the blameable listener since it has a setUserValue method:

gedmo.listener.blameable:
    class: Gedmo\Blameable\BlameableListener
    tags:
        - { name: doctrine.event_subscriber, connection: default }
    calls:
        - [ setAnnotationReader, [ @annotation_reader ] ]
        - [ setUserValue, [ @security.token_storage ] ]

but it doesn't work, I get this error (the 4 bundles are the ones used in my project):

The class 'Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage' was not found in the chain configured namespaces Cf\UserBundle\Entity, Cf\DocumentBundle\Entity, Cf\SouffleBundle\Entity, FOS\UserBundle\Model

I understand it is missing the user id or security token as an argument in one way or another but I just can't find an example anywhere. That's where I'm stuck. Any idea ?

JML
  • 111
  • 1
  • 5
  • Did you put the `use` on top of your class? Can you show that to us? – Alessandro Lai May 05 '15 at 15:29
  • Also, Blameable works only if the entity is created\updated with the security context on and containing a user token, that is taken into account and persisted in your field. – Alessandro Lai May 05 '15 at 15:30
  • Do you use [https://github.com/stof/StofDoctrineExtensionsBundle](https://github.com/stof/StofDoctrineExtensionsBundle)? I have it and blameable works. – kba May 06 '15 at 07:02
  • 1
    Did you ever get this working? I am having a similar issue? – MattW Jul 27 '15 at 18:25
  • Are you using Symfony >= 2.6 or Symfony <= 2.5? Do you add the DoctrineExtensionsListener as explained here https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/symfony2.md? (Note: this code works only on Symfony <= 2.5, for Symfony >= 2.6 you must change the onKernelRequest method) – Stefano P. Jul 07 '16 at 15:17

4 Answers4

16

I also found it hard to enable Blameable behavior with StofDoctrineExtensionsBundle (let's assume you are using it).

There is one piece of configuration that is not mentioned in that bundle:

# Add in your app/config/config.yml
stof_doctrine_extensions:
    orm:
        default:
            blameable: true

Apart from that, I created a BlameableEntity trait:

<?php

namespace AppBundle\Entity\Traits;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use AppBundle\Entity\User;

/**
 * Add Blameable behavior to an entity.
 */
trait BlameableEntity {

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

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

    /**
     * Set createdBy
     *
     * @param User $createdBy
     * @return Object
     */
    public function setCreatedBy(User $createdBy)
    {
        $this->createdBy = $createdBy;

        return $this;
    }

    /**
     * Get createdBy
     *
     * @return User
     */
    public function getCreatedBy()
    {
        return $this->createdBy;
    }

    /**
     * Set updatedBy
     *
     * @param User $updatedBy
     * @return Object
     */
    public function setUpdatedBy(User $updatedBy)
    {
        $this->updatedBy = $updatedBy;

        return $this;
    }

    /**
     * Get updatedBy
     *
     * @return User
     */
    public function getUpdatedBy()
    {
        return $this->updatedBy;
    }

}

And in your entity, just add a use statement like this:

<?php

namespace AppBundle\Entity;

use AppBundle\Entity\Traits\BlameableEntity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;

/**
 * Your precious Foo entity
 *
 * @ORM\Table(name="foo")
 * @ORM\Entity(repositoryClass="AppBundle\Entity\FooRepository")
 */
class Foo
{
    use BlameableEntity;

    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    // ...

I hope this helps!

ChMat
  • 348
  • 2
  • 9
  • I got it to work, thanks! I did had to change 1 thing tho: i had to make the $createdBy and $updatedBy fields optional to prevent the error: `Typed property App\Entity\Note::$createdBy must not be accessed before initialization`. The following code is my final solution: ```/** * @var ?User $updatedBy * * @Gedmo\Blameable(on="update") * @ORM\ManyToOne(targetEntity=User::class) * @ORM\JoinColumn(name="updated_by", referencedColumnName="id", nullable=true) */ private ?User $updatedBy;``` – Robin Bastiaan May 26 '21 at 07:56
3

the small additional contribution for people which turns on symfony 3.0 +:

In "doctrine_extensions.yml" add/modify :

gedmo.listener.blameable:
    class: Gedmo\Blameable\BlameableListener
    tags:
        - { name: doctrine.event_subscriber, connection: default }
    calls:
        - [ setAnnotationReader, [ "@annotation_reader" ] ]
Breith
  • 2,160
  • 1
  • 23
  • 32
1

If you want to update the updated_by field, you must specify the field so that when you update it, do so in updated_by. For example:

/**
 * @var \DateTime $updated
 *
 * @Gedmo\Timestampable(on="update")
 * @ORM\Column(type="datetime", nullable=true)
 */
protected $updated;

/**
 * @var string $updatedBy
 *
 * @Gedmo\Blameable(on="update", field="updated")
 * @ORM\Column(type="string", nullable=true)
 */
protected $updatedBy;

Pay attention to field="updated"

-1

If someone still has this problem, I can finally get Gedmo Blameable working following these steps:

  1. Follow this guide https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/symfony2.md
  2. Add this to doctrine_extensions.yml

    gedmo.listener.blameable:
      class: Gedmo\Blameable\BlameableListener
      tags:
        - { name: doctrine.event_subscriber, connection: default }
      calls:
        - [ setAnnotationReader, [ "@annotation_reader" ] ]
    
  3. Modify the onKernelRequest method as follow:

    public function onKernelRequest(GetResponseEvent $event) {
        if (Kernel::MAJOR_VERSION == 2 && Kernel::MINOR_VERSION < 6) {
            $securityContext = $this->container->get('security.context', ContainerInterface::NULL_ON_INVALID_REFERENCE);
            if (null !== $securityContext && null !== $securityContext->getToken() && $securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
                $loggable = $this->container->get('gedmo.listener.loggable');
                $loggable->setUsername($securityContext->getToken()->getUsername());
            }
        }
        else {
            $tokenStorage = $this->container->get('security.token_storage')->getToken();
            $authorizationChecker = $this->container->get('security.authorization_checker');
            if (null !== $tokenStorage && $authorizationChecker->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
                $loggable = $this->container->get('gedmo.listener.loggable');
                $loggable->setUsername($tokenStorage->getUser());
                $blameable = $this->container->get('gedmo.listener.blameable');
                $blameable->setUserValue($tokenStorage->getUser());
            }
        }
    }
    
Stefano P.
  • 179
  • 1
  • 9