xiidea / EasyAuditBundle

A Symfony Bundle To Log Selective Events
http://xiidea.github.io/EasyAuditBundle/
MIT License
89 stars 22 forks source link

Attempted to call an undefined method named getBestCandidatePropertyForIdentify of class Resolver\EntityEventResolver #33

Closed braianj closed 5 years ago

braianj commented 5 years ago

Hello, I just try to install this bundle again in a new project and got this error

Attempted to call an undefined method named "getBestCandidatePropertyForIdentify" of class "Resolver\EntityEventResolver".

So after debugging between and old project with the bundle working and this new one, inside the EntityEventResolver in vendor directory I got this diff file

--- /newProject/vendor/xiidea/easy-audit/Resolver/EntityEventResolver.php   Sat Aug  4 13:24:34 2018
+++ /oldProject/vendor/xiidea/easy-audit/Resolver/EntityEventResolver.php   Wed Jan 31 07:31:39 2018
@@ -14,6 +14,8 @@
 use Symfony\Component\DependencyInjection\ContainerAwareInterface;
 use Symfony\Component\DependencyInjection\ContainerAwareTrait;
 use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
+use Symfony\Component\PropertyAccess\PropertyAccess;
 use Xiidea\EasyAuditBundle\Events\DoctrineEntityEvent;
 use Xiidea\EasyAuditBundle\Events\DoctrineEvents;

@@ -22,6 +24,10 @@
 {
     use ContainerAwareTrait;

+    protected $candidateProperties = array('name', 'title');
+
+    protected $propertiesFound = array();
+
     protected $eventShortName;

     /** @var  $event DoctrineEntityEvent */
@@ -30,8 +36,6 @@
     protected $entity;

     protected $eventName;
-
-    protected $identity = ['', ''];

     /**
@@ -52,22 +56,12 @@
             return null;
         }

-        $reflectionClass = $this->getReflectionClassFromObject($this->entity);
+        $entityClass = $this->getReflectionClassFromObject($this->entity);

         return array(
-            'description' => $this->getDescription($reflectionClass->getShortName()),
-            'type'        => $this->getEventType($reflectionClass->getShortName())
+            'description' => $this->getDescription($entityClass),
+            'type' => $this->getEventType($entityClass->getShortName()),
         );
-
-    }
-
-    protected function getSingleIdentity()
-    {
-        foreach ($this->event->getIdentity() as $field => $value) {
-            return [$field, $value];
-        }
-
-        return ['', ''];

     }

@@ -78,20 +72,10 @@
     private function initialize(DoctrineEntityEvent $event, $eventName)
     {
         $this->eventShortName = null;
+        $this->propertiesFound = array();
         $this->eventName = $eventName;
         $this->event = $event;
         $this->entity = $event->getLifecycleEventArgs()->getEntity();
-        $this->identity = $this->getSingleIdentity();
-    }
-
-    private function getIdField()
-    {
-        return $this->identity[0];
-    }
-
-    private function getIdValue()
-    {
-        return $this->identity[1];
     }

     protected function getChangeSets($entity)
@@ -104,6 +88,21 @@
         return $this->getEventShortName() == 'updated';
     }

+    /**
+     * @param string $name
+     * @return string|mixed
+     */
+    protected function getProperty($name)
+    {
+        $propertyAccessor = PropertyAccess::createPropertyAccessor();
+
+        try {
+            return $propertyAccessor->getValue($this->entity, $this->propertiesFound[$name]);
+        } catch (NoSuchPropertyException $e) {
+            return '{INACCESSIBLE} property! ' . $e->getMessage();
+        }
+    }
+

     /**
      * @param string $typeName
@@ -115,17 +114,23 @@
     }

     /**
-     * @param string $shortName
-     * @return string
-     */
-    protected function getDescription($shortName)
-    {
+     * @param \ReflectionClass $reflectionClass
+     * @return string
+     */
+    protected function getDescription(\ReflectionClass $reflectionClass)
+    {
+        $property = $this->getBestCandidatePropertyForIdentify($reflectionClass);
+
+        $descriptionTemplate = '%s has been %s';
+
+        if (!empty($property)) {
+            $descriptionTemplate .= sprintf(' with %s = "%s"', $property, $this->getProperty($property));
+        }
+
         return sprintf(
-            '%s has been %s with %s = "%s"',
-            $shortName,
-            $this->getEventShortName(),
-            $this->getIdField(),
-            $this->getIdValue()
+            $descriptionTemplate,
+            $reflectionClass->getShortName(),
+            $this->getEventShortName()
         );
     }

@@ -142,6 +147,38 @@
     }

     /**
+     * @param \ReflectionClass $reflectionClass
+     * @return null|string
+     */
+    protected function getBestCandidatePropertyForIdentify(\ReflectionClass $reflectionClass)
+    {
+        $foundPropertyName = $this->getPropertyNameInCandidateList($reflectionClass);
+
+        if ("" !== $foundPropertyName) {
+            return $foundPropertyName;
+        }
+
+        return $this->getNameOrIdPropertyFromPropertyList($reflectionClass,
+            strtolower($reflectionClass->getShortName()) . "id"
+        );
+    }
+
+    /**
+     * @param \ReflectionClass $reflectionClass
+     * @return string
+     */
+    protected function getPropertyNameInCandidateList(\ReflectionClass $reflectionClass)
+    {
+        foreach ($this->candidateProperties as $property) {
+            if($reflectionClass->hasProperty($property)) {
+                return $this->propertiesFound[$property] = $property;
+            }
+        }
+
+        return "";
+    }
+
+    /**
      * @return string
      */
     protected function getName()
@@ -165,4 +202,20 @@
     {
         return $this->container->get('doctrine')->getManager()->getUnitOfWork();
     }
+
+    /**
+     * @param \ReflectionClass $reflectionClass
+     * @param $entityIdStr
+     * @return null|string
+     */
+    private function getNameOrIdPropertyFromPropertyList(\ReflectionClass $reflectionClass, $entityIdStr)
+    {
+        foreach (array('id', $entityIdStr) as $field) {
+            if($reflectionClass->hasProperty($field)) {
+                return $this->propertiesFound['id'] = $field;
+            }
+        }
+
+        return "";
+    }
 }

