Git Product home page Git Product logo

easyauditbundle's Introduction

Easy Audit

Build Status Coverage Status Scrutinizer Code Quality Latest Stable Version Latest Unstable Version Total Downloads License

A Symfony Bundle To Log Selective Events. It is easy to configure and easy to customize for your need.

Versions

Symfony PHP EasyAuditBundle Support
6.x >=8.0.2 3.x.x New Features / Bugs
5.x >=7.2.5 2.x.x Bugs
2.7-4.4 ^5.6,^7.0 1.4.x -
<=2.8 ^5.4-7.3 1.3.x -
<=2.4 ^5.4 1.2.x -

Install

  1. Add EasyAuditBundle in your composer.json
  2. Enable the Bundle
  3. Create audit_log entity class
  4. Configure config.yml
  5. Update Database Schema

1. Add EasyAuditBundle in your composer.json

Add EasyAuditBundle in your composer.json:

{
    "require": {
        "xiidea/easy-audit": "^3.0"
    }
}

Now tell composer to download the bundle by running the command:

$ php composer.phar update xiidea/easy-audit

Composer will install the bundle to your project's vendor/xiidea directory.

2. Enable the Bundle

<?php
// app/AppKernel.php

public function registerBundles()
{
    $bundles = array(
        // ...
        new Xiidea\EasyAuditBundle\XiideaEasyAuditBundle(),
    );
}

3. Create audit_log entity class

The XiideaEasyAuditBundle supports Doctrine ORM/MongoDB by default. However, you must provide a concrete AuditLog class. Follow the instructions to set up the class:

4. Configure config.yml

You can find sample config data in Resources/config/config-sample.yml file

# app/config/config.yml
xiidea_easy_audit:
    #resolver: xiidea.easy_audit.default_event_resolver                           #Optional
    #audit_log_class : MyProject\Bundle\MyBundle\Entity\AuditLog                  #Required
    #doctrine_event_resolver : xiidea.easy_audit.default_doctrine_event_resolver  #Optional
    #default_logger : true                                                        #Optional
    
    #user property to use as actor of an event
    #valid value will be any valid property of your user class
    user_property : ~ # or username                            #Optional

    #List of doctrine entity:event you wish to track or set to false to disable logs for doctrine events
    # valid events are = [created, updated, deleted]
    #doctrine_objects :                                              #Optional
    #     MyProject\Bundle\MyBundle\Entity\MyEntity : [created, updated, deleted]
    #     MyProject\Bundle\MyBundle\Entity\MyEntity2 : []

    #List all events you want to track  (Optional from v1.2.1 you can now use subscriber to define it)
    events :                                                   #Optional
        - security.interactive_login

    #List all custom resolver for event
    #custom_resolvers :
    #       security.interactive_login : user.event_resolver

    #logger_channel:
    #    xiidea.easy_audit.logger.service: ["info", "debug"]
    #    file.logger: ["!info", "!debug"]

    #Custom Event Resolver Service
services:
    #user.event_resolver:
    #     class: Xiidea\EasyAuditBundle\Resolver\UserEventResolver
    #     calls:
    #        - [ setContainer,[ @service_container ] ]

5. Update Database Schema

As all setup done, now you need to update your database schema. To do so,run the following command from your project directory

$ php app/console doctrine:schema:update --force

Core Concepts

Logger

Logger is the core service which are responsible for persist the event info. You can define as many logger as you like. EasyAudit Bundled with a logger service xiidea.easy_audit.logger.service which is the default logger service. You can easily disable the service by setting default_logger: false in configuration.

Resolver

Resolver is like translator for an event. It used to translate an event to AuditLog entity. EasyAudit bundled with two(2) resolver services xiidea.easy_audit.default_event_resolver, xiidea.easy_audit.default_doctrine_event_resolver. And a custom EventResolver class UserEventResolver to illustrate how the transformation works. You can define as many resolver service as you want and use them to handle different event. Here is the place you can set the severity level for a event. Default level is Psr\Log\LogLevel::INFO. Custom severity levels are not available. EasyAudit supports the logging levels described by PSR-3. These values are present for basic filtering purposes. You can use this value as channel to register different logger to handle different event. If you add any other field to your AuditLog object, this is the place to add those extra information (tags, metadata, etc..)

Channel

