![]() |
|
Practical symfonyДень 22: Кэш |
|
You are currently reading "Practical symfony" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.

|
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. |
Сегодня мы поговорим о кэшировании. У Symfony есть много встроенных стратегий кэширования. Например, конфигурационные YAML-файлы сначала конвертируются в PHP и затем кэшируются в файловой системе. Мы так же видели, что модули, созданные с помощью генератора админки, кэшируются для улучшения производительности.
Но сегодня мы будем говорить о другом кэше: кэш HTML. Для улучшения производительности Вашего веб-сайта, вы можете кэшировать целые страницы HTML или только их части.
По умолчанию функциональность кэширования шаблонов Symfony включена в
конфигурационном файле settings.yml для окружения prod, но не для
test и dev:
prod:
.settings:
cache: on
dev:
.settings:
cache: off
test:
.settings:
cache: off
Поскольку нам нужно протестировать функциональность кэша перед выпуском, мы можем
активировать кэш для окружения dev или создать новое окружение.
Напомним, что окружение определяется его именем (строка), связанным фронт-контроллером
и опционально набором специальных значений конфигурации.
Для того, чтобы попробовать систему кэширования в Jobeet, мы создадим окружение cache,
подобное окружению prod, но с журналированием и информацией для отладки, доступной
в окружении dev.
Создайте контроллер, связанный с новым окружением cache скопировав dev контроллер
web/frontend_dev.php в web/frontend_cache.php:
// web/frontend_cache.php if (!in_array(@$_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'))) { die('You are not allowed to access this file. Check '.basename(__FILE__).' for more information.'); } require_once(dirname(__FILE__).'/../config/ProjectConfiguration.class.php'); $configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'cache', true); sfContext::createInstance($configuration)->dispatch();
Это все, что нужно сделать. Теперь мы можем использовать новое окружение cache. Единственное
отличие - это второй аргумент для метода getApplicationConfiguration(),
где указывается имя окружения - cache.
Вы можете протестировать окружение cache в вашем браузере с помощью вызова его контроллера:
http://jobeet.localhost/frontend_cache.php/
Скрипт контроллера начинается с кода, который проверяет, что он вызван с локального IP-адреса. Это меры безопасности для защиты от вызова контроллера на промышленных серверах. Мы поговорим об этом более детально в завтрашнем уроке.
Сейчас окружение cache наследует конфигурацию по умолчаюнию. Измените
конфигурационный файл settings.yml для добавления специфичной конфигурации для окружения cache:
# apps/frontend/config/settings.yml
cache:
.settings:
error_reporting: <?php echo (E_ALL | E_STRICT)."\n" ?>
web_debug: on
cache: on
etag: off
В этих настройках, была включена функциональность кэша шаблонов с помощью
настройки cache и веб-панель отладки - с помощью настройки web_debug.
Поскольку мы также хотим журналировать SQL-запросы, нам нужно изменить конфигурацию
базы данных. Измените databases.yml и добавьте следующую конфигурацию в начале файла:
# config/databases.yml
cache:
propel:
class: sfPropelDatabase
param:
classname: DebugPDO
Поскольку конфигурация по умолчанию сохраняет все настройки в кэше, Вам нужно очистить его перед тем, как Вы увидите изменения в Вашем браузере:
$ php symfony cc
Теперь, если Вы обновите страницу в Вашем браузере, должна появиться веб-панель отладки в
верхнем-правом углу страницы, как это было для окружения dev.
Кэш шаблонов Symfony может быть настроен в конфигурационном файле cache.yml.
Конфигурацию по умолчанию для приложения можно найти в apps/frontend/config/cache.yml:
default: enabled: off with_layout: false lifetime: 86400
По умолчанию, поскольку все страницы могут содержать динамическую информацию,
кэш глобально отключен (enabled: off). Нам не нужно менять эту настройку,
поскольку мы будем включать кэш на постраничной базе.
Настройка lifetime определяет время жизни кэша в секундах
(86400 соответствует одним суткам) на стороне сервера.
Вы можете это сделать другим способом: включить кэш глобально и затем отключить его на специальных страницах, которые не могут быть кэшированы. Это зависит от того где меньше работы для Вашего приложения.
Поскольку домашняя страница Jobeet возможно будет наиболее посещаемой страницей веб-сайта, вместо запроса данных из базы данных каждый раз, когда пользователь приходит на нее, она будет кэширована.
Создайте файл cache.yml для модуля sfJobeetJob:
# plugins/sfJobeetPlugin/modules/sfJobeetJob/config/cache.yml index: enabled: on with_layout: true
Конфигурационный файл
cache.ymlимеет те же свойства что и любой другой файл конфигурации как напримерview.yml. Это означает что вы например можете включить кэш для всех действий модуля используя специальный ключall.
Если Вы обновите страницу в браузере, Вы увидите что Symfony украсил страницу рамкой показывая что содержимое было кэшировано:

