Vhost caching issue

While I was deploying the new website, I ran into some issues where CakePHP was blowing up on missing model database tables. The weirdness was that these models were not part of my application, but were part of another application running on the same nginx box. I immediately deduced that the problem was the cache, but where was the disconnect? Since the issue was related to model caching, it had to be part of the internal CakePHP caching mechanism.

The problem was a simple one, I forgot to change the $prefix (defaults to myapp_) cache variable in Config/core.php. A small oversight, but a problematic one at that. Just a reminder to everyone else that this variable does exist and to change it when running vhosts.

Naming your cache keys

Everyone caches, that's a pretty well known fact. However, the problem I always seemed to have was how to properly name my cache keys. After much trial and tribulation, I believe I have found a great way to properly name cache keys. To make things easy, my keys usually follow this format.

<model|table>__<function|method>[-<params>]

To clear up some confusion, it goes as follows. The first word of your cache key should be your model name (or database table name), as most cached data relates to a database query result. The model name is followed by a double underscore, which is then followed by the function/method name (which helps to identify exactly where the cache is set), which is then followed by multiple parameters (optional). Here's a quick example:

public function getUserProfile($id) {
	$cacheKey = __CLASS__ .'__'. __FUNCTION__ .'-'. $id;
	// Check the cache or query the database
	// Cache the query result with the key
	// Return the result
}

The $cacheKey above would become: User__getUserProfile-1337, assuming the user's ID is 1337. Pretty easy right? Besides the verbosity that it takes to write these constants, it works rather well (unless you want to write the method and class manually). You may also have noticed that I used __FUNCTION__ over __METHOD__ -- this was on purpose. The main reasoning is that __METHOD__ returns the class and method name, like User::getUserProfile, while __FUNCTION__ just returns the method name.

The example above will work in most cases, but there are other cases where something more creative is needed. The main difficulty is how to deal with array'd options. There are a few ways of dealing with that, the first is checking to see if an ID or limit is present, if so, use that as the unique value. If none of the options in the array are unique, you can implode/serialize the array and run an md5() on the string to create a unique value.

User::getTotalActive();
// User__getTotalActive
Topic::getPopularTopics($limit);
// Topic__getPopularTopics-15
Forum::getLatestActivity($id, $limit);
// Forum__getLatestActivity-1-15
Post::getAllByUser(array('user_id' => $user_id, 'limit' => $limit));
// Post__getAllByUser-1-15
User::searchUsers(array('orderBy' => 'username', 'orderDir' => 'DESC'));
// User__searchUsers-fcff339541b2240017e8d8b697b50f8b

In most cases an ID or query limit can be used as a unique identifier. If you have another way that you name your cache keys or an example where creating the key can be difficult, be sure to tell us about it!

Pitfalls within Cake's Cache engine

For the past week I have been working on a DataSource for the WeGame API. Everything was working fine until I added some logic to cache the results. No matter what I did I received the following fatal error:

//Fatal error: Call to a member function init() on a non-object in C:\xampp\htdocs\cake_1.2.5\cake\libs\cache.php on line 195
// Line 195
$_this->_Engine[$engine]->init($settings);

The error was being thrown from within Cache::set() and it seemed to fail because the actual storage engine object was not instantiated. I spent a good couple hours trying to track down the bug, but to no avail. I tried removing Cache::set(), placing Cache::config() within config/core.php, but still nothing.

Finally I thought to myself "Let me check my Config settings", so I did. I noticed that Cache.disable was set to true, and once I set it to false the whole system worked! Bah, pretty annoying right? I sure love Cake, but its error and exception handling isn't that great. I've ran into so many of these case scenarios where a simple trigger_error() or uncaught exception would of helped (This is for you Cake team *cough*2.0*cough*).

Another thing I would like to note, is that the path option in Cache::config() does not actually create the folder, you must do that manually. Here's how I resolved that problem.

$cachePath = CACHE .'we_game'. DS;
// Create the cache dir
if (!file_exists($cachePath)) {
	$this->Folder = new Folder();
	$this->Folder->create($cachePath, 0777);
}
Cache::config('weGame', array('path' => $cachePath));

Calling functions within your CSS files

I always thought CSS should have a little bit more power, like the use of inline programming functions and dynamic variables. That's exactly why I set out to write my Compression class. On top of compressing the CSS files, saving cached versions and assigning dynamic variables, the class can now call PHP functions that have been written inline within the CSS itself.

This allows for tons of new possibilities for allowing dynamic content, values and structure within boring old stylesheets. As an example, here is the PHP function, the CSS written with the function and the result.