It is now possible to register logger for specific channel. channel is refers to log level. you can configure EasyAudit logger services to handle only specific level of event.

Warning - BC Breaking Changes

  • Since v1.2.2 pre_persist_listener option has been removed. You can use this cookbook to achieve the same functionality

  • Since v1.2.2 EventResolverInterface been split into EmbeddedEventResolverInterface and EventResolverInterface

  • Since v1.3.x The new Event object has been adapted. And the signature of EmbeddedEventResolverInterface and EventResolverInterface also changed. Now it expects extra $eventName parameter

  • Since v1.4.7 EntityEventResolver been refactored to a simplified version, if your code directly depends on older version of the implementation you are advised to copy the content of old implementation from here

  • Since v2.0 The FosUserBundle Events are removed from UserEventResolver and Event class using Symfony\Contracts\* namespace

  • Since v3.0 As Symfony\Component\Security\Core\Event\AuthenticationEvent not exists anymore, security.authentication.failure resolver also removed.

Cookbook

Look the cookbook for another interesting things.

easyauditbundle's People

Contributors

afurculita avatar ajaxray avatar damienharper avatar jayesbe avatar jeroennoten avatar johonunu avatar mbennett-talentnet avatar ronisaha avatar saifulferoz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

easyauditbundle's Issues

Possible incompatibility with symfony/contracts

Hi everybody,

I'm having some troubles using this bundle to log JWT authentication events (using lexik/jwt-authentication-bundle).

Following the cookbook, I declared my custom resolvers

    events:
        - security.interactive_login
        - security.authentication.failure
        - security.authentication.success
        - lexik_jwt_authentication.on_authentication_success
        - lexik_jwt_authentication.on_authentication_failure
        - lexik_jwt_authentication.on_jwt_created
        - lexik_jwt_authentication.on_jwt_encoded
        - lexik_jwt_authentication.on_jwt_decoded
        - lexik_jwt_authentication.on_jwt_authenticated
        - lexik_jwt_authentication.on_jwt_invalid
        - lexik_jwt_authentication.on_jwt_not_found
        - lexik_jwt_authentication.on_jwt_expired
    custom_resolvers:
        lexik_jwt_authentication.on_authentication_success: app.jwt_event_resolver
        lexik_jwt_authentication.on_authentication_failure: app.jwt_event_resolver
        lexik_jwt_authentication.on_jwt_created: app.jwt_event_resolver
        lexik_jwt_authentication.on_jwt_encoded: app.jwt_event_resolver
        lexik_jwt_authentication.on_jwt_decoded: app.jwt_event_resolver
        lexik_jwt_authentication.on_jwt_authenticated: app.jwt_event_resolver
        lexik_jwt_authentication.on_jwt_invalid: app.jwt_event_resolver
        lexik_jwt_authentication.on_jwt_not_found: app.jwt_event_resolver
        lexik_jwt_authentication.on_jwt_expired: app.jwt_event_resolver

registered my service

    app.jwt_event_resolver:
        class: App\Resolver\JWTEventResolver

and implemented my class (still WIP, so I know that the description and infos are not really helpful right now)

<?php

namespace App\Resolver;

use Xiidea\EasyAuditBundle\Resolver\EventResolverInterface;
use Symfony\Component\EventDispatcher\Event;

class JWTEventResolver implements EventResolverInterface
{

    public function getEventLogInfo(Event $event, $eventName)
    {
        return array(
            'description'=>'JWT description',
            'type'=>$eventName
        );
    }

}

I'm getting the following errors

TypeError: Argument 1 passed to Xiidea\EasyAuditBundle\Listener\LogEventsListener::resolveEventHandler() must be an instance of Symfony\Component\EventDispatcher\Event, instance of Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationFailureEvent given, called in /var/www/html/vendor/symfony/event-dispatcher/Debug/WrappedListener.php on line 126

TypeError: Argument 1 passed to Xiidea\EasyAuditBundle\Listener\LogEventsListener::resolveEventHandler() must be an instance of Symfony\Component\EventDispatcher\Event, instance of Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent given, called in /var/www/html/vendor/symfony/event-dispatcher/Debug/WrappedListener.php on line 126

Doing some reverse engeneering brught me to the fact that those event classes are all extensions of this base class of JWT namespace

<?php

