Puli: Powerful Resource Management for PHP

Since the introduction of Composer and the autoloading standards PSR-0 and PSR-4, the PHP community changed a lot. Not so long ago, it was difficult to integrate third-party code into your application. Now, it has become a matter of running a command on the terminal and including Composer's autoload file. As a result, developers share and reuse much more code than ever before.

Unfortunately, sharing your work gets a lot harder when you leave PHP code and enter the land of configuration files, images, CSS files, translation catalogs – in short, any file that is not PHP. For brevity, I'll call these files resources here. Using resources located in Composer packages is quite tedious: You need to know exactly where the package is installed and where the resource is located in the package. That's a lot of juggling with absolute and relative file system paths and prone to error.

Plugins, Modules, Bundles

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

To simplify matters, most frameworks implement their own mechanisms on top of Composer packages. Some call them "plugins", others "modules", "bundles" or "packages". They have in common that they follow some sort of predefined directory layout together with a naming convention that lets you refer to resources in the package. In Symfony, for example, you can refer to a Twig template profiler.html.twig located in FancyProfilerBundle like this:

$twig->render('FancyProfilerBundle::profiler.html.twig');

This only works if you use Symfony, of course. If you want to use the FancyProfiler in a different framework, the current best practice is to extract the framework-agnostic PHP code into a separate package (the FancyProfiler "library") and put everything else into "plugins", "modules" and "bundles" tied to the chosen framework. This leads to several problems:

  • You need to duplicate many resource files: images, CSS files or translation catalogs hardly depend on one single framework. If you use a widespread templating engine like Twig, then even your templates will be very similar across frameworks.

  • You need to maintain many packages: The core library plus one package per supported framework. That's a lot of maintenance work.

Wouldn't it be nice if this could be simplified?

Puli

One and a half years ago I talked about this problem with Beau Simensen and several others at PHP-FIG. I wrote a blog post about The Power of Uniform Resource Location in PHP. Many people joined the discussion. The understanding of the problem and its solution got riper as we spoke.

Today, I am glad to present to you the first alpha version of Puli, a framework-agnostic resource manager for PHP. Puli manages resources in a repository that looks similar to a UNIX file system: You map files and directories to paths in the repository and use the same paths (we'll call them Puli paths) to find the files again.

The mapping is done in a puli.json file in the root of your project or package:

{
    "resources": {
        "/app": "res"
    }
}

In this example, the Puli path /app is mapped to the directory res in your project. The repository can be dumped as PHP file with the Puli Command-Line Interface (CLI):

$ puli dump

Use the repository returned from the generated file to access your resources:

$repo = require __DIR__.'/.puli/resource-repository.php';

// res/views/index.html.twig
echo $repo->get('/app/views/index.html.twig')->getContents();

Composer Integration

That alone is nice, but not highly useful. However, Puli supports a Composer plugin that loads the puli.json files of all loaded Composer packages. Let's take the puli.json in the fictional "webmozart/fancy-profiler" package again for example:

{
    "resources": {
        "/webmozart/fancy-profiler": "res"
    }
}

By convention, Puli paths in reusable Composer packages use the vendor and package names as top-level directories. This way it is easy to know where a Puli path belongs. Let's dump the repository again and list the contained files:

$ puli dump
$ puli list -r /webmozart/fancy-profiler
/webmozart/fancy-profiler/views
/webmozart/fancy-profiler/views/index.html.twig
/webmozart/fancy-profiler/views/layout.html.twig
...

Both in the application and the profiler package, we can access the package's resources through the repository:

// fancy-profiler/res/views/index.html.twig
echo $repo->get('/webmozart/fancy-profiler/views/index.html.twig')->getContents();

Tool Integration

I think this is quite exciting already, but it gets better once you integrate Puli with your favorite framework or tool. There already is a working Twig Extension which supports Puli paths in Twig templates:

{% extends '/app/views/layout.html.twig' %}

{% block content %}
    {# ... #}
{% endblock %}

You can also use relative Puli paths:

{% extends '../layout.html.twig' %}

The Symfony Bridge integrates Puli into the Symfony Config component. With that, you can reference configuration files by their Puli paths:

# routing_dev.yml
_wdt:
    resource: /symfony/web-profiler-bundle/config/routing/wdt.xml
    prefix:   /_wdt

The Symfony Bundle adds Puli support to a Symfony full-stack project. You can also start a new Symfony 2.5 project from the Symfony Puli Edition, if you like. An Assetic Extension is work-in-progress.

I focused on supporting the Symfony ecosystem for now because that is the one I know best, but Puli can, should and hopefully will be integrated into many more frameworks and tools. The Puli repository can be integrated into your favorite IDE so that you can browse and modify the repository without ever leaving your editor. There are countless possibilities.

Getting Started

Download the Puli Alpha version with Composer:

$ composer require puli/puli:~1.0

Make sure you set the "minimum-stability" option in your composer.json properly before running that command:

{
    ...,
    "minimum-stability": "alpha"
}

Beware that this is an alpha version, so some things may not work or change before the final release. Please do not use Puli in production.

Due to the limited scope of this post, I just scratched the top of Puli's functionality here. Read the Puli documentation to learn everything about what you can do with Puli. Head over to the issue tracker if you find bugs.

And of course, please leave a comment here :) I think Puli will significantly change the way we use and share packages. What do you think?

Discussion