Log entity changes¶
The Shopsys platform allows you to log changes to entities. Thanks to the mechanism, you can answer the questions like "Who and when changed the order status?", "Why is the product price changed?", etc.
The logging system works to capture changes in the doctrine unit of work before they are flushed.
We used PHP attributes above class, property, and method for setting logging. (https://www.php.net/manual/en/language.attributes.overview.php)
As an implemented sample, you can study the settings on Order, OrderItem, OrderStatus and Country entities. The principle of use will be described on these examples.
The attributes Loggable
and LoggableChild
are used to mark the entity to be logged.
Both of these attributes have logging strategy settings available.
If I want to log all properties in the base, I use the Loggable(Loggable::STRATEGY_INCLUDE_ALL)
strategy.
If I would like to not log certain properties, I can mark them with the ExcludeLog
attribute.
Conversely, if I want to log only a few properties from an entity, it would be better to use the Loggable(Loggable::STRATEGY_EXCLUDE_ALL)
strategy and then mark which properties I want to log using the Log
attribute.
Danger
It is possible to log only entities with primary key id
and method getId()
/**
* @ORM\Table(name="orders")
* @ORM\Entity
*/
#[Loggable(Loggable::STRATEGY_INCLUDE_ALL)]
class Order
{
...
or
/**
* @ORM\Table(name="order_items")
* @ORM\Entity
*/
#[LoggableChild(Loggable::STRATEGY_INCLUDE_ALL)]
class OrderItem
{
...
Danger
Extended entities in the App namespace need to be marked with the Loggable or LoggableChild attribute as well.
The difference between Loggable
and LoggableChild
is in the possibility of assigning a log on the entity marked LoggableChild
under the logs of another assigned entity.
For example, OrderItem is a child entity of the Order entity. In the case of a child entity, it is still necessary to mark its binding property using the LoggableParentProperty
attribute.
In the case of OrderItem, it's the $order
property.
/**
* @ORM\Table(name="order_items")
* @ORM\Entity
*/
#[LoggableChild(Loggable::STRATEGY_INCLUDE_ALL)]
class OrderItem
{
...
/**
* @var \Shopsys\FrameworkBundle\Model\Order\Order
* @ORM\ManyToOne(targetEntity="Shopsys\FrameworkBundle\Model\Order\Order", inversedBy="items")
* @ORM\JoinColumn(name="order_id", referencedColumnName="id", nullable=false, onDelete="CASCADE")
*/
#[LoggableParentProperty]
protected $order;
...
}
Option to name the logged record¶
The administrator wants to see a human-readable record for the logged record.
This is what the EntityLogIdentify
attribute is used for.
This attribute is set on the method that should return the name of the entity.
For the OrderItem entity, this is the getName()
method.
Some entities are translatable and you need to mark them like this: EntityLogIdentify(EntityLogIdentify::IS_LOCALIZED)
.
In the background, the administration locale is inserted when such a method is called.
#[LoggableChild(Loggable::STRATEGY_INCLUDE_ALL)]
class OrderItem
{
...
/**
* @return string
*/
#[EntityLogIdentify]
public function getName()
{
return $this->name;
}
...
}
There are properties in the Order entity that are not a simple scalar data type.
For example, the status property is of data type OrderStatus or deliveryCountry is of data type Country (another entity).
These entities themselves do not need to be logged, but we want to see the human name on the order in the event of a status change.
The EntityLogIdentify
attribute is again used for this naming.
So if I want to see in the order log that the status has changed from "New" to "In Progress", I need to mark the getName function on the OrderStatus entity.
/**
* @ORM\Table(name="order_statuses")
* @ORM\Entity
* @method \Shopsys\FrameworkBundle\Model\Order\Status\OrderStatusTranslation translation(?string $locale = null)
*/
class OrderStatus extends AbstractTranslatableEntity
{
...
#[EntityLogIdentify(EntityLogIdentify::IS_LOCALIZED)]
/**
* @param string|null $locale
* @return string
*/
public function getName($locale = null): string
{
return $this->translation($locale)->getName();
}
...
}
List of results¶
Shopsys\FrameworkBundle\Component\EntityLog\Model\Grid\EntityLogGridFactory::createByEntityNameAndEntityId($entityName,$entityId)
is available to display the logs.
You can then write this grid, for example, under the editing form.
$entityLogGrid = $this->entityLogGridFactory->createByEntityNameAndEntityId(
EntityLogFacade::getEntityNameByEntity($order),
$order->getId()
);
return $this->render('@ShopsysFramework/Admin/Content/Order/edit.html.twig', [
...
'entityLogGridView' => $entityLogGrid->createView(),
]);
and render it
{{ entityLogGridView.render() }}