// The PHP function
public function colWidth() {
	$args = func_get_args(); 
	$width = $args[0] * 100;
	return $width .'px';
}
// The CSS
.col1 { width: colWidth(5); }
.col2 { width: colWidth(3); }
.col3 { width: colWidth(1); }
// After being parsed
.col1 { width: 500px; }
.col2 { width: 300px; }
.col3 { width: 100px; }

This new feature has been released in the new Compression version 1.4, which is now available! You can view the updated documentation, or download the files to see the example usages, enjoy! Be sure to spread the word!

Download Compression v1.4

Fixing a weird cache issue in Firefox

About two weeks ago my Firefox started acting up and would not cache pages correctly. Actually, the content would cache, but any image on the actual page would load 1 by 1 and I can watch it go in sequence. This was extremely annoying, especially working in PhpMyAdmin, watching each image icon load 1 by 1 until the page was complete. This process would take nearly 5x what it usually did, so it got quite annoying real fast. I wasn't sure what caused this to happen as all my settings were normal and untouched.

So once I had enough of this, I checked Firefoxs core config (by typing about:config in the address bar). I then typed "cache" in the filter field and once it populated, I noticed the following fields were set to false (turned off).

browser.cache.disk.enable
browser.cache.memory.enable

Once I reset these to defaults (enabling it to true), my weird image cache problem vanished. I am not entirely sure if messing with these settings are ok, so if anyone has any input on the subject it would be appreciated. But besides that, everything seems to be running smoothly, and I thought it would be a good idea to let others know in case they run into it as well.

Caching each query individually

So lately I have been delving into the caching capabilities of CakePHP. Most, if not all of its capabilities work wonderfully; although I personally can't get into $cacheAction (within the controllers). The $cacheAction property only works for static and non-user generated pages, in other terms, any content that changes depending on a logged in user wont work correctly with $cacheAction (unless you want thousands and thousands of cache files). So I stopped using $cacheAction all together in my latest application, and instead built a method that caches individual queries, instead of the whole page. All the modifications have been applied to the models find() method. To use this, place the following code within your app/app_model.php.

/**
 * Wrapper find to cache sql queries
 * @param array $conditions
 * @param array $fields
 * @param string $order
 * @param string $recursive
 * @return array
 */
public function find($conditions = null, $fields = array(), $order = null, $recursive = null) {
	if (Configure::read('Cache.disable') === false && Configure::read('Cache.check') === true && isset($fields['cache']) && $fields['cache'] !== false) {
		$key = $fields['cache'];
		$expires = '+1 hour';
		if (is_array($fields['cache'])) {
			$key = $fields['cache'][0];
			if (isset($fields['cache'][1])) {
				$expires = $fields['cache'][1];
			}
		}
		// Set cache settings
		Cache::config('sql_cache', array(
			'prefix' 	=> strtolower($this->name) .'-',
			'duration'	=> $expires
		));
		// Load from cache
		$results = Cache::read($key, 'sql_cache');
		if (!is_array($results)) {
			$results = parent::find($conditions, $fields, $order, $recursive);
			Cache::write($key, $results, 'sql_cache');
		}
		return $results;
	}
	// Not cacheing
	return parent::find($conditions, $fields, $order, $recursive);
}

In the next step, you would create a folder called sql within your tmp/cache/ and chmod the permissions to 777. Once you have created the folder, open up your app/config/core.php file and place the following code at the bottom (near the default cache settings).

Cache::config('sql_cache', array(
    'engine'		=> 'File',
    'path'		=> CACHE .'sql'. DS,
    'serialize'	=> true,
));

By default, caching will not work on your applications queries, you would need to set an additional "cache" option within your find(). Each SQL cache should have its own unique identifier so that it does not conflict with other queries. Also by default, queries will be cached for one hour and will be saved as a serialized array. The following examples explain how the cache option works.

// Cache query to /tmp/cache/sql/model-test_sql_query
$results = $this->Model->find('all', array(
	'cache' => 'test_sql_query'
));
// Cache query to /tmp/cache/sql/model-another_query that expires in 24 hours
$results = $this->Model->find('all', array(
	'cache' => array('another_query', '+24 hours')
));

What if I have a query that's used multiple times but each has its own limit (custom method), but uses the same cache slug? Simply give the cache slug a dynamic name like so:

// Cache query to /tmp/cache/sql/model-dynamic_query-15 
$results = $this->Model->find('all', array(
	'limit' => $limit, // 20, 30, etc
	'cache' => 'dynamic_query-'. $limit
));

I personally have found an increase in load times up to 150-200% faster using this method. This should only be applied to queries that are used on landing pages, and queries that do not change according to which user is logged in. Have fun.