This article reflects v3.4 and has not yet been revised

Gestión de Activos (Assets)

Phalcon\Assets is a component that allows you to manage static resources such as CSS stylesheets or JavaScript libraries in a web application.

Phalcon\Assets\Manager is available in the services container, so you can add resources from any part of the application where the container is available.

Adding Resources

Assets supports two built-in resources: CSS and JavaScripts. You can create other resources if you need. The assets manager internally stores two default collections of resources - one for JavaScript and another for CSS.

You can easily add resources to these collections like follows:

<?php

use Phalcon\Mvc\Controller;

class IndexController extends Controller
{
    public function index()
    {
        // Agregar algunos recursos CSS locales
        $this->assets->addCss('css/style.css');
        $this->assets->addCss('css/index.css');

        // Y otros recursos JavaScript locales
        $this->assets->addJs('js/jquery.js');
        $this->assets->addJs('js/bootstrap.min.js');
    }
}

Then in a view, these resources can be printed:

<html>
    <head>
        <title>Un sitio web asombroso</title>

        <?php $this->assets->outputCss(); ?>
    </head>

    <body>
        <!-- ... -->

        <?php $this->assets->outputJs(); ?>
    </body>
<html>

Sintaxis Volt:

<html>
    <head>
        <title>Some amazing website</title>

        {{ assets.outputCss() }}
    </head>

    <body>
        <!-- ... -->

        {{ assets.outputJs() }}
    </body>
<html>

For better page load performance, it is recommended to place JavaScript at the end of the HTML instead of in the <head>.

Local/Remote resources

Local resources are those who are provided by the same application and they’re located in the document root of the application. URLs in local resources are generated by the url service, usually Phalcon\Url.

Remote resources are those such as common libraries like jQuery, Bootstrap, etc. that are provided by a CDN.

The second parameter of addCss() and addJs() says whether the resource is local or not (true is local, false is remote). By default, the assets manager will assume the resource is local:

<?php

public function indexAction()
{
    // Add some remote CSS resources
    $this->assets->addCss('//netdna.bootstrapcdn.com/twitter-bootstrap/2.4.0/css/bootstrap-combined.min.css', false);

    // Then add some local CSS resources
    $this->assets->addCss('css/style.css', true);
    $this->assets->addCss('css/extra.css');
}

Collections

Collections group resources of the same type. The assets manager implicitly creates two collections: css and js. You can create additional collections to group specific resources to make it easier to place those resources in the views:

<?php

// Javascripts en la cabecera
$headerCollection = $this->assets->collection('header');

$headerCollection->addJs('js/jquery.js');
$headerCollection->addJs('js/bootstrap.min.js');

// Javascripts en el pie del documento
$footerCollection = $this->assets->collection('footer');

$footerCollection->addJs('js/jquery.js');
$footerCollection->addJs('js/bootstrap.min.js');

Then in the views:

<html>
    <head>
        <title>Algún sitio web asombroso</title>

        <?php $this->assets->outputJs('header'); ?>
    </head>

    <body>
        <!-- ... -->

        <?php $this->assets->outputJs('footer'); ?>
    </body>
<html>

Sintaxis Volt:

<html>
    <head>
        <title>Some amazing website</title>

        {{ assets.outputCss('header') }}
    </head>

    <body>
        <!-- ... -->

        {{ assets.outputJs('footer') }}
    </body>
<html>

URL Prefixes

Collections can be URL-prefixed, this enables you to easily change from one server to another at any moment:

<?php

$footerCollection = $this->assets->collection('footer');

if ($config->environment === 'development') {
    $footerCollection->setPrefix('/');
} else {
    $footerCollection->setPrefix('http:://cdn.example.com/');
}

$footerCollection->addJs('js/jquery.js');
$footerCollection->addJs('js/bootstrap.min.js');

A chainable syntax is available too:

<?php

$headerCollection = $assets
    ->collection('header')
    ->setPrefix('https://cdn.example.com/')
    ->setLocal(false)
    ->addJs('js/jquery.js')
    ->addJs('js/bootstrap.min.js');

Minification/Filtering

Phalcon\Assets provides built-in minification of JavaScript and CSS resources. You can create a collection of resources instructing the Assets Manager which ones must be filtered and which ones must be left as they are. In addition to the above, Jsmin by Douglas Crockford is part of the core extension offering minification of JavaScript files for maximum performance. In the CSS land, CSSMin by Ryan Day is also available to minify CSS files.

The following example shows how to minify a collection of resources:

<?php

