Secciones

Gestión de Recursos (Assets)


Resumen

Phalcon\Assets es un componente que le permite gestionar recursos estáticos como hojas de estilo CSS o librerías JavaScript en una aplicación web.

Phalcon\Assets\Manager is the component you can use to register your assets and use them throughout your application. If you are using the Phalcon\Di\FactoryDefault container, the Assets Manager is already registered for you. Puede acceder a él usando la clave assets desde su contenedor Di.

<?php

use Phalcon\Di\FactoryDefault();

$container = new FactoryDefault();
$manager   = $container->get('assets')

Alternatively, you can register the Phalcon\Assets\Manager in your Phalcon\Di\Di:

<?php

use Phalcon\Assets\Manager;
use Phalcon\Di\Di();
use Phalcon\Html\TagFactory();

$container  = new Di();
$tagFactory = new TagFactory();

$container->set(
    'assets',
    function () use ($tagFactory) {
        return new Manager($tagFactory);
    }
)

If you do use the Phalcon\Di\FactoryDefault, the Phalcon\Html\TagFactory is already registered as a service with the name tag and automatically injected in the constructor of Phalcon\Assets\Manager. This is to ensure that objects are reused and memory usage is kept to a minimum. If you are registering the Phalcon\Assets\Manager on your own, and you already have the Phalcon\Html\TagFactory registered in your container, you can reuse it without creating a new instance.

Recursos Activos

Los recursos se pueden añadir al gestor o una colección usando las clases relativas a Asset. The Phalcon\Assets\Asset class. El objeto acepta los datos necesarios para crear el recurso.

type

can be css, js or something else, depending on whether you want to extend the functionality of the component.

path

the path of the asset

local

whether this is a local asset or not

filter

any filter attached to this asset

attributes

attributes relative to the asset

version

version of the asset

autoVersion

let the component auto version this asset or not

Each asset has a unique key assigned to it. The key is computed using sha256 and it is calculated as:

$this->getType() . ":" . $this->getPath()

This ensures uniqueness and does not duplicate assets in the asset manager.

<?php

use Phalcon\Assets\Asset;

$asset = new Asset(
    'css',
    'css/bootstrap.css',
    true,
    null,
    [],
    '1.0',
    true
);

CSS

You can also use the Phalcon\Assets\Asset\Css class to create a CSS asset. This class is a helper class that extends the Phalcon\Assets\Asset class and internally sets the first parameter to css.

<?php

use Phalcon\Assets\Asset\Css;

$asset = new Css(
    'css/bootstrap.css',
    true,
    null,
    [],
    '1.0',
    true
);

JS

You can also use the Phalcon\Assets\Asset\Js class to create a JS asset. This class is a helper class that extends the Phalcon\Assets\Asset class and internally sets the first parameter to js.

<?php

use Phalcon\Assets\Asset\Js;

$asset = new Js(
    'js/bootstrap.js',
    true,
    null,
    [],
    '1.0',
    true
);

En Línea

Hay veces que la aplicación necesita generar CSS o JS para ser inyectado en la vista. You can use the Phalcon\Assets\Inline class to generate this content. The object can be created with the following parameters:

type

can be css, js or something else, depending on whether you want to extend the functionality of the component.

content

the content to be injected

filter

any filter attached to this asset

attributes

attributes relative to the asset

<?php

use Phalcon\Assets\Inline;

$asset = new Inline(
    'css',
    '.spinner {color: blue; }'
);

CSS

You can also use the Phalcon\Assets\Inline\Css class to create an inline CSS asset. This class is a helper class that extends the Phalcon\Assets\Inline class and internally sets the first parameter to css.

<?php

use Phalcon\Assets\Inline\Css;

$asset = new Css(
    '.spinner {color: blue; }'
);

JS

You can also use the Phalcon\Assets\Inline\Js class to create an inline JS asset. This class is a helper class that extends the Phalcon\Assets\Inline class and internally sets the first parameter to js.

<?php

use Phalcon\Assets\Asset\Js;

$asset = new Js(
    'alert("hello");'
);

Personalizado

Implementing the Phalcon\Assets\AssetInterface enables you to create different asset classes that can be handled by the Asset Manager.

Excepción

Any exceptions thrown in the Assets Manager component will be of type Phalcon\Assets\Exception. Puede usar esta excepción para capturar selectivamente sólo las excepciones lanzadas desde este componente.

<?php

use Phalcon\Assets\Exception;
use Phalcon\Mvc\Controller;

class IndexController extends Controller
{
    public function index()
    {
        try {
            $this->assets->addCss('css/style.css');
            $this->assets->addCss('css/index.css');
        } catch (Exception $ex) {
            echo $ex->getMessage();
        }
    }
}