namespace Lexik\Bundle\JWTAuthenticationBundle\Event;

use Symfony\Component\EventDispatcher\Event as BaseEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Contracts\EventDispatcher\Event as ContractsBaseEvent;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

if (is_subclass_of(EventDispatcher::class, EventDispatcherInterface::class)) {
    class Event extends ContractsBaseEvent
    {
    }
} else {
    class Event extends BaseEvent
    {
    }
}

In fact, if I "patch" this declaration forcing it to be extended from BaseEvent, it starts involving my custom dispatchers correctly

Is there a way to avoid this behaviour? I know it sounds like a Lexik problem, but I think it could involve any custom event using contracts and could be useful to provide an integration.

Allow PHP 8 in composer.json version constraints.

Hi,
with PHP 8 being productive now, many people (like me) prepare a version upgrade of both symfony and components. I've not found major issues with using PHP8 on this bundle and would ask to add PHP 8.0 to version constraints.

Cheers

flush($entity) is deprecated, and duplicates updates and logs

According to doctrine/orm#6915
$em->flush($entity) shouldn't be used anymore since it will perform a full flush.

This currently causes duplicate updates, and entries in the audit log if you update multiple entities in the same flush.
The first entity is only updated once and one audit log. The second entity twice. 3rd 3 times and so on.

Further more $em->flush($entity) will be removed with doctrine v3.0.

I'm currently running on Doctrine v2.9 with EasuAuditBundle v1.4.9

Upgraded to Symfony 5

Hello,

I upgraded to Symfony 5 and also the EasyAuditBundle to v2.0 but I still have this error:

Argument 1 passed to Xiidea\EasyAuditBundle\Logger\Logger::__construct() must be an instance of Doctrine\Common\Persistence\ManagerRegistry, instance of Doctrine\Bundle\DoctrineBundle\Registry given

Does anyone have a fix for this ?

Error when trying to get property that not exist

Hello, I always save the id inside the table. Sometimes it save null because do not have an id. But In a particular table I have a combine primaty key, and the try catch is not working

captura de pantalla de 2017-12-26 14-52-14

captura de pantalla de 2017-12-26 14-52-39

What can I do to catch the error and so return null or another value?

Documentation needs to be corrected

In order to track all events for an entity, documentation states to setup configuration as below:

xiidea_easy_audit:
    doctrine_entities :
         MyProject\Bundle\MyBundle\Entity\MyEntity: ~

This does not work, no event is tracked for MyEntity.

But, setting up configuration as below does work:

xiidea_easy_audit:
    doctrine_entities :
         MyProject\Bundle\MyBundle\Entity\MyEntity: []

Documentation should be corrected or code adjusted to follow the doc.

Support ODM

To support ODM, we have to do

  • Replace all reference to \Doctrine\ORM\* With equivalent \Doctrine\Common\*
  • Change Configuration Option ref-34
  • Rename Entity* Term to more generic Object*
  • Rename ORMSubscribedEvents to SubscribeDoctrineEvents
  • Pass all existing test
  • Add new test for ODM Support
  • Add Documentation
  • Add CouchDB Support

Viewer Interface

Need to create a view interface to view logged events... at least last N events

Event log type

Need event log type to be added like: Debug, Info, Error ... etc

Need to find wich field changed or data inserted

hello
i want to find when i updated entity,which fields changed with changed value , like this
Table1 and Field1 This Value V1 Changed to V2
(parameter: Table1,Field1,V1,V2)

and when i inserted a data in table this log:
Table1 and Field1 This Value V1 Inserted,Field2 This Value V2 Insertet
(parameter: Table1,Field1,Field2,V1,V2)

for each field in table all of them in one string stored in a row in audit_log table

thank you

Doctrine Entity Not Logged

Doctrine entity configured through configuration not working

 doctrine_entities :                                                          
         AppBundle\Entity\Project : [created, updated, deleted]

A small typo in documentation

In the readme file and other documentation pages the "Channel" is written as "Chanel".
You may fix it or I can submit a pull request.

Thanks

Anis

Symfony 4 incompatibility

When using your bundle in a Symfony 3.4.x project, I get the following deprecated warning:

User Deprecated: The "annotation_reader" service is private, getting it from the container is deprecated since Symfony 3.2 and will fail in 4.0. You should either make the service public, or stop using the container directly and use dependency injection instead.

