SiteCrafting, Inc.
16 Feb
Function Caching in PHP
Web apps become notoriously slow when running database queries. Often times we find ourselves writing a useful look up as a function, and then forget that it requires a query each time. This is where caching comes in. And PHP has a really clean way to do it.
The Problem
A common problem in web app development is the relative slowness of database queries. In an effort to make my code more efficient, I began searching for ways to cache the results so I wouldn't have so much latency. There are several places where this would be helpful. Take the example of looking up a user by ID. The function below is a dirt simple lookup. (Note: $id should be sanitized before use in a database query and is left out for simplification)function getUser($id) {
$sql = "SELECT * FROM users WHERE id = $id";
$res = mysql_query($sql);
return $res ? mysql_fetch_assoc($sql) : false;
}This function is quite simple and works great for a one time lookup. But if you need to look up the user a second time, why waste time with a whole new query? We should create a caching method.Global Variables
Originally, I used the $GLOBALS variable to store the results. I typically would use a string with a really long name to prevent functions from writing over each other. But $GLOBALS is slow and a memory hog, and there is the potential to be overwritten by other functions. This makes it very dangerous to use.$GLOBALS['GET-USER-CACHE'] = array();You can see that there are many downfalls to this. First, you create a global variable, whether you use it or not. That will slow down all your code. Second, the long string must be the same or it will break your code (or cause two global variables, depending on your config). And third, if any other function uses that variable, it could ruin your code.
function getUser($id) {
$user =& $GLOBALS['GET-USER-CACHE'][$id];
if(!$user) {
$sql = "SELECT * FROM users WHERE id = $id";
$res = mysql_query($sql);
$user = $res ? mysql_fetch_assoc($res) : false;
}
return $user;
}
Static Keyword
Then I discovered the static keyword can be used from within a function. The effects of static variables within classes are well known, but what was unknown to me was how it would effect a function. After some time in my sandbox I discovered it could be used like this:function getUser($id) {
static $users = array();
$user =& $users[$id];
if(!$user) {
$sql = "SELECT * FROM users WHERE id = $id";
$res = mysql_query($sql);
$user = $res ? mysql_fetch_assoc($res) : false;
}
return $user;
}It turns out the variable $users is kept between calls of getUser(). The static nature of this variable means that it can be used to cache the results of the database query without any external access. It makes your close look cleaner. And another function can't come in and break it. No memory hog, no global variables, no problem.Other Uses
Because static variables hold state we can use it for auto incrementing. In the example below, we are looking to alternate between even and odd. In the first example, you would have setup $i = 0, then call alternateRows($i++) each time. This means $i would be managed outside the function. In the second example, there is no need to know the internals of how it determines even or odd.function alternateRows($i) {
return $i%2 ? 'odd' : 'even';
}V.S.function alternateRows() {
static $i = 0;
return $i++%2 ? 'odd' : 'even';
}Again, the function becomes protected from outside code. Since $i is commonly used in loops, a mistaken, duplicate variable name could break your code. But now the variable is hidden from view and the results become predictable.Conclusion
Static is a keyword that can be applied to a variable (both in PHP 4 and 5) within a function to give in state between function calls. It has a great many uses, such as object caching or auto incrementing. This becomes particularly handy when writing libraries to hide the complexities of optimizations. What other uses can you think of for static variables in functions? Leave us a note in the comments.
From the Workbench, PHP, Software Engineering
by Paul Sayre | 2/16/2010 12:30pm | Comments (5)
by Paul Sayre | 2/16/2010 12:30pm | Comments (5)
First, this is a BAD example:
$sql = "SELECT * FROM users WHERE id = $id";
Better would be:
$sql = "SELECT * FROM users WHERE id = ".(int)$id;
It is never too complex not using it!
A hint for your caching idea:
getUser($id, $unset=false)
If your script updates a user, you somehow need the possibility to get a "fresh" entry.
Your getUser($id) function only makes sense if your script calls it multiple times for the same user. Otherwise it would be a waste of RAM. Much more performant would be saving the cache with APC, XCache, eAccelerator or Memcache.
regards, Julius
Left by Julius Beckmann | Feb 17, 2010
Julius,
You are absolutely correct. It was for the sake brevity that I neglected to sanitize the $id. I would never have production code that did not sanitize something like this, especially something so simple.
And, again, it was for brevity that I did not include a freshness flag. All one would have to do to add it is to include the variable in the if statement that checks the cache.
You are also correct that the cache would be cleared after every script execution. At SiteCrafting, we use multiple caching methods outside of PHP to make page loads snappy. That include caching database queries, which would make my example moot.
The purpose of the article was to focus on the static keyword and how it can be used in PHP.
All great points, thank you for improving the documentation for this example.
Left by Paul Sayre | Feb 17, 2010
I'm newbe at PHP and web development. A cache feature I would like to have is mantaining some values between two different browser requests of the same php page. In other words getting php "statefull". I thought the static usage you are suggesting here could accomplish that but, for me, it doesn't work in that way. Am I missing something ?
Are there any other way to get a variable to mantain its value after the end of the running php script ? (other than storing it in a DB)
Left by Marco | Feb 20, 2010
Marco,
This may not be what you ware looking for, but PHP has a super global called $_SESSION. You can read about it here: http://www.php.net/manual/en/book.session.php
Let's say you want to keep the user's information between pages, your code could look like this:
session_start();
if(is_numeric($_POST['user_id'])) {
$_SESSION['user'] = getUser($_POST['user_id']);
}
$_SESSION['user']['id'] // the user's ID
$_SESSION['user']['name'] // etc.
This is no replacement for a good login page, and is just a example of using the session variable. Anything you put in the $_SESSION variable will be stored in the server between page calls. A cookie is given to the browser to uniquely identify them, keeping variables separate between users.
Is this what you were looking for?
Left by Paul Sayre | Feb 22, 2010
Paul -
Session is for between page hits, but if you need to call the same function multiple times in the same script execution, the static variable works great.
It's also worth noting that using sessions where you don't NEED them creates disk I/O since session data is cached on-disk. The disk writes required for this are slower operations in the grand scheme, and should be avoided when possible.
That said, obviously there are a plethora of cases where sessions are absolutely needed, and you shouldn't shy away from them when there is a need that they actually do meet.
Left by Ryan V | Mar 17, 2010