Añadir Recursos

Archivos

Phalcon\Assets\Manager supports two built-in assets: CSS and JavaScript assets. También puede crear otros tipos de recurso si lo necesita. El gestor de recursos almacena internamente dos colecciones de recursos por defecto - una para JavaScript y otra para CSS.

Fácilmente puede añadir recursos a estas colecciones:

<?php

use Phalcon\Mvc\Controller;

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

        $this->assets->addJs('js/jquery.js');
        $this->assets->addJs('js/bootstrap.min.js');
    }
}

Para un mejor rendimiento en la carga de la página, se recomienda colocar enlaces JavaScript al final del HTML en vez de en el elemento <head>. However, this might not be always feasible based on the Javascript files you need to load and their dependencies.

También puede añadir recursos al gestor usando objetos Asset:

<?php

use Phalcon\Assets\Asset\Css;
use Phalcon\Assets\Asset\Js;
use Phalcon\Mvc\Controller;

class IndexController extends Controller
{
    public function index()
    {
        $css1 = new Css('css/style.css');
        $css2 = new Css('css/index.css');

        $this->assets->addAsset($css1);
        $this->assets->addAssetByType('css', $css2);

        $js1 = new Js('js/jquery.js');
        $js2 = new Js('js/bootstrap.min.js');

        $this->assets->addAsset($js1);
        $this->assets->addAssetByType('js', $js2);
    }
}

En Línea

También puede añadir recursos en línea al gestor. Los recursos en línea representan cadenas de CSS o JS que necesitan ser inyectadas en sus vistas dinámicamente (no desde un fichero de recursos). addInlineCode(), addInlineCodeByType(), addInlineCss() y addInlineJs() están disponibles para su uso.

<?php

use Phalcon\Assets\Manager;
use Phalcon\Assets\Inline;

$css      = '.spinner {color: blue; }';
$js       = 'alert("hello")';
$manager  = new Manager();
$assetCss = new Inline('css', $css};
$assetJs  = new Inline('js', $js};

$manager
    ->addInlineCode($assetCss)
    ->addInlineCode($assetJs)
;

$manager
    ->addInlineByType('css', $assetCss)
    ->addInlineByType('js', $assetJs)
;

$manager
    ->addInlineCss($css)
    ->addInlineJs($js)
;

Recursos Locales/Remotos

Local assets are those who are provided by the same application, and they are located in a public location (usually public). The URLs for local assets are generated using the url service.

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

El segundo parámetro de addCss() y addJs() significa si el recurso es local o no (true es local, false es remoto). Por defecto, el gestor de recursos asumirá que el recurso es local:

<?php

public function indexAction()
{
    $this->assets->addCss(
        '//cdn.assets.com/bootstrap/4/css/library.min.css', 
        false
    );

    $this->assets->addCss('css/style.css', true);
    $this->assets->addCss('css/extra.css');
}

Colecciones

Phalcon\Assets\Collections are objects that group assets of the same type. El gestor de recursos implícitamente crea dos colecciones: css y js. Puede crear colecciones adicionales para agrupar recursos específicos y facilitar la colocación de esos recursos en las vistas:

<?php

// Javascript - <head>
$headerCollection = $this->assets->collection('headerJs');

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

// Javascript - <footer>
$footerCollection = $this->assets->collection('footerJs');

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

Obtener

The getter methods exposed by the component, allow you to get the collection from anywhere in your code and manipulate it according to your needs. El gestor ofrece get(), getCollection(), getCss() y getJs(). These methods will return the collection that the manager stores. getCss() y getJs() devolverán las colecciones incorporadas preregistradas.

The collection() method acts as a creator and getter at the same time. Le permite crear una colección y obtenerla para que le puedas añadir recursos. getCss() y getJs() realizan la misma función, es decir, crean la colección si no existe y la devuelven. Estas dos colecciones establecen las colecciones predefinidas css y js en el gestor.

<?php

$headerCollection = $this->assets->collection('headerJs');

$headerCollection = $this->assets->get('headerJs');

Has

The has() method allows you to check if a particular collection exists in the manager;

<?php

$headerCollection = $this->assets->collection('headerJs');

echo $this->assets->has('headerJs'); // true

Establecer

Si las colecciones incorporadas css y js no son suficientes para sus necesidades, puede adjuntar una nueva colección al gestor usando set().

<?php

use Phalcon\Assets\Collection;

$collection = new Collection();

$this->assets->set('outputJs', $collection);

Prefijos de URL