return $this->container->get('annotation_reader');

Problem with DoctrineSubscriber delete event when using proxy classes

Hello,

In you DoctrineSubscriber in the method "isConfiguredToTrack" you extract the class name with get_class.

If we delete from doctrine with a proxy class as in
$em->remove($em->getReference("AppBundle:EntityName",$entityId));
The get_class will return the proxy class name for this Entity causing the "isConfigured" call to fail since the proxy class isn't configured.

I suggest to replace the "get_class" call on line 79 of DoctrineSubscriber with Doctrines class resolver
\Doctrine\Common\Util\ClassUtils::getClass($entity);
to get the correct class.

How to log the many to many changes

Hello, for example the changes into rol of a user something like

/**
  * @var \Doctrine\Common\Collections\Collection
  *
  * @ORM\ManyToMany(targetEntity="LoginBundle\Entity\Roles", inversedBy="usuarios")
  * @ORM\JoinTable(name="user_roles",
  *   joinColumns={
  *     @ORM\JoinColumn(name="id_user", referencedColumnName="id")
  *   },
  *   inverseJoinColumns={
  *     @ORM\JoinColumn(name="id_rol", referencedColumnName="id")
  *   }
  * )
  */
 private $roles;

When I make changes on a rol of a user, there is no print of that instance

Error an exception occurred while executing insert into

I got this error when it try to insert into the audit_lo table

An exception occurred while executing 'INSERT INTO audit_log (id, type_id, type, description, event_time, user, impersonatingUser, ip) VALUES (?, ?, ?, ?, ?, ?, ?, ?)' with params [5, "security.interactive_login", "security.interactive_login", "security.interactive_login", "2017-11-22 12:56:48", "bmellor", null, "::1"]:

and this log

`Uncaught PHP Exception Doctrine\DBAL\Exception\SyntaxErrorException: "An exception occurred while executing 'INSERT INTO audit_log (id, type_id, type, description, event_time, user, impersonatingUser, ip) VALUES (?, ?, ?, ?, ?, ?, ?, ?)' with params [5, "security.interactive_login", "security.interactive_login", "security.interactive_login", "2017-11-22 12:56:48", "bmellor", null, "::1"]: SQLSTATE[42601]: Syntax error: 7 ERROR: syntax error in or close to ยซuserยป LINE 1: ..._log (id, type_id, type, description, event_time, user, impe... ^" at /project/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractPostgreSQLDriver.php line 70`

How to save the circular reference

I'm trying to save the user / roles but whe I do that, it save every for and then every user of those roles, and the every role and so on.
If I insert a user inside another entity, it save all the object of user, and then roles, and then user and so on.

This is my event resolver

<?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');


        if (empty($changesMetaData)) {
            $encoder = new JsonEncoder();
            $normalizer = new ObjectNormalizer();
            $normalizer->setIgnoredAttributes($ignorar);
            $normalizer->setCircularReferenceLimit(0);
            $normalizer->setCircularReferenceHandler(function ($object) {
                return $object->getId();
            });

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

            $datos = $this->serializer->serialize($this->entity,'json');
        } elseif (is_string($changesMetaData)) {
            $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 = $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;
    }

    /**
     * @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;
    }
}

Posible duplicate with #24

BaseAuditLog must be interface

BaseAuditLog must be interface and be as less strict and verbose as possible. Only minimum set of methods for work support of common code should be defined,

Doctrine ODM namespace error

Hi!

I found an issue in latest beta version. I create AuditLog document extends from Model, but didn't work. This is the error message:

The class 'App\\Document\\AuditLog' was not found in the chain configured namespaces App\\Entity, Xiidea\\EasyAuditBundle\\Entity

Thx

Saving collection of entity form types problem: PreUpdateEventArgs::__construct() must be of the type array, null given

Hi,

  • I have a form type based on a entity that has a collection type of forms and each one has also entities related with other form types.
  • In my config.yml I am tracking all the events (created, updated, deleted) to the affected entities and also their correspondent proxy classes (just for guaranteeing not having any problem: #23 )
  • When I do not make use of logging capabilities (comment those entities in config.yml to not track changes on those entities,from EasyAuditBundle, the form is saved without any problem! -> This is very important for the case!
  • When I make use of entity logging capabilities of this bundle (uncomenting those entities from config.yml) and I try to save that form, it gives me the following error:

Uncaught PHP Exception Symfony\Component\Debug\Exception\FatalThrowableError: "Type error: Argument 3 passed to Doctrine\ORM\Event\PreUpdateEventArgs::__construct() must be of the type array, null given, called in /var/www/app/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php on line 1060" at /var/www/app/vendor/doctrine/orm/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php line 46 {"exception":"[object] (Symfony\Component\Debug\Exception\FatalThrowableError(code: 0): Type error: Argument 3 passed to Doctrine\ORM\Event\PreUpdateEventArgs::__construct() must be of the type array, null given, called in /var/www/app/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php on line 1060 at /var/wwwapp/vendor/doctrine/orm/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php:46)"}

  • Is the problem related with the internal flush of your bundle?
    in vendor/xiidea/easy-audit/Logger/Logger.php (line 38) if(empty($event)) { return; } $this->getEntityManager()->persist($event); $this->getEntityManager()->flush($event); } /** * @return EntityManager */
  • Can we have flush() inside flush()? How the transaction flush created by my instruction in the controller and the flush that is executed in the beetweens of the transaction I think, to save the record in the audit_log table are handled?
  • The exception passes here:

