The Symfony2 Expression Language is a very great component allowing to create expressions that can be evaluated and compiled into plain PHP. It's mostly used by the Symfony2 Framework to add an other dimension of configuration but can be integrated into any PHP project.
One of the use cases I'm thinking about is a pricing engine that could be used in e-commerce softwares. Discount rules could be defined as expressions and applied to the base product price in order to calculate it's final price. Developers would be able to configure the pricing rules for their clients without having to write any line of code. In most e-commerce CMS, users need to select values, operators, dates in order to produce very limited discount rules, while they could simply enter one line expressions to create rules of any complexity.
In order to understand this article you must already be familiar with the component. It's documentation can be found here.
The Product model
Firstly, we need to define our Product model as the discount rules will depend on the product attributes.
Determining the basic rules and functions
Before coding anything we need to determine which functions we want to make available in our language.
We would like to create those kinds of rules:
- apply a 10% discount if there is only one item left in stock
- apply a 5% discount during the sales (from 20/01/2014 to 02/02/2014 for example)
- apply a 50% discount if the product has been created more than one year ago and we have less than five items in stock
So we need the following functions:
This function returns the specified time as a
It will be needed to transform the dates
DateTime instances to perform operations on them as specified in the second rule.
It does the same as
DateTime::__construct(time) in PHP.
This function modifies the date and returns it.
It will be needed to increment the product creation date of one year to be used in the third rule.
It is the same function as
DateTime::modify(time) in PHP.
Translated into expressions (using our functions), the discount rules become:
product.getStock() == 1 ? 0.1 : 0
date('now') >= date('2014-01-20') and date('now') <= date('2014-02-02') ? 0.05 : 0
date('now') < date_modify(product.getCreationDate(), '+1 year') and product.getStock() < 5 ? 0.5 : 0
Using the ternary operator, each rule evaluates the discount coefficient that will be applied
Product price (for example 0.1 for 10%).
Creating our DSL
Language class extends the default
ExpressionLanguage and represents our Domain Specific Language.
Building the engine
The pricing engine will make use of our custom
Language class to calculate the price of a
Now the engine has been developed, we can use it with some examples.
Note : Depending on when you will execute this code, you will get different results due to the rules based on today's date!
The system we built is very simplistic as we don't take into account if discounts can be cumulated. In most systems, discounts are ordered (for example taxes are applied at the end). This could be done by creating prioritized groups of rules. Also, our rules are applied to all products, while in real systems they can also be attached to specific products or categories.
In a nutshell, the Symfony2 Expression Language component is really powerful and allows to create custom rule engines by extending it with simple functions defined as closures.comments powered by Disqus