JasonDaly.name

PHP, Ruby, Symfony, Rails, Doctrine, MooTools. Web Development.

Posts tagged with "php5.3"

August 17, 2010

Caching Static Assets with Cache-Busting Support in Symfony 1.4

(Note: This technique can be applied anywhere. This article is specifically related to a Symfony 1.4 implementation)

As recommended in the Google Page Speed best practices, static resources should be cached locally in the browser. This means setting a far-future expiration date on filetypes such as .css and .js content. This first step is achieved rather simply by adding something like below to the application’s .htaccess file or virtualhost’s httpd.conf include.

<IfModule mod_expires.c>
  Header set cache-control: public
  ExpiresActive on
  ExpiresDefault                          "access plus 1 month"

 # Other ExpiresBy... declarations here...

 # css and javascript
  ExpiresByType text/css                  "access plus 1 month"
  ExpiresByType application/javascript    "access plus 1 month"
  ExpiresByType text/javascript           "access plus 1 month"
</IfModule>

FileETag None

Note the last line above removes ETags, since according to Yahoo ETags are not needed for static content with far-future expiries set1.

The changes above will cause CSS and Javascript files to be cached locally in the browser for 1 month from the first access time, causing future requests to be made without checking against the server for newer versions of these static resources. Though the caching desired is now in place, it’s not perfect since if these static resources are modified in any way, the cached local browser version of these files will not be invalidated.

Invalidating the Cache

Currently in the <head> of my application, default stylesheets are added and then all included stylesheets (including those added by the specific action being requested) have their <link ... /> tags constructed and appended to the DOM using the following code

  use_stylesheet('main.css');
  use_stylesheet('handheld.css', '', array('media' => 'handheld'));

  include_autoversioned('stylesheets');

The include_autoversioned() function is where the work is done which invalidates the browsers’ cache. This function is a wrapper of Symfony’s include_stylesheets() and include_javascripts(). I have added a Template.php file to my application’s lib/helpers/ directory which contains the function as shown below.

/**
 * Depending on the type of include requested (stylesheets or javascripts), for
 * each file already added to the response object, for each file, i.e. /css/base.css,
 * replaces it with a string containing the file's mtime, i.e. /css/base.1221534296.css.
 *  
 * @see http://stackoverflow.com/questions/118884/what-is-an-elegant-way-to-force-browsers-to-reload-cached-css-js-files
 *  
 * @param $file  The file to be loaded.  Must be an absolute path (i.e.
 *               starting with slash)
 *               
 * @throws InvalidArgumentException when invalid type is requested
 *                
 * @return void (outputs string response directly)               
 */
function include_autoversioned($type = 'stylesheets'){
  if (!in_array(strtolower($type), array('stylesheets', 'javascripts'))) {
    throw new InvalidArgumentException(sprintf("\$type can only be 'stylesheets' or 'javascripts': '%s' was passed", $type));
  }

  $function = sprintf('get_%s', $type);
  $code = $function();
  unset($function);

  $code = preg_replace_callback('/(href|src)\=\"([^\"]+)\"/', function($matches){    
    if (strpos($matches[2], '/') !== 0 || !file_exists(sfConfig::get('sf_web_dir') . DIRECTORY_SEPARATOR . $matches[2])) {
      $path = $matches[2];
    } else {
      $mtime = filemtime(sfConfig::get('sf_web_dir') . DIRECTORY_SEPARATOR . $matches[2]);
      $path = preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $matches[2]);
    }

    return str_replace($matches[2], $path, $matches[0]);
  }, $code);

  echo $code;
}

(Note: The above code requires >= PHP5.3 due to the use of an anonymous function)

Also, again to the application’s .htaccess file or virtualhost’s httpd.conf include, the following must be added above the other RewriteRules that are in place for Symfony applications by default2.

# Cache-busing js and css files
RewriteRule ^(.*)\.[\d]{10}\.(css|js)$ $1.$2 [L]

To show what this does, let’s look at the <link ... /> tags generated using Symfony’s get_stylesheets in the <head>

<link rel="stylesheet" type="text/css" media="screen" href="/css/main.css" />
<link rel="stylesheet" type="text/css" media="handheld" href="/css/handheld.css" />

and compare this to the output generated by the new include_autoversioned('stylesheets')

<link rel="stylesheet" type="text/css" media="screen" href="/css/main.1282090705.css" />
<link rel="stylesheet" type="text/css" media="handheld" href="/css/handheld.1282076131.css" />

By modifying the filenames of these static resources by injecting a unix timestamp for the last-modified time of each file, whenever a change is made to one of these files, however small, it will cause the filename of that resource to change, effectively invalidating the local browser’s copy of that file. This guarantees that a browser will maintain a copy of a static resource for as long as can be reasonably expected, while always guaranteeing they immediately receive the latest changes to those resources as soon as they’re published.


  1. The .htaccess section was in part taken from http://github.com/paulirish/html5-boilerplate 

  2. Though modified considerably, the cache invalidation was inspired by this stackoverflow answer 

Tags: cache code php symfony symfony 1.4 tips php5.3

May 8, 2010

Introducing SpellCheck

After learning Github is now offering SVN support (nearly all of my development work is done using SVN), I decided it was time to properly version my small changes to the great gRaphael library by forking the original code with my own account. I also decided to start to maintain smaller utilities I write for personal use through git on Github as well. The first of these packages is \Deefour\SpellCheck.

SpellCheck v1.0

Introducing ”SpellCheck v1.0”. As mentioned in the README,

SpellCheck leverages “…the XML request/response used by the Google Toolbar…” accepting “… a string to be transformed into a corrected version of itself.”

This class simply makes all corrections suggested by Google to the original string passed in. Admittedly, this is not as flexible as some will like, but for now it suits my needs and is a great start. Some small points:

Usage instructions and code can be found in the v1.0 Tag on GitHub.

Tags: php spellcheck releases v1.0 code github 5.3 php5.3