Can PuliPHP Re-Revolutionize PHP Package Development?

Nicola Pietroluongo
Nicola Pietroluongo

Puli is a new toolkit built on top of Composer that helps to manage and exchange resources like configuration files, images, CSS files, translation catalogs, and others. These are, you’ll agree, often difficult to maintain and share across projects.

Puli logo

Puli provides a framework-agnostic solution to this problem, keeping track of every resource location and avoiding the problems of using absolute or relative paths inside different systems.

How it works

  • You map resources via CLI.
  • A puli.json file keeps track of them.
  • You use a ResourceRepository instance to handle resources.

The Puli project also provides libraries for other functionalities like URL generation, or Twig support.

Puli components

Install the Puli CLI

To use Puli, first you need to install the Puli CLI. It’s recommended to install it as a phar, which can be done by downloading the latest release from GitHub.

It can be placed in the /usr/local/bin directory for global access.

Below there are command line steps for a quick installation of the 1.0.0-beta7 pre-release in Unix-like systems:

chmod 755 puli.phar
sudo mv puli.phar /usr/local/bin

You can also install Puli as a Composer dependency in your project folder:

composer require --dev puli/cli:^1.0

or as a global Composer package with:

composer global require puli/cli:^1.0`

Once you have installed the CLI, you can use Puli in a Symfony Project, in a PHP Application or a Composer package. You can find more information on your specific project’s type in the getting started guide.

Project layout

It’s always recommended to have a neat directory project structure. The Puli docs reinforce this suggestion, adding a clear separation between the PHP code and the other non-PHP resources.
Below is a recommended directory layout example:

Puli recommended directory layout

Mapping resources

Mapping single files or entire directories can be performed with the Puli map command. For example, to map the res directory’s content using myapp as a resource name (Puli path), you can run:

puli.phar map /myapp res

You will notice that the map is stored in the puli.json file generated inside the project folder.

It is now possible to reach all the mapped resources from PHP using the Puli path /myapp.

The Puli CLI offers other useful commands to manage the mappings. Some of the most used are:

  • ls, to list all the resources.
  • tree, to print the resource list tree.
  • find, to find a resource with some filter criteria, (i.e. puli find --name *.css will find all the .css files).
  • map with -u or -d arguments, to update/delete a map (ie: puli map -u myapp --add /new/folder).

Another important feature is the ability to map resources in Composer packages. For example, to map the /view directory inside a /vendor/author/packagename directory you could use:

puli map /somepulipath @author/packagename:view

Pratical example

To show Puli in action, I have created an example project with Symfony: an image gallery. The demo displays all the images stored in an images_resource folder.

Asset management

Before moving on, it’s worth pointing out a few things about the asset management:

  • Symfony uses the web folder to store all the front controller files and the web assets (CSS, JS, images, other). The web folder is, by default, the web application’s public access point. In other words, direct URL access to the other project directories is not possible.
  • The demo project contains one AppBundle with its CSS and views stored in src/AppBundle/Resources.
  • The Symfony asset management automatically copies every bundle’s resources into the public web folder.

You will see how asset management with Puli is more flexible and provides extra configuration options.

Installation of the demo

Check out the first version with:

git clone -b 1.0

then enter the project’s root directory and run: composer install.

The Symfony console will ask you to configure some parameters, but you can just press enter to accept the default values.

The two important parameters are:

  • images_resource: the images folder
  • images_url: the public images URL

Finally, in the project root, you can run app/console server:start to run the server and see the app in the browser.

Demo project home logo

Mapping the project

To map all the resources in the demo project Bundle you can run:

puli.phar map /myapp/ src/AppBundle/Resources

With puli.phar tree you will see something like:

Puli demo project tree

The Controller

To use the mapped view in the Controller, you can change the DefaultController as follows:

    //old value: $this->template->render('AppBundle:Default:index.html.twig',


You can reopen the project’s page to see that nothing has changed, because Puli perfectly called the mapped view.

The View

The next goal is to refactor the view using a Puli path for the CSS.

You can publish the resources of /myapp/public into the public folder of the demo project, the web directory. Publishing is essentially copying or symlinking the resources over.

Let’s do it step by step:

  • Create a server and link the public folder to it with:

    puli.phar server --add localhost web
  • Register the Puli public path with:

    puli.phar publish /myapp/public localhost /app
  • Install the resources with:

    puli.phar publish --install

The command will create symlinks of the /myapp/public elements into the /web/app path.

Now, it’s time to change the CSS path in the index.html.twig as follows:

    <link href="{{ resource_url('/myapp/public/css/style.css') }}" rel="stylesheet">

At this point you can find all the progress you have made till now in the version 2.0 of the demo project:

git clone -b 2.0


git checkout 2.0

if you already have the demo.

Don’t forget to run composer install in the project’s folder, then puli.phar publish --install.

URL generation

One of Puli’s advantages is to support different server resource locations. For example, if you want to move your resources to a different server like you can run:

puli.phar server -u localhost --url-format

and every mapped URL will be updated automatically.

Another use case can be creating a simple cache invalidation system, appending to the query string something like %s?v1.

Resource discovery

The demo project is quite flexible and allows us to change the images folder and URL. Puli provides a Discovery component which can add even more flexibility – for example in filtering specific resources.

The Discovery component manages the connection between resource providers and resource consumers. In other words, it can connect resources with services that will use them.

In the example, the controller can be a resource consumer, and the image folder can be a resource provider. To handle a resource with the Discovery component, you first define a resource type, then link it to a resource path.

In the example, we will create an app/image resource type linked to the web/images folder with a filter to get only JPEG images.

First, we need to map the web/images folder with:

puli.phar map /myapp/images /web/images

then, you create a resource type:

puli.phar type --define mygallery/image

finally, you can bind the mapped folder with the jpg query:

puli.phar bind /myapp/images/*.jpg mygallery/image

Now, you can refactor the DefaultController as follows:

   // The constructor needs the puli Discovery component instead of the images folder
    public function __construct(EngineInterface $template, KeyValueStoreDiscovery $puliDiscovery)
        $this->template = $template;
        $this->puliDiscovery = $puliDiscovery;

// [...]

    private function getImages()
        $images = array();
        //the discovery component will find the bind type with the filter
        $bindings = $this->puliDiscovery->findByType('mygallery/image');
        //the loop gets the resources image names
        foreach ($bindings as $binding) {
            foreach ($binding->getResources() as $resource) {
                $images[] = $resource->getName();

        return $images;

The AppBundle/Resource/config/service.xml might be:

        class:        AppBundle\Controller\DefaultController
          - @templating
          - @puli.discovery

If you reopen the homepage, you should notice that only .png images are missing, as expected.

To see the final version of the demo project, check out version 3.0 and run composer install and puli publish --install to see the correct result.

The Discovery component was built mainly to connect Composer packages and make this integration easy. You can find more information on The Discovery Component official page.

Final thoughts

Puli solves a real problem about resource management. The CLI is easy to use, and the Composer connection is one of the strongpoints of the project. Thanks to the author and all the contributors, there will soon be a stable release; although the beta works quite well.

You can find more information about Puli in the official documentation and share your personal thoughts in the comments below.