Search code examples
phpsymfonydoctrinephp-5.6

Symfony Gedmo Blameable not working


Did any one ever find an solution for this issue? I'm having the same issue.

My config.yml:

# Doctrine Configuration
doctrine:
    dbal:
        driver:   "%database_server%"
        host:     "%database_host%"
        port:     "%database_port%"
        dbname:   "%database_name%"
        user:     "%database_user%"
        password: "%database_password%"
        charset:  UTF8
        # if using pdo_sqlite as your database driver:
        #   1. add the path in parameters.yml
        #     e.g. database_path: "%kernel.root_dir%/data/data.db3"
        #   2. Uncomment database_path in parameters.yml.dist
        #   3. Uncomment next line:
        #     path:     "%database_path%"
    orm:
        auto_generate_proxy_classes: "%kernel.debug%"
        naming_strategy: doctrine.orm.naming_strategy.underscore
        auto_mapping: true
        #Gedmo Package extension for Symfony and Doctrine
        mappings:
            gedmo_tree:
                type: annotation
                prefix: Gedmo\Tree\Entity
                dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Entity"
                alias: GedmoTree
                is_bundle: false
            gedmo_sortable:
                type: annotation
                prefix: Gedmo\Sortable\Entity
                dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Sortable/Entity"
                alias: GedmoTree
                is_bundle: false
[...]
stof_doctrine_extensions:
    default_locale: "%locale%"
    translation_fallback: true
    orm:
        default:
            timestampable: true
            blameable: true

My doctrine_extension.yml is included in the config file:

services:

  extension.listener:
    class: Omega\HomeBundle\Library\Listener\DoctrineExtensionListener
    calls:
      - [ setContainer, [@service_container]]
    tags:
      # loggable hooks user username if one is in security context
      - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
  # Doctrine Extension listeners to handle behaviors
  gedmo.listener.tree:
    class: Gedmo\Tree\TreeListener
    tags:
      - { name: doctrine.event_subscriber, connection: default }
    calls:
      - [ setAnnotationReader, [ @annotation_reader ] ]
  gedmo.listener.sortable:
    class: Gedmo\Sortable\SortableListener
    tags:
      - { name: doctrine.event_subscriber, connection: default }
    calls:
      - [ setAnnotationReader, [ @annotation_reader ] ]
  gedmo.listener.timestampable:
    class: Gedmo\Timestampable\TimestampableListener
    tags:
      - { name: doctrine.event_subscriber, connection: default }
    calls:
      - [ setAnnotationReader, [ @annotation_reader ] ]
  gedmo.listener.loggable:
    class: Gedmo\Loggable\LoggableListener
    tags:
      - { name: doctrine.event_subscriber, connection: default }
    calls:
      - [ setAnnotationReader, [ @annotation_reader ] ]
  gedmo.listener.blameable:
    class: Gedmo\Blameable\BlameableListener
    tags:
      - { name: doctrine.event_subscriber, connection: default }
    calls:
      - [ setAnnotationReader, [ @annotation_reader ] ]
      - [ setUserValue, [ @security.token_storage ] ]

I created myself an trait to handle the created, updated, updated_by and createdby fields:

        namespace HomeBundle\Traits;
        use Doctrine\ORM\Mapping as ORM;
        use Omega\UserBundle\Entity\Users;
        use Gedmo\Mapping\Annotation as Gedmo;

        trait LogableTrait
        {

            /**
             * @var Users
             * @Gedmo\Blameable(on="create")
             * @ORM\ManyToOne(targetEntity="UserBundle\Entity\Users")
             * @ORM\JoinColumn(name="log_created_by", referencedColumnName="id")
             */
            protected $CreatedBy;

            /**
             * @var Users
             * @Gedmo\Blameable(on="update")
             * @ORM\ManyToOne(targetEntity="UserBundle\Entity\Users")
             * @ORM\JoinColumn(name="log_updated_by", referencedColumnName="id")
             */
            protected $UpdatedBy;
            /**
             * @Gedmo\Timestampable(on="create")
             * @ORM\Column(name="created", type="datetime")
             * @var \DateTime
             */
            protected $created;

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

            /**
             * @return Users
             */
            public function getCreatedBy ()
            {
                return $this->CreatedBy;
            }

            /**
             * @param Users $CreatedBy
             *
             * @return $this
             */
            public function setCreatedBy (Users $CreatedBy )
            {
                $this->CreatedBy = $CreatedBy;
                return $this;
            }

            /**
             * @return Users
             */
            public function getUpdatedBy ()
            {
                return $this->UpdatedBy;
            }

            /**
             * @param Users $UpdatedBy
             *
             * @return $this
             */
            public function setUpdatedBy (Users $UpdatedBy )
            {
                $this->UpdatedBy = $UpdatedBy;
                return $this;
            }


        }

