xiidea / easyauditbundle Goto Github PK
View Code? Open in Web Editor NEWA Symfony Bundle To Log Selective Events
Home Page: http://xiidea.github.io/EasyAuditBundle/
License: MIT License
A Symfony Bundle To Log Selective Events
Home Page: http://xiidea.github.io/EasyAuditBundle/
License: MIT License
Need event log type to be added like: Debug, Info, Error ... etc
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.
Anis
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
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
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
[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 ?
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.
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
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
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?
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.
Any plans to make the bundle compatible with symfony 6?
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.
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.
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,
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`
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.
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
A lot of people are running on the LTS version of symfony, will it be supported?
Hi,
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)"}
in vendor/xiidea/easy-audit/Logger/Logger.php (line 38) if(empty($event)) { return; } $this->getEntityManager()->persist($event); $this->getEntityManager()->flush($event); } /** * @return EntityManager */
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;
}
<?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;
}
}
Can you give a hand on this? Can you reproduce the error? Thanks.
Need to create a view interface to view logged events... at least last N events
The checker detected 2 security issues in package symfony/symfony installed in version 3.0.4.0
Posted from SensioLabsInsight
To support ODM, we have to do
\Doctrine\ORM\*
With equivalent \Doctrine\Common\*
Entity*
Term to more generic Object*
ORMSubscribedEvents
to SubscribeDoctrineEvents
Doctrine entity configured through configuration not working
doctrine_entities :
AppBundle\Entity\Project : [created, updated, deleted]
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 ?
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
Deprecated in 2.4, to be removed in 3.0. The event name is passed to the listener call
Trying to use custom logger as default logger, leads to error :Unrecognized option "loggers" under "xiidea_easy_audit"
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.
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
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
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.