$manager

    // Este JavaScripts es ubicado en el final de la página
    ->collection('jsFooter')

    // El nombre final del archivo reducido
    ->setTargetPath('final.js')

    // La etiqueta del script es generado con esta URI
    ->setTargetUri('production/final.js')

    // Este es un recurso remoto que no necesita ser filtrado
    ->addJs('code.jquery.com/jquery-1.10.0.min.js', false, false)

    // Este es un recurso local que necesita ser filtrado
    ->addJs('common-functions.js')
    ->addJs('page-functions.js')

    // Unir todos los recursos en un solo archivo
    ->join(true)

    // Usar el filtro incorporado jsmin
    ->addFilter(
        new Phalcon\Assets\Filters\Jsmin()
    )

    // Usar un filtro personalizado
    ->addFilter(
        new MyApp\Assets\Filters\LicenseStamper()
    );

A collection can contain JavaScript or CSS resources but not both. Some resources may be remote, that is, they’re obtained by HTTP from a remote source for further filtering. It is recommended to convert the external resources to local for better performance.

As seen above, the addJs() method is used to add resources to the collection, the second parameter indicates whether the resource is external or not and the third parameter indicates whether the resource should be filtered or left as is:

<?php

// Este Javascripts es ubicado al final de la página
$jsFooterCollection = $manager->collection('jsFooter');

// Este es un recurso remoto que no necesita ser filtrado
$jsFooterCollection->addJs('code.jquery.com/jquery-1.10.0.min.js', false, false);

// Este es un recurso local que debe ser filtrado
$jsFooterCollection->addJs('common-functions.js');
$jsFooterCollection->addJs('page-functions.js');

Filters are registered in the collection, multiple filters are allowed, content in resources are filtered in the same order as filters were registered:

<?php

// Usar el filtro incorporado Jsmin
$jsFooterCollection->addFilter(
    new Phalcon\Assets\Filters\Jsmin()
);

// Usar un filtro personalizado
$jsFooterCollection->addFilter(
    new MyApp\Assets\Filters\LicenseStamper()
);

Note that both built-in and custom filters can be transparently applied to collections. The last step is to decide if all the resources in the collection must be joined into a single file or serve each of them individually. To tell the collection that all resources must be joined you can use the join() method.

If resources are going to be joined, we need also to define which file will be used to store the resources and which URI will be used to show it. These settings are set up with setTargetPath() and setTargetUri():

<?php

$jsFooterCollection->join(true);

// La ruta y nombre del archivo final
$jsFooterCollection->setTargetPath('public/production/final.js');

// La etiqueta HTML del script es generada con esta URI
$jsFooterCollection->setTargetUri('production/final.js');

Built-In Filters

Phalcon provides 2 built-in filters to minify both JavaScript and CSS, their C-backend provide the minimum overhead to perform this task:

Filtro Descripción
Phalcon\Assets\Filters\Jsmin Reduce archivos JavaScript eliminando caracteres innecesarios que son ignorados por los compiladores/intérpretes de Javascript
Phalcon\Assets\Filters\Cssmin Reduce archivos CSS al quitar caracteres innecesarios que son ignorados por los navegadores

Custom Filters

In addition to the built-in filters, you can create your own filters. These can take advantage of existing and more advanced tools like YUI, Sass, Closure, etc.:

<?php

use Phalcon\Assets\FilterInterface;

/**
 * Flitra el contenido de archivos CSS usando YUI
 *
 * @param string $contents
 * @return string
 */
class CssYUICompressor implements FilterInterface
{
    protected $options;

    /**
     * CssYUICompressor constructor
     *
     * @param array $options
     */
    public function __construct(array $options)
    {
        $this->options = $options;
    }

    /**
     * Hace el filtrado
     *
     * @param string $contents
     *
     * @return string
     */
    public function filter($contents)
    {
        // Escribe las cadenas de caracteres filtradas en un archivo temporal
        file_put_contents('temp/my-temp-1.css', $contents);

        system(
            $this->options['java-bin'] .
            ' -jar ' .
            $this->options['yui'] .
            ' --type css ' .
            'temp/my-temp-file-1.css ' .
            $this->options['extra-options'] .
            ' -o temp/my-temp-file-2.css'
        );

        // Devuelve el contenido del archivo
        return file_get_contents('temp/my-temp-file-2.css');
    }
}

Usage:

<?php

// Obtiene alguna colección CSS 
$css = $this->assets->get('head');

// Agrega/Habilita el filtro reductor YUI en la colección
$css->addFilter(
    new CssYUICompressor(
        [
            'java-bin'      => '/usr/local/bin/java',
            'yui'           => '/some/path/yuicompressor-x.y.z.jar',
            'extra-options' => '--charset utf8',
        ]
    )
);

In a previous example, we used a custom filter called LicenseStamper:

<?php