public function postUpdate(LifecycleEventArgs $args) { $this->handleEvent(DoctrineEvents::ENTITY_UPDATED, $args); }

  • Can be any chance of conflicts with my PreUpdate /PrePersist events?

  • Other notes 1: I have a CustomEntityEventResolver which was built based in your example, and that has minor changes, just to tracking changeSets that are serialized to a audit_log new field ($this->getChangeSets($this->entity);) and getEventLogInfo(Event $event, $eventName) returns a AuditLog entity

  • Other notes 2: So, I have a custom AuditLog entity with that new field for tracking changes (the deltas)

<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Xiidea\EasyAuditBundle\Entity\BaseAuditLog;
/**
 * @ORM\Entity)
 * @ORM\Table(name="audit_log")
 */
class AuditLog extends BaseAuditLog
{ 
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;
/*...*/ 

    /**
     * @var string
     * @ORM\Column(name="change_sets", type="text", nullable=true, unique=false, nullable=true)
     */
    protected $changeSets;

}
  • I'm posting here the simplest code and clear as possible to you get a quick understand:
<?php 
// MyController.php Class:
class MyController extends Controller
{
	//....
	
	public function editMawbFlightsFactModalAction(Mawb $mawb, Request $request) {
		
		$entityManager = $this->getDoctrine()->getManager();

		$form = $this->createForm(MawbFlightsFactType::class, $mawb);

		if ($request->isMethod('POST')) {

			$form->submit($request->request->get($form->getName()), false);

			// - Handle submitted data
			if ($form->isSubmitted() && $form->isValid()) {

				// @var Mawb $mawb
				$mawb = $form->getData();

				$entityManager = $this->getDoctrine()->getManager();
				$entityManager->persist($mawb);
				$entityManager->flush();

				return new JsonResponse(['msg' => 'ok']);
			}
		}

		return $this->render('app/editMawbFlightsFact.html.twig',
			array(
				'form' => $form->createView(),
				'mawb' => $mawb
			)
		);
	}
}

//-----------------------------------------------------------------------------------------
//Mawb.php Entity Class:
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;

/**
 * Mawb
 *
 * @ORM\Table(name="mawb")
 * @ORM\Entity
 */
class Mawb
{
	/**
     * @var integer
     *
     * @ORM\Column(name="id_mawb", type="integer", nullable=false, unique=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $idMawb;
	
	
	//... other entity properties......
	
	/**
     * @var \Doctrine\Common\Collections\Collection
     *
     * @ORM\OneToMany(targetEntity="FlightsFactMawb", mappedBy="mawb", orphanRemoval=true, cascade={"persist"})
     */
    private $flightsFactMawbCollection;
	
    public function __construct() {
        $this->flightsFactMawbCollection = new ArrayCollection();
    }
	
	//...getters and setters..
}

//-----------------------------------------------------------------------------------------
//MawbFlightsFactType.php form Type
class MawbFlightsFactType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder
            ->add('flightsFactMawbCollection', CollectionType::class, array(
                'entry_type' => FlightFactMawbType::class,
                'by_reference' => false,
                'prototype' => true
            ));
    }

    public function configureOptions(OptionsResolver $resolver) {
        $resolver->setDefaults(['data_class' => Mawb::class]);
    }
}

