Applying version stamp to Symfony & SonataAdmin

I spend a lot of time in my job in developing and maintaining backoffice systems to process some workflow logic for Forex portals. Our systems are developed using Symfony2 and depend highly on SonataAdminBundle with deep customization to impose business rules for Forex companies.

Recently some data inconsistency appeared in a system of one of our clients, and after digging into logs, I found that the cause of the problem is two users processing same user application in almost same time range, and that caused one edit operation to override the other one, and many other undesired consequences occurs after that.

so in order to fix this issue, and prevent it from happening again, I worked on adding “Version Stamps” to my entity to maintain offline data consistency within the application, and I would like to share here what I learned.

Version Stamps and offline consistency

Version Stamps is a field that changes every time a writing operation is performed on the data, it is used to ensure that no one else has changed data of that row before applying your modification.
There is several ways to implement version stamps, and the simplest way is an integer value which is noted on the read, its value is compared to the submitted data before write, and once validated, the write operation take place with increasing version stamp value.
Let’s say there is a form bind to an entity in Symfony application, version stamp column will be present in the entity and added as a hidden field in the form, once submitted version stamp value submitted will be compared to the one in the database currently, to ensure that no other edit is performed on that entity -in the time between displaying your form initially and submitting it-, if the condition fails, the update operation will be rejected, thus by adding error via constraint.

Implementation in SonataAdmin

The implementation of this concept is quite simple as following:
In the desired entity, I applied those modifications:

Define new field to hold stamp value.

Mark the field to be a version stamp, using doctrine @Version annotation; this will cause doctrine to update versionStamp field every time the object is persisted to the database

In configureFormFields() method, the version stamp is added as hidden field, also I set mapped option to false, to prevent persisting its value along the form. version stamp value must be modified only via PreUpdate() method inside the entity.

Here is the important point, which is validating version_stamp posted from the form against the one that is already saved in the database. There is two methods to apply that, one if by using doctrine locking mechanism, and the other is using sonata inline validation to add extra validation layer by implementing validate() method in order to apply additional validation layer.

This approach will take advantage of doctrine locking support. Here is brief explanation:

I have overridden getObject() method in admin class, to add two extra parameters for getModelManager()->find() method;
third parameter indicates locking type, I used here LockMode::PESSIMISTIC_WRITE
fourth parameter represents the expected version stamp value -to compare with database value before flushing.

I have overridden update($object) method so I catch OptimisticLockException exception and add error flash message to handle it

option 2:
In this approach, I used sonata inline validation to detect the form as invalid before even trying to persist and flush the object to the database:

Test Solution

In order to verify that this mechanism solved the issue, I emulated the inconsistency behavior, by opening sonata admin edit page on two browsers, and then try to modify the data and submitted on each browser consequently.
The browser that got submitted last, will not save data and error will appear
“Record data seems outdated, probably someone else modified it, please refresh and try again.”
In that way, the inconsistency is prevented by stopping the second user from overriding the information modified by the other user.

At last

“Version Stamp” approach helped me in preventing data inconsistency in my Symfony/SonataAdmin application, hope it will help others who face similar scenario. I would like to know if anyone else has other idea or better way to handle that issue.

About me

My name is Zeid Rashwani, Syrian LAMP developer, lives in Jordan/Amman and works in Wewebit. Web development is my main activity, in my free time I tend to watch anime and movies..
This blog contains technical topics related to web development, along with other matters.