01 Медиа-ресурсы
Управление ресурсами
Phalcon\Assets
— это компонент позволяющий разработчику управлять статичными ресурсами в веб-приложении, такими как каскадные таблицы стилей или скрипты.
Phalcon\Assets\Manager доступен в контейнере сервисов и вы можете добавлять ресурсы из любой части приложения, где контейнер доступен.
Добавление ресурсов
Поддерживаются ресурсы двух типов: CSS и JavaScript. Но при необходимости, можно реализовать поддержку любых других. Внутренний механизм менеджера ресурсов хранит две коллекции, одну для JavaScript, а другую для CSS.
Добавить ресурсы в эти коллекции очень просто:
<?php use Phalcon\Mvc\Controller; class IndexController extends Controller { public function indexAction() { // Добавляем некоторые локальные CSS ресурсы $this->assets->addCss('css/style.css'); $this->assets->addCss('css/index.css'); // А теперь некоторые локальные JavaScript ресурсы $this->assets->addJs('js/jquery.js'); $this->assets->addJs('js/bootstrap.min.js'); } }
Далее, добавленные ресурсы могут быть отображены в представлениях:
<html> <head> <title>Некоторый удивительный веб-сайт</title> <?php $this->assets->outputCss(); ?> </head> <body> <!-- ... --> <?php $this->assets->outputJs(); ?> </body> <html>
С использованием синтаксиса Volt:
<html> <head> <title>Некоторый удивительный веб-сайт</title> {{ assets.outputCss() }} </head> <body> <!-- ... --> {{ assets.outputJs() }} </body> <html>
В целях достижения лучшей производительности, рекомендуется размещать загрузку JavaScript-скриптов в конце страницы, а не в <head>
.
Локальные/удалённые ресурсы
Локальные ресурсы это те, которые предоставляются вами в том же приложении. Обычно они расположены в корневом каталоге приложения. Ссылки на локальные ресурсы генерируются с помощью сервиса url
, чаще с применением Phalcon\Mvc\Url.
Удалённые ресурсы, такие как общие библиотеки, например jQuery, Bootstrap или пр., предоставляемые посредством CDN.
Второй параметр методов addCss()
и addJs()
говорит является ли ресурс локальным или нет (если true
, то локальный, false
— удалённый). По умолчанию, менеджер ресурсов будет предполагать, что ресурс является локальным:
<?php public function indexAction() { // Добавляем некоторые удалённые CSS ресурсы $this->assets->addCss('//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css', false); // А теперь, некоторые локальные CSS ресурсы $this->assets->addCss('css/style.css', true); $this->assets->addCss('css/extra.css'); }
Коллекции
В коллекциях группируются однотипные ресурсы. Менеджер ресурсов всегда создает две коллекции: css
и js
. Для группирования специфичных ресурсов вы можете создавать дополнительные:
<?php // JavaScript ресурсы в коллекии header $headerCollection = $this->assets->collection('header'); $headerCollection->addJs('js/jquery.js'); $headerCollection->addJs('js/bootstrap.min.js'); // JavaScript ресурсы в коллекии footer $footerCollection = $this->assets->collection('footer'); $footerCollection->addJs('js/jquery.js'); $footerCollection->addJs('js/bootstrap.min.js');
Затем в представлении:
<html> <head> <title>Некоторый удивительный веб-сайт</title> <?php $this->assets->outputJs('header'); ?> </head> <body> <!-- ... --> <?php $this->assets->outputJs('footer'); ?> </body> <html>
Синтаксис шаблонизатора Volt:
<html> <head> <title>Некоторый удивительный веб-сайт</title> {{ assets.outputCss('header') }} </head> <body> <!-- ... --> {{ assets.outputJs('footer') }} </body> <html>
URL префиксы
К коллекциям могут применяться URL префиксы, это позволит в любой момент легко изменить расположение ресурсов с одного сервера на другой:
<?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');
Также, доступен синтаксис цепочки (Method chaining):
<?php $headerCollection = $assets ->collection('header') ->setPrefix('http://cdn.example.com/') ->setLocal(false) ->addJs('js/jquery.js') ->addJs('js/bootstrap.min.js');
Минификация/Фильтрация
Phalcon\Assets
предоставляет встроенную возможность минимизации JavaScript и CSS. Разработчик может создать коллекцию ресурсов, с указаниями для менеджера ресурсов, к каким ресурсам должны быть применены фильтры, а к каким нет. В дополнении к вышесказанному, Jsmin
Дугласа Крокфорда (Douglas Crockford) входит в состав ядра минимизации JavaScript для увеличения производительности. Для минимизации CSS используется CSSMin
Райна Дэйя (Ryan Day).
Следующий пример показывает, как минимизировать набор ресурсов:
<?php $manager // Этот JavaScript расположен внизу страницы ->collection('jsFooter') // Название получаемого файла ->setTargetPath('final.js') // С таким URI генерируется тэг HTML ->setTargetUri('production/final.js') // Это удалённый ресурс, не нуждающийся в фильтрации ->addJs('code.jquery.com/jquery-1.10.0.min.js', false, false) // Это локальные ресурсы, к которым необходимо применить фильтры ->addJs('common-functions.js') ->addJs('page-functions.js') // Объединяем все ресурсы в один файл ->join(true) // Используем встроенный фильтр Jsmin ->addFilter( new Phalcon\Assets\Filters\Jsmin() ) // Используем пользовательский фильтр ->addFilter( new MyApp\Assets\Filters\LicenseStamper() );
Коллекция может содержать либо JavaScript, либо CSS ресурсы, но не оба типа ресурсов сразу. Некоторые ресурсы могут быть удалёнными, то есть, полученными с помощью HTTP-запроса для дальнейшей фильтрации. Рекомендуется преобразовывать внешние ресурсы в локальные, для устранения накладных расходов на их получение.
Как было замечено выше, метод addJs()
, использующийся для добавления ресурсов в коллекцию, в качестве второго принимает параметр, указывающий является ли ресурс внешним или нет, а также третий параметр, указывающий, следует ли применять фильтр к ресурсу или оставить как есть:
<?php // Эти JavaScript ресурсы расположены внизу $jsFooterCollection = $manager->collection('jsFooter'); // Это удалённый ресурс, не нуждающийся в фильтрации $jsFooterCollection->addJs('code.jquery.com/jquery-1.10.0.min.js', false, false); // Это локальные ресурсы, к которым необходимо применить фильтры $jsFooterCollection->addJs('common-functions.js'); $jsFooterCollection->addJs('page-functions.js');
Фильтры регистрируются в коллекции. Допускается регистрировать несколько фильтров. Ресурсы в наборе фильтруются в том же порядке, в каком были зарегистрированы фильтры:
<?php // Используем встроенный фильтр Jsmin $jsFooterCollection->addFilter( new Phalcon\Assets\Filters\Jsmin() ); // Используем пользовательский фильтр $jsFooterCollection->addFilter( new MyApp\Assets\Filters\LicenseStamper() );
Обратите внимание, встроенные и пользовательские фильтры могут прозрачно применяться ко всей коллекции ресурсов. Последний шаг, определяет, стоит ли объединять все ресурсы набора в один файл, или использовать каждый по отдельности. Если все ресурсы набора должны объединяться в один файл, вы можете использовать метод join()
.
Если ресурсы должны быть объединены, то вы должны также определить какой файл будет использоваться для хранения ресурсов и по какому URI он будет доступен. Эти параметры настраиваются при помощи методов setTargetPath()
и setTargetUri()
:
<?php $jsFooterCollection->join(true); // Название получаемого файла $jsFooterCollection->setTargetPath('public/production/final.js'); // С таким URI генерируется тэг HTML $jsFooterCollection->setTargetUri('production/final.js');
Встроенные фильтры
Phalcon предоставляет два встроенных фильтра минимизации JavaScript и CSS ресурсов. Их реализация на языке Си обеспечивает минимальные накладные расходы для решения подобной задачи:
Фильтр | Описание |
---|---|
Phalcon\Assets\Filters\Jsmin | Минимизирует JavaScript удаляя не нужны символы, которые игнорируются интерпретатором/компилятором JavaScript |
Phalcon\Assets\Filters\Cssmin | Минимизирует CSS удаляя ненужные символы, которые игнорируются браузерами |
Пользовательские фильтры
Кроме использования встроенных фильтров, вы можете создавать свои собственные фильтры. Вы можете воспользоваться существующими более продвинутыми инструментами, такими как YUI, Sass, Closure и другие:
<?php use Phalcon\Assets\FilterInterface; /** * Фильтрация CSS содержимого при помощи YUI * * @param string $contents * @return string */ class CssYUICompressor implements FilterInterface { protected $options; /** * Конструктор CssYUICompressor * * @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'); } }
Применение:
<?php // Получаем некоторую CSS коллекцию $css = $this->assets->get('head'); // Добавляем (включаем) фильтр YUI-компрессор к коллекции $css->addFilter( new CssYUICompressor( [ 'java-bin' => '/usr/local/bin/java', 'yui' => '/some/path/yuicompressor-x.y.z.jar', 'extra-options' => '--charset utf8', ] ) );
В предыдущем примере мы использовали пользовательский фильтр, который назывался LicenseStamper
:
<?php use Phalcon\Assets\FilterInterface; /** * Добавляет лицензионное собщение в начало файла * * @param string $contents * * @return string */ class LicenseStamper implements FilterInterface { /** * Выполнение фильтрации * * @param string $contents * @return string */ public function filter($contents) { $license = '/* (c) 2015 Ваше имя здесь */'; return $license . PHP_EOL . PHP_EOL . $contents; } }
Пользовательский вывод
Методы outputJs()
и outputCss()
создают необходимые HTML-тэги в соответствии с каждым типом ресурсов. Однако вы можете переопределить эти методы или выводить ресурсы вручную, используя следующий подход:
<?php use Phalcon\Tag; $jsCollection = $this->assets->collection('js'); foreach ($jsCollection as $resource) { echo Tag::javascriptInclude( $resource->getPath() ); }
Повышение производительности
Существует множество способов оптимизации ресурсов обработки. Ниже мы опишем простой метод, который позволяет обрабатывать ресурсы напрямую через веб-сервер для оптимизации времени отклика.
Сначала нам нужно настроить менеджера активов. Мы будем использовать базовый контроллер, но вы можете использовать поставщика услуг или любое другое место:
<?php namespace App\Controllers; use Phalcon\Mvc\Controller; use Phalcon\Assets\Filters\Jsmin; /** * App\Controllers\ControllerBase * * Это базовый контроллер для приложения. */ class ControllerBase extends Controller { public function onConstruct() { $this->assets ->useImplicitOutput(false) ->collection('global') ->addJs('https://code.jquery.com/jquery-3.2.1.js', false, true) ->addFilter(new Jsmin()); } }
Затем, нам понадобится настроит маршрутизацию:
<?php /* * Определяем маршруты. * Этот файл будет подключен при определении сервиса router. */ $router = new Phalcon\Mvc\Router(); $router->addGet('/assets/(css|js)/([\w.-]+)\.(css|js)', [ 'controller' => 'assets', 'action' => 'serve', 'type' => 1, 'collection' => 2, 'extension' => 3, ]); // Другие маршруты...
Наконец нам нужно создать контроллер для обработки запросов ресурсов:
<?php namespace App\Controllers; use Phalcon\Http\Response; /** * Обработчик запросов ресурсов. */ class AssetsController extends ControllerBase { public function serveAction() : Response { // Создаём экземпляр Response $response = new Response(); // Подготавливаем пути $collectionName = $this->dispatcher->getParam('collection'); $extension = $this->dispatcher->getParam('extension'); $type = $this->dispatcher->getParam('type'); $targetPath = "assets/{$type}/{$collectionName}.{$extension}"; // Настраиваем тип ответа $contentType = $type == 'js' ? 'application/javascript' : 'text/css'; $response->setContentType($contentType, 'UTF-8'); // Проверяем коллекцию на существование if (!$this->assets->exists($collectionName)) { return $response->setStatusCode(404, 'Not Found'); } // Настраиваем коллекцию ресурсов $collection = $this->assets ->collection($collectionName) ->setTargetUri($targetPath) ->setTargetPath($targetPath); // Сохраняем содержимое на диск и возвращаем полный путь к сохранённому файлу $contentPath = $this->assets->output($collection, function (array $parameters) { return BASE_PATH . '/public/' . $parameters[0]; }, $type); // Устанавливаем содержимое ответа $response->setContent(file_get_contents($contentPath)); // Возвращаем объект Response return $response; } }
сли обработанные ресурсы существуют на диске, они должны быть возвращены непосредственно веб-сервером. Таким образом, чтобы получить выгоду от работы со статикой, мы должны обновить конфигурацию веб-сервера. В примере ниже мы будем использовать конфигурацию для Nginx. Настройка других веб-серверов, например Apache, будет немного отличаться:
location ~ ^/assets/ { expires 1y; add_header Cache-Control public; add_header ETag ""; # Если статический ресур существует, обработать его веб-сервером, # без запуска PHP-приложения try_files $uri $uri/ @phalcon; } location / { try_files $uri $uri/ @phalcon; } location @phalcon { rewrite ^(.*)$ /index.php?_url=$1; }
Нам нужно создать assets/js
и assets/css
каталоги в корне документа приложения (например. public
).
Каждый раз, когда пользователь запрашивает статический ресурс вида /assets/js/global.js
, запрос будет послан на обработку контроллером AssetsController
, если файла ещё нет в файловой системе. В противном случае, ресурс будет возвращён веб-сервером.
Это не самый лучший пример. Тем не менее, это отражает основную идею: разумная конфигурация веб-сервера с приложением может помочь оптимизировать время отклика многократно.
Узнать больше о конфигурировании веб-сервера и маршрутизации можно в соответствующих статьях Настройка веб-сервера и Маршрутизация.