Symfony2: Make your own Assetic Filter

Symfony2 uses Assetic as its asset manager, it provides many excellent filters for processing CSS, JavaScript and Image files. You may find though that the existing filters do not perform a task you need. Thanks to the excellent Dependency Injection in Symfony2 it is easy to write your own filter and put it to use without touching any of the code for Assetic itself.

Your filter can live where ever you see fit as we will be able to point to it using our service config, I am going to put mine in the test bundle I have been using. My post, Symfony2: Controller as Service, has details of how to create a Dependency Injection extension to load a service config for your bundle as you will need to use this to configure the filter later.

Your filter needs to implement the Assetic/Filter/FilterInterface which is as follows:

<?php

/*
 * This file is part of the Assetic package, an OpenSky project.
 *
 * (c) 2010-2011 OpenSky Project Inc
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Assetic\Filter;

use Assetic\Asset\AssetInterface;

/**
 * A filter manipulates an asset at load and dump.
 *
 * @author Kris Wallsmith <kris.wallsmith@gmail.com>
 */
interface FilterInterface
{
    /**
     * Filters an asset after it has been loaded.
     *
     * @param AssetInterface $asset An asset
     */
    function filterLoad(AssetInterface $asset);

    /**
     * Filters an asset just before it's dumped.
     *
     * @param AssetInterface $asset An asset
     */
    function filterDump(AssetInterface $asset);
}

So you need to implement the two methods filterLoad and filterDump, the former is run when the filter is first loaded and the latter when the assets are dumped. In most cases the work can be done in filterDump so you can just have an empty filterLoad method:

<?php

namespace LimeThinking\SpringBundle\Assetic\Filter;

use Assetic\Asset\AssetInterface;
use Assetic\Filter\FilterInterface;

class MyFilter implements FilterInterface
{


    public function filterLoad(AssetInterface $asset)
    {
    }


    public function filterDump(AssetInterface $asset)
    {
        $content = $asset->getContent();
        //Do something to $content
        $asset->setContent($content);
    }


}

So how do we put this to use? The key is to use the tag element in the service config:

<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <parameters>
        <parameter 
            key="lime_thinking_spring.my_filter.class"
        >LimeThinking\SpringBundle\Assetic\Filter\MyFilter</parameter>
    </parameters>
    
    <services>
        
        <service id="lime_thinking_spring.my_filter"
                 class="%lime_thinking_spring.my_filter.class%"
        >
            <tag name="assetic.filter" alias="my_filter"></tag>
        </service>
         
    </services>

</container>

Tagging the service with assetic.filter will register it with Assetic, the alias attribute is required and is the name which you will use to reference the filter from Twig. You can now use your filter from Twig:

{% stylesheets filter="my_filter"
    output="/css/main.css"
    '@LimeThinkingSpringBundle/Resources/css/overall.css'
    '@LimeThinkingSpringBundle/Resources/css/social.css' 
%}
    <link href="{{ asset_url }}" rel="stylesheet" media="screen" />
{% endstylesheets %}

You can use Dependency Injection to configure any object dependencies and parameters needed by your filter from the service config, there is more on this in my posts Symfony2: Dependency Injection Types and Symfony2: Injecting Dependencies Step by Step. The basic service config loader in my post Symfony2: Controller as Service just uses the parameters in your XML config file, it is possible though to open up these parameters to be set in an app's config file, I will look at how to do this in a forthcoming post.