In a recent project, I used the Symfony2 PHP Web Framework, which has Twig as its default template system. Having worked with Twig before, I decided to keep it, rather than trying to learn some other system and integrate it with Symfony. During the process, I realized that I was doing a lot of the same logic over and over, and it would make more sense to condense that into a simple function call. I knew about Twig filters, which can apply logic to a variable in a Twig template and return the result, but I hadn’t previously thought about making my own. When I did a little research, I learned that it was actually a very easy process.
As an example, for the project to which I referred previously, I was working with a developer at another company, who was developing an API which was then being used by the front-end application I was developing. For several of the date timestamps, he was returning a JSON Date object. This comes in the format of:
/Date(1365004652303-0500)/
As you might guess, PHP cannot handle this by default, and if you try to separate the numbers by the dash to get a timestamp/offset pair, you’ll see that the timestamp is actually more digits than the PHP time() function returns. When you call time(), you get a 10-digit number, but this number is 13-digits. So I found that I couldn’t expect to be able to just pop that into date() and hope that it gives me a realistic date. I did a little bit of research and found a StackOverflow answer that had the function I needed.
But rather than just putting that function into my controller class and doing extra processing before the values are sent to the template, I decided to create a Twig filter to handle it for me so that I still had the original JSON Date object if I needed it without having to add new variables.
To create one or more Twig filters, you need two things:
- a service definition
- a service class
The service definition looks like this (the main thing to note is the tag, twig.extension) and the class I created looks like this. In order to keep your bundle organized, the suggested namespace/directory structure for your class is AcmeDemoBundleTwig, which contains the class AcmeExtension. The way it works is it associates a text label (typed in the template) with a callback function, usually a method of the service class. This is done by the getFilters() method, which returns an associative array, with the keys being the label used in the template, and the values being the callable functions that contain the logic.
In my example, parseJsonDate() takes three arguments, the first of which is automatically provided as the value on which you are applying the filter. So if I were to use {{ dateVariable|parseJsonDate }}, the value of dateVariable would be the first argument. The next two are optional arguments that control the formatting and manipulate the value. As an example of using all of the arguments, I could type {{ dateVariable|parseJsonDate(‘m/d/Y’, ‘+1 week’) }}.
If you want to add another filter, you don’t need a new service definition or class, you just add the filter to the array in getFilters() and create a new method to handle the logic.
A second example I am using is a currency_format filter that is used instead of the number_format filter when working with money values. It works the same with the following notable differences:
- The value is prepended with a currency symbol (second-to-last argument of filter; default: $)
- When no decimal precision is given, it defaults to 2 decimal places, rather than 0 (first argument of filter)
- Negative values are by default wrapped in parentheses and the negative sign is dropped. (disable by making the final argument false)
As an example: {{ 100|currency_format }} results in $100.00. {{ -100|currency_format }} results in ($100).
Because filters are simply PHP closures, nearly any repetitive process can be automated. I suggest you see if you can save some time by creating a filter instead of doing a lot of extra, repetitive logic in the template or processing in your business logic.