SunshinePHP, Feb 8th 2013 © David Buchmann, Liip AG
David is a senior developper at Liip SA, specializing in Symfony2. He happens to also be a certified Scrum Master and sometimes enjoys doing the scrum master or product owner role for a project.
Liip is doing custom web development with PHP in Switzerland.
Jordi Boggiano
Understand the effects of your changes.
Donald E. Knuth
# layout.html.twig
@@ -6,8 +6,6 @@
{% include 'DbuCoreBundle::stylesheets.html.twig' %}
- {% include 'DbuCoreBundle::javascripts.html.twig' %}
<link rel="shortcut icon" href="{{ asset('favicon.ico') }}" >
</head>
@@ -91,5 +89,11 @@
</div>
+ {# javascript at the bottom: browser waits with rendering
+ until all files referenced before are loaded
+ #}
+ {% include 'DbuCoreBundle::javascripts.html.twig' %}
</body>
Browser starts rendering after as soon as it has all CSS files, no need to wait for javascript to load.
Page renders after about 5 seconds
Browser starts rendering after as soon as it has all CSS files, no need to wait for javascript to load.
Page renders after about 5 seconds
/* main.css */
.sprite {
display:block;
background-repeat:no-repeat;
background-image:url(/assets/images/sprite.png);
}
.logo {
width:336px;
height:63px;
}
/* generated sprites.css */
.logo {background-position:-160px 0px}
# config_prod.yml
assetic:
filters:
yui_css:
jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar"
apply_to: "\.css$"
yui_js:
jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar"
apply_to: "\.js$"
YUICompressor has been discontinued by the YUI team. You can still get the compiled .jar from the github page.
Server: Apache/2.2.16 (Ubuntu)
cache-control: max-age=600, public
last-modified: Wed, 02 May 2012 09:12:17 GMT
If-Modified-Since: Wed, 02 May 2012 09:12:17 GMT
Server:
304 Not Modified
firewalls:
name:
logout:
path: /logout
target: /
invalidate_session: true
delete_cookies:
PHPSESSID: { path: null, domain: null }
ExpiresActive On
ExpiresByType text/css "access plus 3 months"
ExpiresByType text/javascript "access plus 3 months"
ExpiresByType application/javascript "access plus 3 months"
ExpiresByType image/gif "access plus 1 day"
ExpiresByType image/png "access plus 1 day"
ExpiresByType image/jpg "access plus 1 day"
ExpiresByType image/jpeg "access plus 1 day"
# app/config.yml
framework:
templating:
assets_version: v1
<!-- generated HTML -->
<link rel="stylesheet" href="/css/style.css?v1" type="text/css"/>
...
<script src="/js/scripts.js?v1"></script>
// DefaultController::indexAction
$response = $this->render('DbuCoreBundle:Default:index.html.twig', array());
$response->setMaxAge(600);
$response->setPublic();
return $response;
Add such code to every action that renders a response.
Or annotate every action with a @Cache comment.
# config.yml
liip_cache_control:
rules:
# Make sure login and logout are not cached at all
-
path: ^/(login|login_check|logout)
controls:
private: true
max_age: 0
# Cache the homepage for 10 minutes
-
path: ^/$
vary: Cookie
controls:
public: true
max_age: 600
# Cache every other page for 1 hour
-
path: ^/
vary: Cookie
controls:
public: true
max_age: 3600
Clear the Symfony cache whenever you change values here
Think carefully and test thoroughly
# app/config.yml
liip_cache_control:
varnish:
domain: http://performance.lo
ips: 10.0.0.10, 10.0.0.11 # comma separated list of ips, or an array of ips
port: 80 # port Varnish is listening on for incoming web connections
// CommentsController::postAction
// the page changed, send a purge request for this url
...
$varnish = $this->container->get('liip_cache_control.varnish');
$varnish->invalidatePath($this->generateUrl('home'));
...
# varnish.vcl
sub vcl_recv {
if (req.request == "PURGE") {
# should check if allowed, i.e. IP filter
purge("req.url ~ " req.url);
error 200 "Success";
}
...
# app/config/config.yml
framework:
esi: { enabled: true }
# app/config/routing.yml
_internal:
resource: "@FrameworkBundle/Resources/config/routing/internal.xml"
prefix: /_internal
Make sure either your webserver is only reachable by Varnish or add access restriction to the Varnish server IP for /_internal
index.html.twig
- {% render 'DbuCoreBundle:Comments:comments' %}
+ {% render 'DbuCoreBundle:Comments:comments' with {}, {'standalone': true} %}
layout.html.twig
<li class=login>
- {% render 'DbuCoreBundle:User:showLoginBox' %}
+ {% render 'DbuCoreBundle:User:showLoginBox' with {}, {'standalone': true} %}
</li>
// UserController::showLoginBoxAction
$response = $this->render('DbuCoreBundle:User:loginBox.html.twig', array());
$response->setVary('Cookie', false);
$response->setMaxAge(0);
$response->setPrivate();
return $response;
// CommentsController::commentsAction
$response = $this->render(
'DbuCoreBundle:Comments:comments.html.twig',
array('comments' => $this->getComments())
);
$response->setMaxAge(3600);
$response->setPublic();
return $response;
$varnish = $this->container->get('liip_cache_control.varnish');
$kernel = $this->container->get('http_kernel');
$varnish->invalidatePath(
$kernel->generateInternalUri('DbuCoreBundle:Comments:comments')
);
Note: generateInternalUri is helpful but does not check if controller exists
david# app/console cache:clear --env=prod --no-debug
david# curl -H "Surrogate-Capability: abc=ESI/1.0" http://performance.lo/
...
<esi:include src="/_internal/secure/DbuCoreBundle%3AUser%3AshowLoginBox/none.html" onerror="continue" />
...
<esi:include src="/_internal/secure/DbuCoreBundle%3AComments%3Acomments/none.html" onerror="continue" />
...
sub vcl_recv {
set req.http.Surrogate-Capability = "abc=ESI/1.0";
...
# try to lookup even if there is a cookie
return (lookup);
}
sub vcl_fetch {
if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
unset beresp.http.Surrogate-Control;
set beresp.do_esi = true; # just "esi;" for varnish < 3.0
}
...
if (beresp.http.Vary) {
return (pass);
}
}
sub vcl_deliver {
if (! req.url ~ ".*\.(css|js|png|gif|jpg|ttf)(\?.*)?$") {
set resp.http.Vary = "Cookie";
# if-modified-since will only confuse us, remove date
unset resp.http.Last-Modified;
}
}
Mix different input formats:
{% stylesheets output="assets/style.css"
'path/to/Resources/css/yahoo-reset.css'
'path/to/Resources/less/main.less'
'path/to/Resources/less/misc.less'
%}
<link href="{{ asset_url }}" media="screen"
type="text/css" rel="stylesheet" />
{% endstylesheets %}