I found that in the new project the version of the bundle is 1.4.9

# php composer.phar show
....
xiidea/easy-audit                    1.4.9    A Symfony Bundle To Log Selective Events. It is easy to configure and easy to customize for your need

and in the old project the version of the bundle is 1.4.7

# php composer.phar show
....
xiidea/easy-audit                    1.4.7    A Symfony Bundle To Log Selective Events. It is easy to configure and easy to customize for your...

My solution at the moment is to downgrade to 1.4.7 but I want to know why the changes and how to fix so I can work with the new version.

Thanks

ronisaha commented 5 years ago

The changes made to simplify the entity event resolver. You can still copy the code from the older version and create your own EntityEventResolver by implementing Xiidea\EasyAuditBundle\Resolver\EventResolverInterface.

xiidea_easy_audit:
     ...       
    entity_event_resolver : App\Resolver\EntityEventResolver     

services:
    App\Resolver\EntityEventResolver: ~

The bundled EntityEventResolver is just an implementation example to support general cases. As not every body wants to use the name property to describe the description.

Have a nice day!

braianj commented 5 years ago

Thanks for your quick response. Yes I got my own implementation

<?php
namespace LoginBundle\Resolver;
use Symfony\Component\EventDispatcher\Event;
use Xiidea\EasyAuditBundle\Events\DoctrineEntityEvent;
use Xiidea\EasyAuditBundle\Resolver\EntityEventResolver as BaseResolver;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
use Psr\Log\LoggerInterface;

class EntityEventResolver extends BaseResolver
{

    private $logger;
    private $serializer;

    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    /**
     * @param Event|DoctrineEntityEvent $event
     * @param $eventName
     *
     * @return array
     */
    public function getEventLogInfo(Event $event, $eventName)
    {
        if (!$event instanceof DoctrineEntityEvent) {
            return null;
        }
        $this->initialize($event, $eventName);
        $changesMetaData = $this->getChangeSets($this->entity);

        if ($this->isUpdateEvent() && $changesMetaData == NULL) {
            return NULL;
        }
        $reflectionClass = $this->getReflectionClassFromObject($this->entity);
        $typeName = $reflectionClass->getShortName();
        //$eventType = $this->getEventType($typeName);
        $eventDescription = $this->getDescriptionString($reflectionClass, $typeName);

        $datos = [];
        $ignorar = array('tu', 'ts', 'updated', 'created', 'transitions', '__initializer__', '__cloner__', '__isInitialized__', 'timezone', 'salt', 'password', 'nuevoPassword');

        if (empty($changesMetaData)) {
            $encoder = new JsonEncoder();
            $normalizer = new ObjectNormalizer();

            $this->serializer = new Serializer(array($normalizer), array($encoder));

            $reflClass = new \ReflectionClass($this->entity);

            $arrayDatos = [];
            foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflMethod) {
                if (
                    0 !== $reflMethod->getNumberOfRequiredParameters() ||
                    $reflMethod->isStatic() ||
                    $reflMethod->isConstructor() ||
                    $reflMethod->isDestructor()
                ) {
                    continue;
                }

                $name = $reflMethod->name;
                $attributeName = null;

                if (0 === strpos($name, 'get') || 0 === strpos($name, 'has')) {
                    // getters and hassers
                    $attributeName = substr($name, 3);

                    if (!$reflClass->hasProperty($attributeName)) {
                        $attributeName = lcfirst($attributeName);
                    }
                } elseif (0 === strpos($name, 'is')) {
                    // issers
                    $attributeName = substr($name, 2);

                    if (!$reflClass->hasProperty($attributeName)) {
                        $attributeName = lcfirst($attributeName);
                    }
                }

                if (!is_null($attributeName) && !in_array($attributeName, $ignorar))
                {
                    $val = $this->entity->$name();

                    $es_colecion = (is_array($val) || ($val instanceof \ArrayAccess && $val instanceof \Traversable));

                    if(!$es_colecion) {
                        $arrayDatos[$attributeName] = $this->extraerValor($val);
                    } else {
                        $arrayDatos[$attributeName] = [];
                        foreach ($val as $item) {
                            $arrayDatos[$attributeName][] = $this->extraerValor($item);
                        }
                    }
                }
            }

            $datos = $this->serializer->serialize($arrayDatos,'json');

        } elseif (is_string($changesMetaData)) {
            // porque esta esto??????
            $datos = $changesMetaData;
        } else {
            foreach ($changesMetaData as $key => $value) {
                if($value[0]!=$value[1] && !in_array($key, $ignorar)) {
                    if(gettype($value[0])== 'object') {

                        $entity = null;
                        foreach (explode('\\',get_class($value[0])) as $v) {
                            $entity = $v;
                        }

                        if((method_exists($value[0], 'getId'))) {

                            $value1 = $value[0]->getId();
                            $value2 = is_null($value[1]) ? null : $value[1]->getId();
                            $datos[$key] = array('entity' => $entity, 'id' => array($value1, $value2));

                        } else {
                            $value1 = $this->isDateOrNot($value[0]);
                            $value2 = $this->isDateOrNot($value[1]);
                            $datos[$key] = array($value1, $value2);
                        }
                    } else {
                        $datos[$key] = $value;
                    }
                }
            }
            if (!empty($datos)) {
                $datos = json_encode($datos);
            }
        }

