Yii 1.1: AssetManager: clearing browser's cache on site update

31 followers

If you like the things to be short and clear, then you can skip right to a short summary of instructions at the end of this article.

Intro

You probably know, that AssetManager might be useful to avoid overlapping of static files like css, js, images in serveral different modules. That is awesome and so on and bla-bla-bla - you can read about it in the Understanding "Assets" article.

**But wait! Do you really create modules so often? ** I can tell what you really do very often when you develop web applications.
You write new pieces of Javascipt code, you update CSS, you add elements to your CSS-sprite images!

And if you are in constant cycle of improvements, then you probably need to deliver all of those tasty new features to your end-user.

And here is the big question: Is it really so hard to do?

And the answer is... nope, it's easy! You just need to know how to do it the right way.
If you think, that storing your files in a folder on the server and then just changing them will deliver it's new contents to the end user then you are deeply mistaken. All your static files by default will be cached by user's browser and will stay there for a long-long time. The cache will be cleared, only if you change the name of the file.

Some may say, well I'll add timestamp to the end of each file like main.css?v=123556
Really? I find this too difficult and this may cause a lot of troubles, if you for example refer to the same static files in several widgets or views. This may cause conflicts.

And here is short and simple explanation of how to do it the right way (on the author's humble opinion).

The point of using AssetManager

First thing we should do is to start using AssetManager in our project.
That is simple. Explanation below.

Step 1. Prepare files.

Create folder named 'assets' under your protected folder.
And place all your static files there. You might skip some files, like favicon or maybe logo or maybe some documents which are referenced from some other websites and have to be located under constant URL.

But the rest of the files, will now look like:

/protected
    /assets
        /css
            /main.css
        /img
            /bg.png
            /sprite.png
            /extra_icons.png
        /js
            /main.js
            /form.js
            /etc.js

You may ask: "Hey man, but how will we refer to these files? They are under protected now!"
Just read further and you'll know it.

Step 2. Prepare the tools.

You probably already have a custom base Controller class for all of your controllers.
We will modify it a little and will make it look like that:

class Controller extends CController
{
        private $_assetsBase;
 
        public function getAssetsBase()
        {
                if ($this->_assetsBase === null) {
                        $this->_assetsBase = Yii::app()->assetManager->publish(
                                Yii::getPathOfAlias('application.assets'),
                                false,
                                -1,
                                defined('YII_DEBUG') && YII_DEBUG
                        );
                }
                return $this->_assetsBase;
        }
 
        public $menu=array();
        public $items=array();
        public $breadcrumbs=array();
}

Really simple, yeah?

What we did now is we created a getter method which publishes our /protected/assets folder and stores the path of published files in _assetsBase private property.

There are several interesting options for CAssetManager.publish() method.

publish(string $path, boolean $hashByName=false, integer $level=-1, boolean $forceCopy=false)

$path - the assets folder we want to publish
$hashByName - very important parameter, which will allow us to clear browser's cache later (please, read on)
$level - we'll just publish all subdirs recursivly
$forceCopy - we set this to YII_DEBUG, which causes instant assets updates on our local PC and causes only required updates on production server (YII_DEBUG should be false in production)

Step 3. Using prepared tools to operate with prepared files.

Now we can use our assetsBase property everywhere to refer our assets.

You can write in your layout or view files something like:

<link rel="stylesheet" type="text/css" href="<?=$this->assetsBase?>/css/main.css" />

or

<?Yii::app()->clientScript->registerScriptFile($this->assetsBase.'/js/utils.js')?>

You can write it this way, because $this is the Controller instance here.
You can also use it within widgets, but the code would be a little different:

<?Yii::app()->clientScript->registerScriptFile(Yii::app()->controller->assetsBase.'/js/widget.js')?>

Now, when we did all that stuff, the really explosive power of AssetManager is about to happen...

Step 4. Updating production version and forcing user's browser cache to clear.

Now, if you remove all subfolders in your /assets folder under the website root, then you'll notice that Yii will automatically create new randomly-named folders.

But if you take a closer look, then you'll notice, that even if you changed the contents of your static files and cleared the assets folder, the new assets subfolder's names will be similar to they were before!

"What the hack?" - you'll ask!
Well, this problem is described in this topic thread and SamDark actualy resolved it with his patch on Nov 8, 2011 and those second $hashByName parameter of CAssetManager.publish() method does all the magic now.

$hashByName parameter set to false says to Yii to build assets folder name as the hash from dirname of the path being published and its modification time.

This means, that if you just do

touch /path/to/your/website/protected/assets

in linux console, then you'll have a brand new assets folder name on next assets' update

And this means, that all your end-users will have to re-validate their browser's cache and will get your new highly improved static files right away.

Short summary

So, basically all you should do is:

  1. Move all your static files to /protected/assets folder
  2. Improve your basic Controller class as described above
  3. Use controller->assetsBase everywhere you refer to static files
  4. When you do a site update, just do touch /path/to/your/website/protected/assets
  5. PROFIT: users will get latest versions of your static files (images, styles, scripts, etc)

Additional links

  1. CAssetManager docs
  2. Topic thread about this issue
  3. Topic thread about this issue in upcoming Yii2 (available for active Yii forum users only)
  4. Extensions which provide cool minification features for assets:
  5. Another article describing usage of CAssetManager
  6. Russian version of this article

Wish you luck!

Total 4 comments

#17257 report it
Jonas at 2014/05/16 10:23am
Permission 777 and what about just touch()?

I don't have access to the shell so using exec() is great. Anyhow it only works when I set the permisssion for assets folder to 777. Otherwise I get an access denied error message.

And there is actually a PHP function doing exactly what we want: touch(). Then it get's as easy as:

touch('/path/to/your/website/protected/assets');

Happy touching :)

#17007 report it
Daniel Galvan at 2014/04/23 10:16am
Great, Thanks for sharing.

For Step 4, instead of go to in linux console, we could do the following.

if (YII_DEBUG) { $command = "touch /path/to/your/website/protected/assets"; exec($command); }

#8468 report it
x00xer at 2012/06/06 02:55am
Thank you!

+1, great idea, clear explanation. Excellent!

#7176 report it
Gismo at 2012/03/01 05:26am
Thanx

This is an excellent solution for a small project. I used to be extended CAssetManager, but for a small project once it is garbage. Excellent, thanx.

Leave a comment

Please to leave your comment.

Write new article