But everytime that I use this Bundle I get:

The class 'Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage' was not found in the chain configured namespaces Gedmo\Tree\Entity, Gedmo\Sortable\Entity, JMS\JobQueueBundle\Entity, AccountingBundle\Entity, DocumentsBundle\Entity, EavBundle\Entity, HomeBundle\Entity, UserBundle\Entity, CustomerBundle\Entity, Jns\Bundle\XhprofBundle\Entity 

I hope some body can help me.


Solution

  • for any one that Is having th same issue like me that the blamable feature is not working:

    My solution was to implement the BlamableListener with an different approach:

      namespace HomeBundle\Library;
    
      use Doctrine\Common\NotifyPropertyChanged;
      use Gedmo\Exception\InvalidArgumentException;
      use Gedmo\Timestampable\TimestampableListener;
      use Gedmo\Blameable\Mapping\Event\BlameableAdapter;
      use Gedmo\Blameable\Mapping\Driver\Annotation;
    
      /**
       * The Blameable listener handles the update of
       * dates on creation and update.
       *
       * @author Gediminas Morkevicius <[email protected]>
       * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
       */
      class BlameableListener extends TimestampableListener
      {
          protected $user;
    
          /**
           * Get the user value to set on a blameable field
           *
           * @param object $meta
           * @param string $field
           *
           * @return mixed
           */
          public function getUserValue($meta, $field)
          {
              if ($meta->hasAssociation($field)) {
                  if (null !== $this->user && ! is_object($this->user)) {
                      throw new InvalidArgumentException("Blame is reference, user must be an object");
                  }
                  $user = $this->user->getToken()->getUser();
                  if(!is_object($user))
                  {
                      return null;
                  }
                  return $user;
              }
    
              // ok so its not an association, then it is a string
              if (is_object($this->user)) {
                  if (method_exists($this->user, 'getUsername')) {
                      return (string) $this->user->getUsername();
                  }
                  if (method_exists($this->user, '__toString')) {
                      return $this->user->__toString();
                  }
                  throw new InvalidArgumentException("Field expects string, user must be a string, or object should have method getUsername or __toString");
              }
    
              return $this->user;
          }
    
          /**
           * Set a user value to return
           *
           * @param mixed $user
           */
          public function setUserValue($user)
          {
              $this->user = $user;
          }
    
          /**
           * {@inheritDoc}
           */
          protected function getNamespace()
          {
              return __NAMESPACE__;
          }
    
          /**
           * Updates a field
           *
           * @param object           $object
           * @param BlameableAdapter $ea
           * @param $meta
           * @param $field
           */
          protected function updateField($object, $ea, $meta, $field)
          {
              $property = $meta->getReflectionProperty($field);
              $oldValue = $property->getValue($object);
              $newValue = $this->getUserValue($meta, $field);
    
              //if blame is reference, persist object
              if ($meta->hasAssociation($field) && $newValue) {
                  $ea->getObjectManager()->persist($newValue);
              }
              $property->setValue($object, $newValue);
              if ($object instanceof NotifyPropertyChanged) {
                  $uow = $ea->getObjectManager()->getUnitOfWork();
                  $uow->propertyChanged($object, $field, $oldValue, $newValue);
              }
          }
      }
    

    adjust the service for the blamable:

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

    you need to copy the mapping library to the same location as the listener itself. Adjust the namespaces and it works. It seems that some structures changed in symfony 2.7 so the plugin no longer works out of the box.