Assets Management¶
Overview¶
Phalcon\Assets
is a component that facilitates the management of static assets, such as CSS stylesheets or JavaScript libraries, in a web application.
Phalcon\Assets\Manager is the key component for registering and utilizing assets in your application. If you are using the Phalcon\Di\FactoryDefault container, the Assets Manager is already registered and can be accessed using the assets
key from your Di container.
<?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 are using the Phalcon\Di\FactoryDefault, the Phalcon\Html\TagFactory is already registered as a service with the name tag
and automatically injected into the constructor of Phalcon\Assets\Manager. This ensures object reuse and minimal memory usage. If you register the Phalcon\Assets\Manager yourself and already have Phalcon\Html\TagFactory in your container, you can reuse it without creating a new instance.
Assets¶
Assets are added to the manager or a collection using the Asset-related classes. The Phalcon\Assets\Asset class is fundamental. It accepts the necessary data to create an asset.
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
, calculated as:
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
);
Inline¶
There are times when the application needs generated CSS or JS to be injected into the view. 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
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
.
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
.
Custom¶
Implementing the Phalcon\Assets\AssetInterface enables you to create different asset classes that can be handled by the Assets Manager.
Exception¶
Any exceptions thrown in the Assets Manager component will be of type Phalcon\Assets\Exception. You can use this exception to selectively catch exceptions thrown only from this component.
<?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();
}
}
}
Adding Assets¶
Files¶
Phalcon\Assets\Manager supports two built-in assets: CSS and JavaScript assets. You can also create other asset types if you need to. The assets manager internally stores two default collections of assets - one for JavaScript and another for CSS.
You can easily add assets to these collections:
<?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');
}
}
For better page load performance, it is recommended to place JavaScript links at the end of the HTML instead of in the <head>
element. However, this might not be always feasible based on the Javascript files you need to load and their dependencies.
You can also add assets to the manager by using Asset objects:
<?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);
}
}
Inline¶
You can also add inline assets to the manager. Inline assets represent strings of CSS or JS that need to be injected in your views dynamically (not from an asset file). addInlineCode()
, addInlineCodeByType()
, addInlineCss()
and addInlineJs()
are available for your use.
<?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)
;
Local/Remote Assets¶
Local assets are those provided by the same application, located in a public location (usually public
). The URLs for local assets are generated using the url service.
Remote assets are those like common libraries such as jQuery, Bootstrap, etc., provided by a CDN.
The second parameter of addCss()
and addJs()
signifies whether the asset is local or not (true
is local, false
is remote). By default, the assets manager will assume the asset is 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');
}
Collections¶
Phalcon\Assets\Collections are objects that group assets of the same type. The assets manager implicitly creates two collections: css
and js
. You can create additional collections to group specific assets to make it easier to place those assets in the views:
<?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');
Get¶
The getter methods exposed by the component allow you to retrieve the collection from anywhere in your code and manipulate it according to your needs. The manager offers get()
, getCollection()
, getCss()
, and getJs()
. These methods will return the collection stored by the manager. The getCss() and getJs() methods return the built-in, pre-registered collections.
The collection()
method acts as a creator and getter at the same time. It allows you to create a collection and retrieve it so that you can then add assets to it. The getCss()
and getJs()
methods perform the same function, creating the collection if it does not exist and returning it. These two collections set the predefined css
and js
collections in the manager.
<?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
Set¶
If the built-in css
and js
collections are not sufficient for your needs, you can attach a new collection to the manager by using set()
.
<?php
use Phalcon\Assets\Collection;
$collection = new Collection();
$this->assets->set('outputJs', $collection);
URL Prefixes¶
Collections can be URL-prefixed, allowing you to change the prefix easily based on the needs of your application. 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');
You can also chain the method calls if that syntax is preferable:
<?php
$headerCollection = $this
->assets
->collection('header')
->setPrefix('https://cdn.example.com/')
->setLocal(false)
->addJs('js/jquery.js')
->addJs('js/bootstrap.min.js');
Built-In Filters¶
Assets can be filtered, i.e., manipulated before their output to the view. Although Phalcon v3 offered minifiers for JavaScript and CSS, license limitations do not allow us to continue using those libraries. For v5, we offer 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.
Custom Filters¶
Creating custom filters is very easy. 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');
}
}
Usage:
<?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',
]
)
);
In a previous example, we used a custom filter called LicenseStamper
, which adds the license message at the top of the file:
<?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;
}
}
Output¶
After all the assets have been added to their relevant collections you can use the output methods to print HTML in your views. These methods are output()
, outputCss()
, outputJs()
, outputInline()
, outputInlineCss()
and outputInlineJs()
.
To output files:
<?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');
Then in the views:
<html>
<head>
<title>Some amazing website</title>
<?php $this->assets->outputJs('headerJs'); ?>
</head>
<body>
<!-- ... -->
<?php $this->assets->outputJs('footerJs'); ?>
</body>
<html>
Volt syntax:
<html>
<head>
<title>Some amazing website</title>
{{ assets.outputCss('header') }}
</head>
<body>
<!-- ... -->
{{ assets.outputJs('footer') }}
</body>
<html>
To output inline:
<?php
$css = '.spinner {color: blue; }';
$js = 'alert("hello")';
$assetCss = new Inline('css', $css};
$assetJs = new Inline('js', $js};
$this
->assets
->addInlineCss($css)
->addInlineJs($js)
;
Then in the views:
<html>
<head>
<title>Some amazing website</title>
</head>
<?php $this->assets->outputInlineCss(); ?>
<body>
<!-- ... -->
<?php $this->assets->outputInlineJs(); ?>
</body>
<html>
Volt syntax:
<html>
<head>
<title>Some amazing website</title>
{{ assets.outputInlineCss() }}
</head>
<body>
<!-- ... -->
{{ assets.outputInlineJs() }}
</body>
<html>
<html>
<head>
<title>Some amazing website</title>
<style>.spinner {color: blue; }</style>
</head>
<body>
<!-- ... -->
<script type="application/javascript">alert("hello")</script>
</body>
<html>
Custom Output¶
The outputJs()
and outputCss()
methods generate the necessary HTML code for each type of asset. You can override this method or print the assets manually, as shown below:
<?php
use Phalcon\Html\TagFactory;
$tagFactory = new TagFactory();
$jsCollection = $this->assets->collection('js');
foreach ($jsCollection as $asset) {
echo (string) $tagFactory
->script()
->add($asset->getPath())
;
}
Implicit Vs Explicit Output¶
There are times when you might need to implicitly output the manager's content. To achieve this, you can use the useImplicitOutput()
method. Calling output()
after that will echo the HTML on the screen.
Versioning¶
The Assets
component supports versioning (automatic or manual), also known as cache busting. Versioning ensures that the browsers are instructed to download the asset files again, receiving the latest CSS and JS code from the server.
To add a version number to your assets, include the version string while creating the asset object:
The output will include the version in the URL:
You can store the version in your configuration file or any other storage and update it when a new release is pushed to production.
Auto Versioning¶
You can also use the file time of the asset file to control the versioning of your assets:
<?php
use Phalcon\Assets\Asset\Css;
$asset = new Css(
'css/bootstrap.css',
true,
null,
[],
null,
true
);
NOTE
Using the auto version feature is not recommended for production environments due to unnecessary file system read operations.
Improving Performance¶
To optimize processing assets, one method is to allow your web server to handle the assets, improving response time. Here's how you can set up the Assets Manager:
Base Controller
<?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)
;
}
}
Routing
<?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,
]
);
// ...
AssetsController
<?php
namespace App\Controllers;
use Phalcon\Http\Response;
/**
* Serve site assets.
*/
class AssetsController extends ControllerBase
{
public function serveAction(): Response
{
// Getting a response instance
$response = new Response();
// Prepare output path
$collectionName = $this->dispatcher->getParam('collection');
$extension = $this->dispatcher->getParam('extension');
$type = $this->dispatcher->getParam('type');
$targetPath = "assets/{$type}/{$collectionName}.{$extension}";
// Setting up the content type
$contentType = $type == 'js' ? 'application/javascript' : 'text/css';
$response->setContentType($contentType, 'UTF-8');
// Check collection existence
if (!$this->assets->exists($collectionName)) {
return $response->setStatusCode(404, 'Not Found');
}
// Setting up the Assets Collection
$collection = $this->assets
->collection($collectionName)
->setTargetUri($targetPath)
->setTargetPath($targetPath);
// Store content to the disk and return a fully qualified file path
$contentPath = $this->assets->output(
$collection,
function (array $parameters) {
return BASE_PATH . '/public/' . $parameters[0];
},
$type
);
// Set the content of the response
$response->setContent(
file_get_contents($contentPath)
);
// Return the response
return $response;
}
}
If precompiled assets exist in the file system they must be served directly by the web server. So to get the benefit of static assets 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 "";
# 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;
}
Other Configuration Directives¶
We need to create assets/js
and assets/css
directories in the document root of the application (eg. public
).
Every time the application requests assets such as /assets/js/global.js
the application will check whether the asset exists. If yes, it will be handled by the web server. Alternatively, it will be redirected to the AssetsController
for handling from the application.
This method is not recommended for production environments and high-load applications. However, the example does show what is possible using this component. The implementation you choose depends on the needs of your application.
In most cases, your web server, CDN, or services such as Varnish HTTP Cache would be preferable.