Рамка дает некоторую ценную информацию о кэше для отладки, как например время жизни кэша и его возраст.
Если Вы обновите страницу еще раз, цвет рами будет изменен с зеленого на желтый, указывая на то что страница была получена из кэша:

Также заметьте, что не было сделано запросов к базе данных во втором случае, как показано на отладочной веб-панели.
Даже если язык будет изменен пользователем, кэш все еще работает, так как язык включен в URL.
Когда страница может быть кэширована, и если кэш еще не существует, Symfony сохраняет объект ответа в кэше в конце запроса. Для всех будущих запросов, Symfony будет отсылать кэшированый ответ без вызова контроллера:

Это сильно влияет на производительность и Вы можете самостоятельно измерить это, используя такие инструменты, как например JMeter.
Входящий запрос с параметрами
GETили отосланный с помощью методовPOST,PUT, илиDELETEне кэшируется Symfony, независимо от конфигурации.
Страница создания работы также может быть кэширована:
# plugins/sfJobeetPlugin/modules/sfJobeetJob/config/cache.yml new: enabled: on index: enabled: on all: with_layout: true
Поскольку две страницы могут быть кэшированы с шаблоном, мы создали секцию all,
которая определяет конфигурацию по умолчанию для всех действий модуля sfJobeetJob.
Если вы хотите очистить кэш страницы, вы можете использовать задачу cache:clear:
$ php symfony cc
Задача cache:clear очищает все кэши Symfony, сохраненные в главной папке
cache/. Она также принимает опции для выборочной очистки некоторых частей кэша.
Для очистки только кэша шаблонов для окружения cache, используйте опции
--type и --env:
$ php symfony cc --type=template --env=cache
Вместо очистки кэша каждый раз, когда вы делаете изменения, вы можете так же отключить кэш с помощью добавления любой строки запроса в URL, или используя кнопку "Игнорировать кэш" из отладочной веб-панели:

Иногда Вы не можете сохранить всю страницу в кэш, но шаблон действия сам по себе может быть кэширован. Попробуйте другой вариант, Вы можете кэшировать все, кроме шаблона.
Для приложения Jobeet, мы не можем кэшировать всю страницу из-за фрагмента "история вакансий".
Измените конфигурацию кэша для модуля job следующим образом:
# plugins/sfJobeetPlugin/modules/sfJobeetJob/config/cache.yml new: enabled: on index: enabled: on all: with_layout: false
Изменив настройку with_layout на false, Вы отключили кэширование шаблона.
Очистите кэш:
$ php symfony cc
Обновите страницу в Вашем браузере, чтобы увидеть отличия:

Несмотря на то, что поток выполнения запроса выглядит очень похоже на упрощенной диаграмме, кэширование без шаблона намного более ресурсоемко.

Для очень динамичных веб-сайтов иногда невозможно кэшировать весь шаблон действия. В этом случае, Вам нужно сконфигурировать кэш на более низком уровне. Фрагменты и компоненты также могут быть кэшированы.