use Phalcon\Assets\FilterInterface;

/**
 * Agrega un mensaje de licencia  al principio del archivo
 *
 * @param string $contents
 *
 * @return string
 */
class LicenseStamper implements FilterInterface
{
    /**
     * Hace el filtrado
     *
     * @param string $contents
     * @return string
     */
    public function filter($contents)
    {
        $license = '/* (c) 2015 Your Name Here */';

        return $license . PHP_EOL . PHP_EOL . $contents;
    }
}

Custom Output

The outputJs() and outputCss() methods are available to generate the necessary HTML code according to each type of resources. You can override this method or print the resources manually in the following way:

<?php

use Phalcon\Tag;

$jsCollection = $this->assets->collection('js');

foreach ($jsCollection as $resource) {
    echo Tag::javascriptInclude(
        $resource->getPath()
    );
}

Improving performance

There are many ways to optimize the processing resources. We’ll describe a simple method below which allows to handle resourses directly through web server to optimize the response time.

First we need to set up the Assets Manager. We’ll use base controller, but you can use the service provider or any other place:

<?php

namespace App\Controllers;

use Phalcon\Mvc\Controller;
use Phalcon\Assets\Filters\Jsmin;

/**
 * App\Controllers\ControllerBase
 *
 * Este es el controlador base para todos los controladores en la aplicación.
 */
class ControllerBase extends Controller
{
    public function onConstruct()
    {
        $this->assets
            ->useImplicitOutput(false)
            ->collection('global')
            ->addJs('https://code.jquery.com/jquery-4.0.1.js', false, true)
            ->addFilter(new Jsmin());
    }
}

Then we have to configure the routing:

<?php
/*
 * Define rutas personalizadas.
 * Este archivo se incluye en la definición del servicio de enrutado (router service).
 */
$router = new Phalcon\Mvc\Router();

$router->addGet('/assets/(css|js)/([\w.-]+)\.(css|js)', [
    'controller' => 'assets',
    'action'     => 'serve',
    'type'       => 1,
    'collection' => 2,
    'extension'  => 3,
]);

// Otras rutas...

Finally, we need to create a controller to handle resource requests:

<?php

namespace App\Controllers;

use Phalcon\Http\Response;

/**
 * Provee activos del sitio.
 */
class AssetsController extends ControllerBase
{
    public function serveAction() : Response
    {
        // Se crea una instancia de Response
        $response = new Response();

        // Se prepara un ruta para salida (output)
        $collectionName = $this->dispatcher->getParam('collection');
        $extension      = $this->dispatcher->getParam('extension');
        $type           = $this->dispatcher->getParam('type');
        $targetPath     = "assets/{$type}/{$collectionName}.{$extension}";

        // Se define el tipo de contenido
        $contentType = $type == 'js' ? 'application/javascript' : 'text/css';
        $response->setContentType($contentType, 'UTF-8');

        // Verifica la exitencia de la colección
        if (!$this->assets->exists($collectionName)) {
            return $response->setStatusCode(404, 'Not Found');
        }

        // Define la Colección de Activos (Assets Collection)
        $collection = $this->assets
            ->collection($collectionName)
            ->setTargetUri($targetPath)
            ->setTargetPath($targetPath);

        // Almacena el contenido en disco y devuelve una ruta validada
        $contentPath = $this->assets->output($collection, function (array $parameters) {
            return BASE_PATH . '/public/' . $parameters[0];
        }, $type);

        // Establece el contenido de la respuesta
        $response->setContent(file_get_contents($contentPath));

        // Devuelve la respuesta
        return $response;
    }
}

If precompiled resources exist in the file system they must be served directly by web server. So to get the benefit of static resources we have to update our server configuration. We will use an example configuration for Nginx. For Apache it will be a little different:

location ~ ^/assets/ {
    expires 1y;
    add_header Cache-Control public;
    add_header ETag "";

    # Si existe el archivo como un archivo estático, entregar directamente sin 
    # ejecutar todas las otras pruebas de reescritura en el
    try_files $uri $uri/ @phalcon;
}

location / {
    try_files $uri $uri/ @phalcon;
}

location @phalcon {
    rewrite ^(.*)$ /index.php?_url=$1;
}

# Otras configuraciones

We need to create assets/js and assets/css directories in the document root of the application (eg. public).

Every time when the user requests resources using address of type /assets/js/global.js the request will be redirected to AssetsController in case this file is absent in the filesystem. Otherwise the resource will be handled by the web server.

It isn’t the best example. However, it reflects the main idea: the reasonable configuration of a web server with an application can help optimize response time multifold.

Learn more about the Web Server Setup and Routing in their dedicated articles Web Server Setup and Routing.