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!

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

Creating a simple debug() function

If you are ever like me and hate having to type print_r and echo pre blocks whenever you want to debug a variable or array, this function is for you! This is a global function that can be used to output any data that you need to debug and it comes equipped with some nifty features. For one thing, the debug() will not output any data on the website if error_reporting is turned off, which is good for instances where you forget to remove the debug() code! It will also display the file and line number where the debug() was called, and you can pass true as the second argument to do a var_dump instead of print_r. Here's the code for your enjoyment!

/**
 * Outputs/Debugs a variable and shows where it was called from
 *
 * @param mixed $var
 * @param boolean $dump
 * @param boolean $backtrace
 * @return string
 */
public function debug($var, $dump = false, $backtrace = true) {
	if (error_reporting() > 0) {
		if ($backtrace) {
			$calledFrom = debug_backtrace();
			echo '<strong>' . trim(str_replace($_SERVER['DOCUMENT_ROOT'], '', $calledFrom[0]['file'])) . '</strong> (line <strong>' . $calledFrom[0]['line'] . '</strong>)';
		}
		echo '<pre class="debug">';
		$function = ($dump) ? 'var_dump' : 'print_r';
		$function($var);
		echo '</pre>';
	}
}

Please note that this is a very basic debug function with very basic backtrace functionality. Here are a few examples on how to use the function (if for some reason you don't understand it).

debug($_SERVER);
// var_dump() instead of print_r()
debug($_SERVER, true);
// Do not display the backtrace
debug($_SERVER, false, false);

Custom PHP Functions: Datetime functions

All of these functions can be found within the Titon Utility library.

It is now time for my second post, in my series of "Basic/Common PHP functions that all programmers should know". The first series was about basic PHP strings and with this issue I am going to tackle 5 more functions for date and time. As always, I try to make my functions short but powerful, enjoy!

If you have any ideas for a series you want to see, or a type of function, be sure to send me an email using the contact page (in the footer), thanks!

Days Between

This function finds the difference in days between two dates. Each date must be a unix timestamp; if not my function will try to convert it to one. This could be modified to find the difference in weeks, seconds, etc.

/**
 * Finds difference in days between dates.
 * 
 * @param int $start
 * @param int $finish
 * @return int
 */
public function daysBetween($start, $finish) {
	if (!is_int($start))	$start = strtotime($start);
	if (!is_int($finish))	$finish = strtotime($finish);
	$diff = $finish - $start;
	$days = $diff / (24 * 3600);
	return round($days);
}
Get Age

This is a pretty simple function, in that it will find the persons age depending on the year given and the current year. The birth year could either be a string of '1988' (4 numeric characters) or the full date 1988-02-26.

/**
 * Gets the age of an individual.
 * 
 * @param int $timeStamp
 * @return int
 */
public function getAge($timeStamp) {
	$seconds = time() - strtotime($timeStamp);
	$year = 60 * 60 * 24 * 365;
	return floor($seconds / $year);
}
Get Date/Time in Timezones

Now this is something that could be done in many numerous ways, but mine simply takes the timezone difference (-8, 0, +6, etc) as the second argument, and then returns a unix timestamp. This function does not have any input checking (so make sure your arguments are correct before hand) and does not apply Daylight Savings Time.

/**
 * Gets a unix timestamp in certain timezones.
 * 
 * @param int $timeStamp
 * @param int $timeZone
 * @return int
 */
public function timeInZone($timeStamp, $timeZone = -8) {
	return strtotime(gmdate('Y-m-d h:i:sa', $timeStamp + (3600 * $timeZone)));
}
Date within a Timeframe

This function will take a date and check to see if it was within a certain date range and return a boolean value. My function will attempt to convert non unix timestamps to unix; be sure to pass the data correctly. Also you can leave the 3rd argument empty ($finish date) and the current time() will be used.

/**
 * Checks to see if a timestamp was within a timeframe.
 * 
 * @param int $check
 * @param int $start
 * @param int $finish
 * @return boolean
 */
public function withinTimeframe($check, $start, $finish = '') {
	if (!is_int($check))	$check = strtotime($check);
	if (!is_int($start))	$start = strtotime($start);
	if (empty($finish)) {
		$finish = time();
	} else {
		if (!is_int($finish)) $finish = strtotime($finish);
	}
	if ($check >= $start && $check <= $finish) {
		return true;
	}
	return false;
}
Breakdown a timestamp

Now this next function is quite different then the previous ones, but is pretty powerful. This function breaks down the timestamp to find how many days, weeks, months, etc there are until the current time (using time()). This type of function is good for doing text like: Last comment 5 days, 3 hours ago.

/**
 * Breaksdown a timestamp into an array of days, months, etc since the current time.
 * 
 * @param int $timeStamp
 * @return array
 */
public function timeBreakdown($timeStamp) {
	if (!is_int($timeStamp)) $timeStamp = strtotime($timeStamp);
	$currentTime = time();
	$periods = array(
		'years'         => 31556926,
		'months'        => 2629743,
		'weeks'         => 604800,
		'days'          => 86400,
		'hours'         => 3600,
		'minutes'       => 60,
		'seconds'       => 1
	);
	$durations = array(
		'years'         => 0,
		'months'        => 0,
		'weeks'         => 0,
		'days'          => 0,
		'hours'         => 0,
		'minutes'       => 0,
		'seconds'       => 0
	);
	if ($timeStamp) {
		$seconds = $currentTime - $timeStamp;
		if ($seconds <= 0){
			return $durations;
		}
		foreach ($periods as $period => $seconds_in_period) {
			if ($seconds >= $seconds_in_period) {
				$durations[$period] = floor($seconds / $seconds_in_period);
				$seconds -= $durations[$period] * $seconds_in_period;
			}
		}
	}
	return $durations;
}

And there you have it, 5 functions dealing with date and time. Stay tuned for the next issue! And again, if you have ideas for functions, send me an email!

Custom PHP Functions: String functions

All of these functions can be found within the Titon Utility library.

It's now time for some PHP that isn't Cake related. This is the first part in a series of "Basic/Common PHP functions that all programmers should know". This series has to deal with manipulating strings. I try to make my functions short, sweet and powerful, and I hope you learn something from them.

Truncate

This function takes a long string and shortens it to a defined length and adds appends an ellipsis (or custom string) to the end. Instead of chopping a word in half (if the limit finished within it), it moves the pointer up to the previous space.

/**
 * Truncates a string to a certain length.
 * 
 * @param string $text
 * @param int $limit
 * @param string $ending
 * @return string
 */
public function truncate($text, $limit = 25, $ending = '...') {
	if (strlen($text) > $limit) {
		$text = strip_tags($text);
		$text = substr($text, 0, $limit);
		$text = substr($text, 0, -(strlen(strrchr($text, ' '))));
		$text = $text . $ending;
	}
	return $text;
}
Shorten

This function works similarly to truncate(), but instead of chopping off the end of the string, it chops out the middle. This is useful for shortening users long names or websites.

/**
 * If a string is too long, shorten it in the middle.
 * 
 * @param string $text
 * @param int $limit
 * @return string
 */
public function shorten($text, $limit = 25) {
	if (strlen($text) > $limit) {
		$pre = substr($text, 0, ($limit / 2));	
		$suf = substr($text, -($limit / 2));	
		$text = $pre .' ... '. $suf;
	}
	return $text;
}
Obfuscate

Now this function doesn't directly scramble the text and confuse the user, instead it scrambles the source code. This is useful for displaying emails or other strings that you don't want to show up directly in your source code.

/**
 * Scrambles the source of a string.
 * 
 * @param string $text
 * @return string
 */
public function obfuscate($text) {
	$length = strlen($text);
	$scrambled = '';
	for ($i = 0; $i < $length; ++$i) {
		$scrambled .= '&#' . ord(substr($text, $i, 1)) . ';';
	}
	return $scrambled;
}
Slugify

This function takes a string and makes it SEO and URL friendly (for example in the address bar for my blog posts). It lowercase's all words, replaces spaces with a dash, removes foreign/illegal characters and follows the guidelines for the best possible SEO. For example "Hello, my name is Miles Johnson!" would convert to "hello-my-name-is-miles-johnson".

/**
 * Rewrite strings to be URL/SEO friendly.
 *
 * @param string $text
 * @return string
 */
public function slugify($text) {
	$text = trim(strtolower($text));
	$text = str_replace(array('-', ' ', '&'), array('_', '-', 'and'), $text);
	$text = preg_replace('/[^a-zA-Z0-9\-_]/is', '', $text);
	$text = urlencode($text);
	return $text;
}
Listing

This final function takes an array of items and turns them into an ordered list with the last item having the word "and" in front of it instead of a comma. Very good for making lists.

/**
 * Creates a comma separated list with the last item having an "and".
 *
 * @param array $items
 * @param string $and
 * @return string
 */
public function listing($items, $and = 'and') {
	if (is_array($items)) {
		$lastItem = array_pop($items);
		$items = implode(', ', $items);
		$items = $items .' '. $and .' '. $lastItem;
	}
	return $items;
}

Now that's all I have for now, and I know you will use this in your code constantly just like I do! Be patient for my next series which will be dealing with basic date/time functions.