Managing Web Assets with Puli

Yesterday marked the release of the next beta version of Puli 1.0. Puli is now feature-complete and ready for you to try. The Puli documentation has been updated and contains all the information that you need to get started. My current plan is to publish a Release Candidate by the end of the month and a first stable release at the end of April.

The most important addition since the last beta release is Puli's new Asset Plugin. Today, I'd like to show you how this plugin helps to manage the web assets of your project and your installed Composer packages independent of any specific PHP framework.

What is Puli?

Update : This post is partially obsolete. Please check the Puli website for up-to date information about Puli.

You never heard of Puli before? In a nutshell, Puli is a resource manager built on top of Composer. Just like Composer generates an autoloader for the classes in your Composer packages, Puli generates a resource repository that contains all files that are not PHP classes (images, CSS, XML, YAML, HTML, you name it). You can access these resources by simple paths prefixed with the name of the package:

echo $twig->render('/acme/blog/views/footer.html.twig');

The only exceptions are end-user applications, which have the prefix /app by convention:

echo $twig->render('/app/views/index.html.twig');

Web Assets

Some resources – such as templates or configuration files – are needed by the web server only. Others – like CSS files and images – need to be placed in a public directory, where browsers can download them. I'll call these files web assets here.

Puli's Asset Plugin takes care of two things:

  • installing web assets in their public location;
  • generating the URLs for these assets.

The public location for installing assets is called an install target in Puli's language. Puli supports virtually any kind of install target, such as:

  • the document root of your own web server
  • the document root of another web server
  • a Content Delivery Network (CDN)

Install targets store three pieces of information:

  • their location (a directory path, a URL, …)
  • the used installer (symlink, copy, ftp, rsync, …)
  • their URL format

The URL format is used to generate URLs for the assets installed in the target. The default format is /%s, but you could set it to more elaborate values such as

Creating an Install Target

Let me walk you through a simple example of using the plugin for a typical project. We will work with the following setup:

  • the application's assets are stored in the Puli path /app/public
  • the assets of the "acme/blog" package are stored in /acme/blog/public
  • all assets should be installed in the directory public_html

Before we can start, we need to install the plugin with Composer:

$ composer require puli/asset-plugin:~1.0

Make sure "minimum-stability" is set to "dev" in your composer.json file:

    "minimum-stability": "dev"

Activate the plugin with Puli's Command Line Interface (CLI):

$ puli plugin install Puli\\AssetPlugin\\Api\\AssetPlugin

The plugin is loaded successfully if the command puli target succeeds:

$ puli target
No install targets. Use "puli target add <name> <directory>" to add a target.

Let's create a target named "local" now that points to the aforementioned public_html directory:

$ puli target add local public_html

Run puli target again to see the target that you just added:

$ puli target
  * local symlink public_html /%s

Installing Web Assets

With the install target ready, we can now map resources to the target:

$ puli asset map /app/public /
$ puli asset map /acme/blog/public /blog

Let's run puli asset to see the mappings we added:

$ puli asset
The following web assets are currently enabled:

    Target default (alias of: local)
    Location:   public_html
    Installer:  symlink
    URL Format: /%s

        54069d /acme/blog/public /blog
        022aeb /app/public       /

Use "puli asset install" to install the assets in their targets.

The output of this command gives us a lot of information:

  • We added our assets to the default target, i.e. our only target "local". In some cases, it is useful to have more than one install target.
  • The assets in /app/public will be installed in public_html.
  • The assets in /acme/blog/public will be installed in public_html/blog.

All that is left to do is installing the assets:

$ puli asset install
Installing /acme/blog/public into public_html/blog via symlink...
Installing /app/public into public_html via symlink...

You should be able to access your assets in the browser now.

Generating Resource URLs

Now that our assets are publicly available, our application needs to generate their proper URLs. If you use Twig, you can use the asset_url() function of Puli's Twig Extension to do that:

<!-- /images/header.png -->
<img src="{{ asset_url('/app/public/images/header.png') }}" />

The function accepts absolute Puli paths or paths relative to the Puli path of your template:

<img src="{{ asset_url('../images/header.png') }}" />

If you need to generate URLs in PHP code, you can use Puli's AssetUrlGenerator. Add the following setup code to your bootstrap file or your Dependency Injection Container:

// Puli setup
$factoryClass = PULI_FACTORY_CLASS;
$factory = new $factoryClass();
$repository = $factory->createRepository();
$discovery = $factory->createDiscovery($repository);

// URL Generator setup
$urlGenerator = $factory->createUrlGenerator($discovery);

Asset URLs can be generated with the generateUrl() method of the URL generator:

// /images/header.png

Read the Web Assets guide in the Puli Documentation if you want to learn more about handling web assets with Puli.

The Future of Packages in PHP

With Puli and especially with Puli's Asset Plugin, we have exciting new possibilities of creating Composer packages that work with different frameworks at the same time. Basically, a bundle/plugin/module/… of the framework of your choice is reduced to:

  • PHP code, which is autoloaded by Composer's autoloader.
  • Resource files that are managed and published by Puli.
  • A thin layer of configuration files/code for integrating your Package with a framework of your choice.

Since the framework-dependent code is reduced to a few configuration files or classes, it is possible to add support for multiple frameworks at the same time. For open-source developers, that's a great thing, because they have to maintain much less packages and code than they had to before. For users of open-source software, that's a great thing too, because it becomes possible to use the magnificent package X with your framework Y, even though X was sadly developed for framework Z. I think that's exciting. Do you?

Let me know what you think in the comments. Read the Web Assets guide in the Puli Documentation if you want to learn more about the plugin.