Давайте кэшируем компонент language, создав файл cache.yml для модуля sfJobeetLanguage:
# plugins/sfJobeetPlugin/modules/sfJobeetLanguage/config/cache.yml _language: enabled: on
Конфигурирование кэша для фрагмента или компонента достигается простым добавлением записи
с его именем в файл конфигурации. Опция with_layout не берется в расчет для этого типа кэша, поскольку в
этом нет смысла:

Контекстное кэширование - когда использовать?
Тот же самый компонент или фрагмент может быть использован во многих разных шаблонах Фрагмент вакансий
_list.php, например, используется модулямиsfJobeetJobиsfJobeetCategory. Поскольку вывод будет всегда тот же самый, фрагмент не зависит от контекста, в котором он используется, и кэш тот же самый для всех шаблонов (кэш по-прежнему очевидно отличается для разных наборов параметров).Но иногда, вывод фрагмента или компонента отличается в зависимости от действия, в которое он включен (подумайте о панели блога например, которая немного отличается для домашней страницы и для страницы с постом в блоге). В этих случаях фрагмент или компонент контекстный, и кэш должен быть настроен соответственно с помощью установки опции
contextualвtrue:_sidebar: enabled: on contextual: true
Сохранение страницы создания вакансии в кэш проблематично, поскольку она содержит форму. Для лучшего понимания проблемы, сходите на страницу "Post a Job" в Вашем браузере для создания кэша. Затем очистите куки сессии, и попробуйте сохранить вакансию. Вы должны увидеть сообщение об ошибке - "CSRF атака":