//-----------------------------------------------------------------------------------------
//FlightFactMawbType.php form Type
class FlightFactMawbType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder
            ->add('flightFact', FlightFactType::class,['data_class' => FlightsFact::class])
            ->add('assigned');   
    }

    public function configureOptions(OptionsResolver $resolver) {
        $resolver->setDefaults(['data_class' => FlightsFactMawb::class]);
    }
}

//-----------------------------------------------------------------------------------------
//FlightsFactMawb.php Entity Class:
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\Traits\CreatedUpdatedAtTrait;

/**
 * FlightsFactMawb
 *
 * @ORM\Table(name="flights_fact_mawb")
 * @ORM\Entity
 * @ORM\HasLifecycleCallbacks
 */
class FlightsFactMawb
{
    use CreatedUpdatedAtTrait;
	
	/**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;
	
	/**
     * @var boolean
     *
     * @ORM\Column(name="assigned", type="boolean", nullable=false, unique=false, options={"default" : "1"})
     */
    private $assigned;
	
	// .... other entity properties.....

    /**
     * @var FlightsFact
     *
     * @ORM\ManyToOne(targetEntity="FlightsFact", cascade={"persist"})
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="id_flights_fact", referencedColumnName="id_flights_fact", nullable=false, onDelete="CASCADE")
     * })
     */
    private $flightFact;
	
	//...getters and setters..
}

//-----------------------------------------------------------------------------------------
//FlightsFact.php Entity Class:
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\Traits\CreatedUpdatedAtTrait;

/**
 * @ORM\Table(name="flights_fact")
 * @ORM\Entity
 * @ORM\HasLifecycleCallbacks
 */
class FlightsFact
{
    use CreatedUpdatedAtTrait;

    /**
     * @var integer
     *
     * @ORM\Column(name="id_flights_fact", type="integer", nullable=false, unique=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $idFlightFact;

    /**
     * @var string
     *
     * @ORM\Column(name="number", type="string", length=5, nullable=true, unique=false)
     */
    private $number;
	
	//...

    /**
     * @ORM\PreUpdate
     */
    public function preUpdateScheduledEpochs() {
        if(!empty($this->getNumber()) {
            $this->setNumber($this->getNumber().'*');
        }
    }
}

//-----------------------------------------------------------------------------------------
//FlightFactMawbType.php form Type
class FlightFactType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options) {
		
        $builder
            ->add('number', null, ['required' => true]);
    }
	
    public function configureOptions(OptionsResolver $resolver) {
        $resolver->setDefaults(array('data_class' => FlightsFact::class));
    }
}

//------------------------------------------------------------------------------------------
// CreatedUpdatedAtTrait.php
namespace AppBundle\Entity\Traits;

use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Mapping as ORM;

trait CreatedUpdatedAtTrait
{
    /**
     * @var \DateTime
     * @ORM\Column(name="created_at", type="datetime", nullable=false)
     */
    protected $createdAt;

    /**
     * @var \DateTime
     * @ORM\Column(name="updated_at", type="datetime", nullable=false)
     */
    protected $updatedAt;


    /**
     * @ORM\PrePersist
     */
    public function onPrePersist() {
        $this->updatedAt = new \DateTime('now');
        $this->createdAt = $this->createdAt == null ? $this->updatedAt : $this->createdAt;
    }

    /**
     * @ORM\PreUpdate
     */
    public function onPreUpdate() {
        $this->updatedAt = new \DateTime('now');
        if (!$this->createdAt) {
            $this->createdAt = new \DateTime('now');
        }
    }

    /**
     * @return \DateTime
     */
    public function getCreatedAt() {
        return $this->createdAt;
    }

    /**
     * @param \DateTime $createdAt
     * @return $this
     */
    public function setCreatedAt($createdAt) {
        $this->createdAt = $createdAt;
        return $this;
    }

    /**
     * @return \DateTime
     */
    public function getUpdatedAt() {
        return $this->updatedAt;
    }

