Getting Started with Assetic
There was a time when asset management meant little more than inserting a
<link> tag or two and a couple of
<script> tags into your HTML.
Nowadays, though, that approach just won’t cut it. There’s performance, for one thing. Assets are a significant performance drain both in terms of file size and the number of HTTP requests – optimizing both has become an essential part of the development process. This has been exacerbated by the proliferation of mobile devices.
Also, as client-side applications have become more and more sophisticated, managing dependencies amongst scripts and libraries has become increasingly complex.
Furthermore, technologies such as Less, Compass and Coffeescript require assets to be compiled, adding yet another step to the process of managing assets.
In this article I’m going to look at a PHP package called Assetic which helps manage, compile and optimize assets such as scripts, stylesheets and images.
What is asset management?
Let’s start by looking at what asset management actually is.
Assetic is a PHP package designed to help with the asset management process.
There are a couple of important concepts to understand when getting started with Assetic – collections and filters.
A collection groups together a bunch of assets. A single file is an asset. ‘Nix users in particular will be familar with glob, which is essentially a way of collecting together a bunch of files whose filenames match a particular pattern; Assetic treats these as an asset as well. For example, the glob patterm
/assets/js/*.js refers to all files in
/assets/js with the
A filter is applied to a collection to modify it in some way. If you refer back to the idea of stages in the asset management process, you can think of each stage as being implemented by one or more filters. So for example, one filter might be responsible for compilation, and one for minification.
Different filters are usually applied to different collections. For example, you might have a project with the following:
- A collection of
.lessfiles which are compiled with the Less filter, minified using a CSS minification filter and concatenated into one CSS file.
- A collection of
.cssfiles which are simply minified
- A collection of Coffeescript files are compiled using the Coffeescript filter, minified using the Google Closure Compiler filter, then concatenated into one
The exact combinations of file types and filters will depend on what technologies you’re using and your personal preference, such as your preferred minifier.
You can install Assetic by downloading or cloning from Github or better still, via Composer:
There are also Assetic packages available on Packagist for frameworks such as Zend Framework, Symfony, Laravel and Silex.
$js = new AssetCollection(array( new GlobAsset('/assets/js/libs/*'), new FileAsset('/assets/js/app.js'), )); print $js->dump();
The code above takes all
.js files from
/assets/js/libs and then the file
/assets/js/app.js, and concatenates them when
dump() is called – we’ll look at what you do with the output shortly.
$scripts = new AssetCollection(array( new GlobAsset('/assets/js/libs/*'), new FileAsset('/assets/js/app.js'), ), array( new JSMinFilter(), )); print $scripts->dump();
Note: If you wish to use JSMin, you can use the following PHP implementation:
"lmammino/jsmin4assetic": "1.0.*@dev"(view it on Github here).
Your stylesheets will need a separate asset collection, given that the filters behave differently. Suppose you have a bunch of CSS files, and you’re using Less to define your grid:
$styles = new AssetCollection(array( new FileAsset('/assets/less/grid.less', array(new LessFilter())), new GlobAsset('/assets/css/*'), ), array( new Yui\CssCompressorFilter('/path/to/yuicompressor.jar'), )); print $styles->dump();
This uses the
LessFilter to compile
grid.less, concatenates it along with all the
.css files in
/assets/css, then compresses the whole thing using the YUI compressor.
Note that in order to get the example above working, you’ll need to ensure you have the YUI Compressor installed correctly, ensure that the path to its
.jarfiles is correct, and add the following to your
If you’d rather use CssMin than the YUI Compressor, you’ll need to install it, and your code becomes:
$styles = new AssetCollection(array( new FileAsset('/assets/less/grid.less', array(new LessFilter())), new GlobAsset('/assets/css/*'), ), array( new CssMinFilter(), )); print $styles->dump();
If you’re using SASS instead of Less, install SCSSphp by adding the following to your
You can then use
ScssphpFilter to compile your SASS assets.
You can optimize images, too. In this example, we use glob to grab all images files from
assets/img and compress them with OptiPng, then do the same with Jpegs but use Jpegoptim:
$images = new AssetCollection(array( new GlobAsset('/assets/img/*.png', array(new OptiPngFilter())), new GlobAsset('/assets/img/*.jpg', array(new JpegoptimFilter()), ));
Manager Classes and the AssetFactory
There are other ways to configure your assets. The
AssetManager class is essentially a keyed store of assets – for example, you can collect the asset collections we defined in the previous section together like this:
$am = new AssetManager(); $am->set('jquery', new FileAsset('/path/to/jquery.js')); $am->set('scripts', $scripts); $am->set('styles', $styles);
FilterManager class works in much the same way:
$fm = new FilterManager(); $fm->set('sass', new SassFilter('/path/to/parser/sass')); $fm->set('yui_css', new Yui\CssCompressorFilter('/path/to/yuicompressor.jar'));
Then you can use the
AssetFactory class to simplify the whole process:
use Assetic\Factory\AssetFactory; $factory = new AssetFactory('/path/to/asset/directory/'); $factory->setAssetManager($am); $factory->setFilterManager($fm); $factory->setDebug(true); $styles = $factory->createAsset(array( '@reset', // load the asset manager's "reset" asset 'css/src/*.scss', // load every scss files from "/path/to/asset/directory/css/src/" ), array( 'scss', // filter through the filter manager's "scss" filter '?yui_css', // don't use this filter in debug mode )); echo $styles->dump();
I’ve shown how to select what you want to include and how to apply filters; but I haven’t yet looked at where you do this or how you reference it. There are a number of approaches you can take.
One possibility is to create a PHP file in a publicly accessible directory, e.g.
Or, you could create a file called
stylesheets.php and embed it inline:
<style> <?php include('/assets/stylesheets.php'); </style>
Alternatively you could generate
.js files and just reference those as normal. You can use the
AssetWriter for this:
use Assetic\AssetWriter; $scripts.js = new AssetCollection(array( new GlobAsset('/assets/js/libs/*'), new FileAsset('/assets/js/app.js'), ), array( new JSMinFilter(), )); // Set target path, relative to the path passed to the // AssetWriter constructor as an argument shortly $scripts->setTargetPath('scripts.js'); $am->set('scripts.js', $scripts.js); // see above for instantiation of $styles $styles->setTargetPath('stylesheets.css'); $am->set('styles', $styles); $writer = new AssetWriter('/assets/build'); $writer->writeManagerAssets($am);
You could create a command-line script to do this as part of your workflow, or use a tool such as Guard to “watch” the filesystem and re-run it whenever one of the relevant files has changed.
Assetic ships with a simple file-based caching mechanism to ensure that filters aren’t run unnecessarily. Here’s an example of caching the output of the YUI Compressor:
use Assetic\Asset\AssetCache; use Assetic\Asset\FileAsset; use Assetic\Cache\FilesystemCache; use Assetic\Filter\Yui; $yui = new Yui\JsCompressorFilter('/path/to/yuicompressor.jar'); $js = new AssetCache( new FileAsset('/path/to/some.js', array($yui)), new FilesystemCache('/path/to/cache') ); // the YUI compressor will only run on the first call $js->dump(); $js->dump(); $js->dump();
In this article I’ve introduced Assetic – a PHP package for managing assets. I’ve shown how you can use it to manage dependencies, run compilation processes, minify / pack / compress / optimise assets, and concatenate files to minimise the number of HTTP requests. Be sure to check out the documentation for details of all the available filters; or, you could even look at implementing
FilterInterface / extending
BaseFilter with a view to defining your own. For packages which complement it, refer to the suggested packages either when you first install it, or by inspecting the
suggests section of its