        $infoFinal = array(
            'table'         => $typeName,
            'type'          => $this->traducirType(),
            'description'   => $eventDescription,
        );

        if ($typeName!='Agentes') {
            $infoFinal['tableId'] = $this->getProperty('id');
        }

        if (!empty($datos)) {
            $infoFinal['data'] = $datos;
        }
        return $infoFinal;
    }

    protected function extraerValor ($value) {
        $es_entidad = method_exists($value, 'getId');

        if($es_entidad) {
            return $value->getId();
        } elseif ($value instanceof \Date) {
            return $value->format("Y-m-d");
        } elseif ($value instanceof \DateTime) {
            return $value->format("Y-m-d H:i:s");
        } elseif (gettype($value)== 'object') {
            $value = json_encode($value, JSON_PRETTY_PRINT|JSON_ERROR_UNSUPPORTED_TYPE|JSON_PARTIAL_OUTPUT_ON_ERROR);
        }
        return $value;
    }

    /**
     * @param DoctrineEntityEvent $event
     * @param string $eventName
     */
    private function initialize(DoctrineEntityEvent $event, $eventName)
    {
        $this->eventShortName = null;
        $this->propertiesFound = array();
        $this->eventName = $eventName;
        $this->event = $event;
        $this->entity = $event->getLifecycleEventArgs()->getEntity();
    }

    protected function isDateOrNot($var) {
        if(is_a($var, 'DateTime')){
            return print_r(($var->format('H:i:s')!='00:00:00') ? $var->format('c') : $var->format('Y-m-d'), true);
        } else {
            return print_r($var, true);
        }
    }

    protected function getDescriptionString(\ReflectionClass $reflectionClass, $typeName)
    {
        $property = $this->getBestCandidatePropertyForIdentify($reflectionClass);
        $descriptionTemplate = '%s %s';//'%s ha sido %s ';
        if ($property) {
            $descriptionTemplate .= sprintf('.%s = %s', $property, $this->getProperty($property));
        }

        return sprintf($descriptionTemplate,
            $this->traducirType(),
            $typeName);
    }
    /**
     * @param $changesMetaData
     *
     * @return null|string
     */
    protected function getAsSerializedString($changesMetaData)
    {
        if (empty($changesMetaData)) {
            return NULL;
        } elseif (is_string($changesMetaData)) {
            return $changesMetaData;
        }
        return serialize($changesMetaData);
    }

    protected function traducirType() {
        $accion = null;
        switch ($this->getEventShortName()) {
            case 'created':
                $accion = 'Crear';
                break;

            case 'updated':
                $accion = 'Actualizar';
                break;

            case 'deleted':
                $accion = 'Eliminar';
                break;

            default:
                $accion = 'Acción';
                break;
        }
        return $accion;
    }
}

But I have conflight If I update the bundle or install in a new project, like today I spend 2 hours trying to fnd the error, so that's why I ask you to know if I need to change my entity resolver

ronisaha commented 5 years ago

Sorry for the inconvenience. I'll Try to add a change log file next time. It was my ignorance about sample implementation, that gave you such troubles. I've only kept the public API intact and forgot the possibility of user extending my Sample implementation :(

braianj commented 5 years ago

no problem! thanks again