Las colecciones pueden ser prefijadas por URL, permitiéndole cambiar el prefijo fácilmente basado en las necesidades de su aplicación. An example of this can be changing from local to production environments and using a different CDN URL for your assets:

<?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');

También puede encadenar las llamadas a métodos si se prefiere esa sintaxis:

<?php

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

Filtros Incorporados

Los recursos se pueden filtrar, es decir, manipular antes de su salida a la vista. Aunque Phalcon v3 ofrecía minificadores para JavaScript y CSS, limitaciones en la licencia no nos permiten continuar usando estas librerías. For v5 we are offering only the Phalcon\Assets\Filters\None filter (which does not change the asset contents) and the Phalcon\Assets\FilterInterface interface, offering the ability to create custom filters.

Filtros Personalizados

Crear filtros personalizados es muy fácil. You can use this extensibility to take advantage of existing and more advanced filtering/minification tools like YUI, Sass, Closure, etc.:

<?php

use Phalcon\Assets\FilterInterface;

/**
 * Filters CSS content using 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;
    }

    /**
     * @param string $contents
     *
     * @return string
     */
    public function filter($contents)
    {
        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'
        );

        return file_get_contents('temp/my-temp-file-2.css');
    }
}

Uso:

<?php

$css = $this->assets->get('head');

$css->addFilter(
    new CssYUICompressor(
        [
            'java-bin'      => '/usr/local/bin/java',
            'yui'           => '/some/path/yuicompressor-x.y.z.jar',
            'extra-options' => '--charset utf8',
        ]
    )
);

En el ejemplo anterior, usamos un filtro personalizado llamado LicenseStamper, que añade el mensaje de licencia al principio del fichero:

<?php

use Phalcon\Assets\FilterInterface;

/**
 * @param string $contents
 *
 * @return string
 */
class LicenseStamper implements FilterInterface
{
    /**
     * Do the filtering
     *
     * @param string $contents
     * @return string
     */
    public function filter($contents)
    {
        $license = '/* (c) 2019 Your Name Here */';

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

Salida

After all the assets have been added to their relevant collections you can use the output methods to print HTML in your views. Estos métodos son output(), outputCss(), outputJs(), outputInline(), outputInlineCss() y outputInlineJs().

Para mostrar ficheros:

<?php

// Javascript - <head>
$headerCollection = $this->assets->collection('headerJs');

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

// Javascript - <footer>
$footerCollection = $this->assets->collection('footerJs');

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

Entonces en las vistas:

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

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

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

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

Sintaxis Volt:

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

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

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

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

Para mostrar en línea:

<?php

$css      = '.spinner {color: blue; }';
$js       = 'alert("hello")';
$assetCss = new Inline('css', $css};
$assetJs  = new Inline('js', $js};

$this
    ->assets
    ->addInlineCss($css)
    ->addInlineJs($js)
;

Entonces en las vistas:

<html>
    <head>
        <title>Some amazing website</title>
    </head>
    <?php $this->assets->outputInlineCss(); ?>
    <body>

        <!-- ... -->

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

Sintaxis Volt:

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

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

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

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

Las líneas anteriores se traducirán a:

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

        <style>.spinner {color: blue; }</style>
    </head>

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

        <script type="application/javascript">alert("hello")</script>
    </body>
<html>

Salida Personalizada

Los métodos outputJs() y outputCss() están disponibles para generar el código HTML necesario de acuerdo a cada tipo de recurso. Puede sobreescribir este método o imprimir manualmente los recursos de la siguiente manera:

<?php

use Phalcon\Html\TagFactory;

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

foreach ($jsCollection as $asset) {
    echo (string) $tagFactory
        ->script()
        ->add($asset->getPath())
    ;
}

Salida Implícita Vs Explícita

Hay veces que podría necesitar mostrar implícitamente la salida del gestor. Para hacer esto, puede usar el métodouseImplicitOutput(). Calling output() after that will echo the HTML on the screen.

<?php

$this
    ->assets
    ->useImplicitOutput(true)
    ->addCss('css/style.css')
    ->output()
;

Versionado

Los componentes Assets también soportan versionado (automático o manual). Versioning of assets is also known as cache busting. En resumen, los ficheros CSS y JS se pueden cachear fácilmente a nivel de navegador. Como tal, cualquier actualización que se suba al sistema de producción con una versión, podría incluir ficheros CSS y JS actualizados. Ya que los navegadores cachean esos recursos, el contenido actualizado no se entregará al navegador del usuario inmediatamente, resultando en una potencial pérdida de funcionalidad. Versionando los recursos, nos aseguramos de que los navegadores son instruidos para descargar de nuevo los ficheros de recursos y por tanto recibir el último código CSS y JS desde el servidor.

Para añadir un número de versión a sus recursos, necesita añadir la cadena de versión mientras crea el objeto de recurso:

<?php

use Phalcon\Assets\Asset\Css;

$asset = new Css(
    'css/bootstrap.css',
    true,
    null,
    [],
    '1.0'
);

Lo anterior resulta en el siguiente script como salida:

<link rel="stylesheet" href="css/bootstrap.css?ver=1.0"

Entonces puede almacenar la versión en su fichero de configuración y en cualquier otro almacenamiento y actualizarla cuando se publique una nueva versión en producción.

Autoversionado

También puede usar la fecha del fichero del recurso para controlar el versionado de sus recursos.

<?php

use Phalcon\Assets\Asset\Css;

$asset = new Css(
    'css/bootstrap.css',
    true,
    null,
    [],
    null,
    true
);

The above will result in the following script as the output (assuming that your file was modified in May 20th 2019): Assuming that your file was last modified on May 20, the version

<link rel="stylesheet" href="css/bootstrap.css?ver=1558392141">

NOTE Using the auto version feature is not recommended for production environments, since Phalcon will need to read the modification time of the asset file for every request. Esto resultará en operaciones de lectura innecesarias en el sistema de ficheros.

Mejorar el Rendimiento

Hay muchas formas de optimizar el procesamiento de los recursos. Un método es permitir al servidor web gestionar los recursos, mejorando así el tiempo de respuesta. Primero necesitamos configurar el Gestor de Recursos. Usaremos un controlador base, pero puede usar el gestor donde lo necesite, accediendo a él desde el contenedor Di:

<?php

namespace App\Controllers;

use Phalcon\Mvc\Controller;

/**
 * App\Controllers\ControllerBase
 *
 * This is the base controller for all controllers in the application.
 */
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)
        ;
    }
}