    /**
     * @param \DateTime $updatedAt
     * @return $this
     */
    public function setUpdatedAt($updatedAt) {
        $this->updatedAt = $updatedAt;
        return $this;
    }
}
  • Other note: Audit Log works nice if I just save a single entity
  • Also, I've tested with master branch and also with the latest tag, giving the same problem.

Can you give a hand on this? Can you reproduce the error? Thanks.

Symfony 5 Support

Todo

  • Remove Deprecated Functions
  • Get ImpersonatingUser From SwitchUserToken
  • Remove FOSUserbundle Dependency
  • Remove BC Layer
  • Update Documentation
  • Use event from Symfony\Contracts namespace
  • Update PHPUnit

Get previous value and changed value.

How can I access this information ? If it dump the event it's there I think but no way to access it. All I'm getting is
array:2 [
"description" => "Listing has been updated with id = "1085""
"type" => "Listing updated"

which is not very useful

Bug/Feature? Implement Proxy Objects in DoctrineSubscriber

Hi,

As I use the Change Tracking Policy: Deferred Explicit I need to persist all of my objects if I want them to be saved.
But sometimes these objects are Proxy objects like Proxies_CG_\App\DemoAcmeBundle\Entity\User instead of the normal App\DemoAcmeBundle\Entity\User entity that is registered in the config under doctrine_entities. (A workaround is to add the proxy objects there for people finding this post, like this: Proxies\__CG__\App\DemoAcmeBundle\Entity\User: [updated,deleted])

However, I would suggest to add a check for proxy objects and check against the real object in this method: \Xiidea\EasyAuditBundle\Subscriber\DoctrineSubscriber::handleEvent

So... I can work around it, but a fix would be nice :) If I had time I would fix it myself ^^ Hope to help someone who also has the same issue with doctrine proxy objects.
Here are some tags people can find this tread on: Easy Audit Bundle Doctrine Events not fired, data or entity saved but listener not triggered.

undefined index throwing notice

[04-Aug-2018 11:12:33 America/Toronto] PHP Notice: Undefined index: AppBundle\Entity\ProductTag in /var/www/rewardify/releases/04082018114835/vendor/xiidea/easy-audit/Subscriber/DoctrineSubscriber.php on line 90

in my case.. the entity ProductTag does not have the subscriber defined.. so im not sure why it's performing this.. but it looks like the code should check to see if the index exist ?

Custom logger Error

Trying to use custom logger as default logger, leads to error :Unrecognized option "loggers" under "xiidea_easy_audit"

Different Logger For different event

Some time it may be useful to have different logger to handle different types of event. for instance i like to log all error events to file when other logs are storing to database. And also for server error I like to get notified through email.

Log should not be registered when action fails

Hi

I'm having incoherences in AudiLog, in cases when an action Update/Create/Delete to an entity fails for any reason: log is still registered while action actual failed.

Imagine you have 2 Entities: Product and Category . And assuming a Product can't "live" without a Category.

Product.php:

namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Table(name="product")
* @ORM\Entity
*/
class Product {

/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;

/**
*  @ORM\Column(type="string")
*/
private $name;

/**
Or OneToOne
* @var Category
* @ORM\ManyToOne(targetEntity="Category") 
* @ORM\JoinColumns({
*     @ORM\JoinColumn(name="id_category", referencedColumnName="id")
* })
*/
private $category;
}

Castegory.php:

namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Table(name="category")
* @ORM\Entity
*/
class Category {

/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;

/**
*  @ORM\Column(type="string")
*/
private $name;
}

Records in each table:

Product:

| id | name | id_category |
| 1  | a    | 1           |

Category:

| id | name | 
| 1  | A    |

Example of deleting Category with id 1:

Because category has its PK present as FK in Product entity and it has the default "onDelete Restrict", it fails to delete the parent entity record, in this case the Category. If I wanted really to delete the category, I had to first delete the reference in the Product, but I don't want that in this case.
In these cases of failure, the log is still registered.

Category has been deleted with id = "1".

Do you recommend a way to tackle this issue?

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

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

Incompatibility for 1.4.6 ->1.4.9

Hi Ronin,

Versions 1.4.* should be compatible with each other and from 1.4.6 to 1.4.9 many errors occur. For example the function "getBestCandidatePropertyForIdentify" is no longer in the class EntityEventResolver.

Version 1.4.* should only be for improvements or bug fixes not such big code changes.

Thank you very much for the development

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.