Почему? Поскольку мы настроили секретный CSRF, когда создавали приложение frontend, Symfony вкладывает ключ CSRF во все формы. Для защиты Вашего приложения от CSRF атак, этот ключ уникален для данного пользователя и для данной формы.
Первый раз, когда страница отображается, сгенерированная HTML-форма сохраняется в кэш с ключом текущего пользователя. Когда следом заходит другой пользователь, будет отображена страница из кэша с CSRF-ключом первого пользователя. Когда форма будет отослана, ключ не совпадет и появится ошибка.
Как мы можем это исправить, поскольку кэширование формы выглядит разумным? Форма создания вакансии не зависит от пользователя, и она ничего не меняет для текущего пользователя. В этом случае CSRF-защита не нужна, и мы можем совсем удалить ключ CSRF:
// plugins/sfJobeetPlugin/lib/form/JobeetJobForm.class.php class JobeetJobForm extends BaseJobeetJobForm { public function __construct(BaseObject $object = null, $options = array(), $CSRFSecret = null) { parent::__construct($object, $options, false); } // ... }
После этого изменения, очистите кэш и попробуйте тот же самый сценарий, чтобы увидеть, что он теперь работает, как ожидалось.
Та же конфигурация должна быть применена для формы смены языка сайта, которая содержится в шаблоне
и будет сохранена в кэше. Поскольку использована sfLanguageForm по умолчанию,
вместо создания нового класса можно просто удалить CSRF-ключ, давайте сделаем это для
действия и компонента модуля sfJobeetLanguage:
// plugins/sfJobeetPlugin/modules/sfJobeetLanguage/actions/components.class.php class sfJobeetLanguageComponents extends sfComponents { public function executeLanguage(sfWebRequest $request) { $this->form = new sfFormLanguage($this->getUser(), array('languages' => array('en', 'fr'))); unset($this->form[$this->form->getCSRFFieldName()]); } } // plugins/sfJobeetPlugin/modules/sfJobeetLanguage/actions/actions.class.php class sfJobeetLanguageActions extends sfActions { public function executeChangeLanguage(sfWebRequest $request) { $form = new sfFormLanguage($this->getUser(), array('languages' => array('en', 'fr'))); unset($form[$form->getCSRFFieldName()]); // ... } }
Метод getCSRFFieldName() возвращает название поля, которое содержит CSRF-ключ.
При отключении этого поля, виджет и соответствующий валидатор удаляются.
Каждый раз, когда пользователь добавляет и активирует вакансию, должен быть обновлен список новых вакансий на домашней странице.
Поскольку нам не нужно, чтобы вакансии появлялись на домашней странице в реальном времени, лучшая стратегия - это уменьшить время жизни кэша:
# plugins/sfJobeetPlugin/modules/sfJobeetJob/config/cache.yml index: enabled: on lifetime: 600
В конфигурации по умолчанию вместо одного дня кэш для домашней страницы будет автоматически удаляться каждые десять минут.
Но если Вы хотите обновлять домашнюю страницу сразу после того, как пользователь активирует
новую вакансию, измените метод executePublish() модуля sfJobeetJob и добавьте ручную очистку кэша:
// plugins/sfJobeetPlugin/modules/sfJobeetJob/actions/actions.class.php public function executePublish(sfWebRequest $request) { $request->checkCSRFProtection(); $job = $this->getRoute()->getObject(); $job->publish(); if ($cache = $this->getContext()->getViewCacheManager()) { $cache->remove('sfJobeetJob/index?sf_culture=*'); $cache->remove('sfJobeetCategory/show?id='.$job->getJobeetCategory()->getId()); } $this->getUser()->setFlash('notice', sprintf('Your job is now online for %s days.', sfConfig::get('app_active_days'))); $this->redirect($this->generateUrl('job_show_user', $job)); }
Кэш управляется классом sfViewCacheManager. Метод remove() удаляет кэш,
связанный в внутренним URI. Для удаления кэша для всех возможных параметров
переменной, используйте * как значение. Мы использовали sf_culture=*
в коде выше, что означает, что Symfony будет удалять кэш для домашней
страницы и на английском, и на французском.
Поскольку менеджер кэша равен null, когда кэш отключен, мы заключили удаление кэша в
блок if.
Класс
sfContext.Объект класса
sfContextсодержит ссылки на объекты ядра Symfony, такие как запрос, ответ, пользователь, и т.д. ПосколькуsfContextработает как одиночка (singleton), Вы можете использовать выражениеsfContext::getInstance(), чтобы получить его в любом месте и затем получить доступ к любым объектам ядра Symfony:$user = sfContext::getInstance()->getUser();Каждый раз, когда Вы хотите использовать
sfContext::getInstance()в одном из Ваших классов, подумайте дважды о том, что это приводит к сильному сопряжению (coupling). Почти всегда намного лучше передать объект, который Вам нужен, как аргумент.Вы даже можете использовать
sfContextкак реестр и добавлять Ваши объекты, используя методset(). Он принимает имя и объект как аргументы и позже может быть использован методget()для получения объекта по имени:sfContext::getInstance()->set('job', $job); $job = sfContext::getInstance()->get('job');
Перед тем, как мы начнем, нам нужно изменить конфигурацию для окружения test,
чтобы включить слой кэширования:
# apps/frontend/config/settings.yml
test:
.settings:
error_reporting: <?php echo ((E_ALL | E_STRICT) ^ E_NOTICE)."\n" ?>
cache: on
web_debug: off
etag: off
Давайте протестируем страницу создания вакансии:
// test/functional/frontend/jobActionsTest.php $browser-> info(' 7 - Job creation page')-> get('/fr/')-> with('view_cache')->isCached(true, false)-> createJob(array('category_id' => $browser->getProgrammingCategory()->getId()), true)-> get('/fr/')-> with('view_cache')->isCached(true, false)-> with('response')->checkElement('.category_programming .more_jobs', '/23/') ;
Тестер view_cache использован для тестирования кэша. Метод isCached() принимает
два булевых значения:
Даже со всеми инструментами, предоставленными фреймворком функционального тестирования, иногда легче найти проблемы с помощью браузера. Это очень легко сделать. Просто создайте контроллер для окружения
test. Журнал который хранится вlog/frontend_test.logможет также оказаться полезным.
Как и большинство другой функциональности Symfony, подфреймворк кэширования очень гибкий и позволяет разработчику настроить кэш на очень низком уровне.
Завтра мы поговорим о последнем шаге в жизненном цикле приложения: развертывании на промышленных серверах.
If you find a typo or an error, please register and open a ticket.
If you need support or have a technical question, please post to the official user mailing-list.