Necesitamos configurar el enrutamiento:

<?php

/**
 * Define custom routes.
 * This file gets included in the router service definition.
 */
$router = new Phalcon\Mvc\Router();

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

// ...

Finalmente, necesitamos crear un controlador para gestionar las peticiones de recursos:

<?php

namespace App\Controllers;

use Phalcon\Http\Response;

/**
 * Serve site assets.
 */
class AssetsController extends ControllerBase
{
    public function serveAction(): Response
    {
        // #01
        $response = new Response();

        // #02
        $collectionName = $this->dispatcher->getParam('collection');
        $extension      = $this->dispatcher->getParam('extension');
        $type           = $this->dispatcher->getParam('type');
        $targetPath     = "assets/{$type}/{$collectionName}.{$extension}";

        // #03
        $contentType = $type == 'js' ? 'application/javascript' : 'text/css';
        $response->setContentType($contentType, 'UTF-8');

        // #04
        if (!$this->assets->exists($collectionName)) {
            return $response->setStatusCode(404, 'Not Found');
        }

        // #05
        $collection = $this->assets
            ->collection($collectionName)
            ->setTargetUri($targetPath)
            ->setTargetPath($targetPath);

        // #06
        $contentPath = $this->assets->output(
            $collection,
            function (array $parameters) {
                return BASE_PATH . '/public/' . $parameters[0];
            },
            $type
        );

        // #07
        $response->setContent(
            file_get_contents($contentPath)
        );

        // #08
        return $response;
    }
}

Legend

  1. Getting a response instance

  2. Prepare output path

  3. Setting up the content type

  4. Check collection existence

  5. Setting up the Assets Collection

  6. Store content to the disk and return fully qualified file path

  7. Set the content of the response

  8. Return the response

Si existen recursos precompilados en el sistema de ficheros deben ser servidos directamente por el servidor web. Así que para obtener beneficios de los recursos estáticos debemos actualizar la configuración de nuestro servidor. Usaremos un ejemplo de configuración para Nginx. For Apache, it will be a little different:

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

    # If the file exists as a static file serve it directly without
    # running all the other rewrite tests on it
    try_files $uri $uri/ @phalcon;
}

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

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

Otras Directivas de Configuración

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

Cada vez que la aplicación solicite recursos como /assets/js/global.js la aplicación comprobará si el recurso existe. En caso afirmativo, será gestionado por el servidor web. Alternativamente será redirigido a AssetsController para la gestión desde la aplicación.

No recomendamos el uso del ejemplo anterior en entornos de producción y aplicaciones de alta carga. Sin embargo, el ejemplo muestra lo que es posible hacer usando este componente. La implementación que elija depende de las necesidades de su aplicación.

In most cases, your web server, CDN or services such as Varnish HTTP Cache would be more preferable.