Entries tagged with “PHP”

Simple PHP Caching Using Output Buffering

I’ve worked on quite a few PHP projects recently, and all of them have required some form of caching. From working with each, I’ve come up with a pretty efficient method for caching code using PHP’s output buffering. It ends up being really quick and super flexible.

Output Buffering Basics

Output buffering is a pretty simple concept: instead of letting PHP return data to your user’s browser, you capture it and store it in a “buffer,” and you can decide what to do with it. Here’s a simple example:

ob_start();
echo "Hello!  This is buffered.";
$buffer = ob_get_clean();

Let’s go through the code line-by-line. The first line calls ob_start() (docs), which starts output buffering. The next line normally would be sent to the browser. Instead, since I called ob_start(), it gets stored in our buffer. The third line takes the current buffer and assigns it to the $buffer variable and stops the current buffer, all using the ob_get_clean() function (docs). It’s really simple stuff, and it becomes very powerful when used correctly.

How Caching Will Work

For this post, I’m going to be caching a simple API, and the general process will work like this:

  1. A user makes an API call, something like http://mysite.com/api/?method=myapp.search&type=people&query=Kyle
  2. If a cache file exists for the call, and it is younger than 15 minutes, skip to #6.
  3. Start buffering PHP’s output.
  4. Run the code to process the request.
  5. Save the contents of the buffer to a file, with a unique filename.
  6. Return result to the user.

Where Cached Output Will Be Saved

To save the output, I’m going to be creating a file for each unique request. For this application, the request will be unique based on the GET parameters passed. To do this, I’ll be creating an MD5 hash of an alphabetical list of GET keys and values. Here’s the function:

function cache_key() {
  $keys = array();
  foreach($_GET as $key => $value) {
    $keys[] = $key . "=" . $value;
  }
  sort($keys);
  return md5(implode('&', $keys));
}

function cache_filename() {
  globals $cache_dir;
  return $cache_dir . '/' . cache_key() . '.cache';
}

Please note that this will have to be customized based on what exactly you’re caching. For instance, if you’re caching individual pages, you may want to create the key using the path to the page. Whatever it is you’re using, just make sure it is unique and consistent for each page.

Checking The Cache

When a request is made, it’s necessary to first check to see if it has already been cached, and, if it has, whether the cache hasn’t expired. I’ll be using the filesystem to achieve this:

$cache_time = 15*60; // 15 minutes in seconds

function cache_exists() {
  globals $cache_time;

  if(@file_exists(cache_filename()) && time() - $cache_time < @filemtime(cache_filename())) {
    return true;
  } else {
    return false;
  }
}

So what exactly is going on here? If you take a close look, we’re using the file_exists() (docs) and filemtime() (docs) functions to see if the cache file already exists and, if it does, whether it’s recent enough to serve (in this case, if it’s less than 15 minutes old, the function returns true). I’m placing @ signs before these two functions so that, if they fail, it doesn’t return an error. Instead, the function will just return false and the code will run as if no cache file exists.

Putting It All Together

Now, it’s time to get everything working together. First, a couple of necessary functions for saving and reading the cache:

function read_cache() {
  return file_get_contents(cache_filename());
}

function save_cache($value) {
  $fp = @fopen(cache_filename(), 'w');
  @fwrite($fp, $value);
  @fclose($fp);
}

Now, a few calls to wrap around your code:

function start_cache() {
  if(cache_exists()) {
    echo read_cache();
    exit();
  } else {
    ob_start();
  }
}

function stop_cache() {
  $data = ob_get_clean();
  save_cache($data);
  echo $data;
}

And to implement it, this is all you need to do:

start_cache();
// Your code that needs to be cached
stop_cache();

And you’re done! All-in-all, it’s a very simple way to achieve a very powerful result.

Drawbacks to This Method

The first thing you want to keep in mind when using this caching method is that it caches the entire page. This can be good: if everyone visiting the page sees the same content anyways, why not cache it for everyone? However, if you’re serving a page that appears different to different users, it can be a bad idea. For instance, what if an administrator visits the page, and it gets cached? When the next non-administrator visits, they’re going to see all the administration information. Bad news.

Also, due to the simplicity of this method, there’s no way to easily expire the cache of a single page. Let’s go back to the blog entry example. If you decide to make a change to the entry, you’ll have to wait at least 15 minutes before the cache is cleared, or you have to go in and delete all the cache files (since it’s difficult to determine which file goes with which page). For many applications, this probably won’t an issue, but it’s something to keep in mind.

Download the Source

Hopefully, this was helpful. If you’d like to download the entire source, you can grab it here.

Posted on September 16, 2008 11 Comments
Tagged with: , , , , , ,

PHP Facebook Paginator

While building the Viddler Facebook application, I needed to create a pagination tool that worked just like Facebook’s. After looking at their HTML and their logic of what pages to display, I came up with one, which I’m releasing for anyone to use. The function takes 5 arguments, in the following order:

  • $base_path: The base path for pagination. For instance, if /videos/4/ was page 4, $base_path would be “/videos”
  • $cur_page: The current page number
  • $total_items: The total number of items on the page
  • $per_page: The number of items that are displayed on each page
  • $footer_bar: If set to “true” this does not include the “Displaying items 1-5 of 10” text, and styles it as a footer paginator, instead of a header paginator.
  • $name: What you’re paginating. This shows up as “Displaying $items 5-10 of 40”

Here’s an example:

<?php echo paginator('/videos', 2, 44, 5, false, 'items'); ?>

And here’s what it would look like:

Paginator example

You can download the source here. Hopefully this helps someone!

Posted on July 8, 2008 6 Comments
Tagged with: , , , ,

A Review of CakePHP

CakePHP Logo Recently, on a somewhat secret project, I’ve had the opportunity to try out CakePHP, an MVC framework for PHP. I wanted to take some time to share my thoughts on what I’ve liked and disliked in my experience so far.

Missing Migrations

My #1 favorite feature in Rails has to be migrations. They allow you to very easily add and remove fields of a database without trying to keep track of SQL queries. In CakePHP, nothing like this exists (at least that I’m aware of). I’ve already had numerous issues with databases being out of sync due to having the code in three different environments (dev, staging, and production). I’ve had to resort to a combination of phpMyAdmin and text files with SQL to keep track of all the changes that need to be made.

Models Fall Short

Personally, I’m a huge subscriber to the “fat model, skinny controller” method of structuring code, which stipulates that the majority of code should reside in your models (which handle the data), rather than the controllers and views. With CakePHP, however, this is a little difficult to implement. When querying the database using a model like $this->User->findAll();, the function just returns a text array, rather than an array of objects like Rails does.

To demonstrate my point, let’s pretend I have a table of users that stores their first name as first_name and last name as last_name. I want to print out their first name, last name, and their full name (which is generated from their first and last name). With Rails, I just add the method User#full_name to the User model. Since the full object is passed to the view, I can call that method directly:

First Name: <%= @user.first_name %><br />
Last Name: <%= @user.last_name %><br />
Full Name: <%= @user.full_name %>

However, with CakePHP, since it’s only an array that’s passed, I can’t call a function, and the code gets a little messy:

First Name: <?php echo $user['User']['first_name']; ?><br />
Last Name: <?php echo $user['User']['last_name']; ?><br />
Full Name: <?php echo $user['User']['first_name] . ' ' . $user['User']['last_name']; ?>

Granted, I could use a helper function in the view, but in my eyes, generating the full name is something that should reside in the model. In this example, it’s really not that big of a problem, but as you want to do more complicated manipulations of the data, it gets a little messy with CakePHP.

Messy Syntax

While it isn’t totally the fault of CakePHP, but rather of PHP itself, Cake requires some pretty messy syntax to accomplish things. For example, let’s say I have posts that have both an editor and an author. To define these relationships in Cake, I’d have to do the following:

// user.php
class User extends AppModel {
  var $name = 'User';
  var $hasMany = array('EditedPosts' => 
                        array('className' => 'Post',
                              'foreignKey' => 'editor_id'),
                        'AuthoredPosts' =>
                         array('className' => 'Post',
                               'foreignKey' => 'author_id')
                      );
}

// post.php
class Post extends AppModel {
  var $name = 'Post';
  var $belongsTo = array('Editor' => 
                          array('className'  => 'User',
                                'foreignKey' => 'editor_id'),
                         'Author' =>
                          array('className'  => 'User',
                                'foreignKey' => 'author_id')
                        );
}

Pretty confusing, huh? All the arrays really get in the way. In Rails, the same situation would be done like this:

# user.rb
class User < ActiveRecord::Base
  has_many :edited_posts,   :foreign_key => 'editor_id',
                            :class       => 'Post'
  has_many :authored_posts, :foreign_key => 'author_id',
                            :class       => 'Post'
end

# post.rb
class Post < ActiveRecord::Base
  belongs_to :editor, :class => 'User'
  belongs_to :author, :class => 'User'
end

To me, the Rails code is a lot easier to read and makes much more sense. Like I said before though, this is really a Ruby vs PHP issue and not Rails vs CakePHP.

Super Easy Deployment

It may seem like I’m really ragging on CakePHP, but there are definitely things I like compared to Rails. One of my (and many others’) major gripes about Rails is how difficult it is to deploy an application: there’s a lot of server side configuration that can get really confusing. With CakePHP, however, it’s practically a non-issue. Since that supports PHP (which I’m guessing is 99% of shared hosting). Just upload your files, and you’re ready to go. With Rails, setting up a production environment would require setting up Apache forwarding, Mongrel instances, and more. In this department, Rails really can’t compete with Cake.

More Resources for the Beginner

PHP has perhaps the best online documentation of any language out there. Every function is well documented, and most have user comments below with tips and explanations on various quirks. It’s also a very widely used language, which means a lot more people can help you out if you have issues. Even though CakePHP itself isn’t particularly well documented, it definitely benefits from the many PHP resources out there. Ruby, on the other hand, though things are getting better, isn’t nearly as well documented as PHP and doesn’t really have a central place to go like PHP.net. There also aren’t nearly as many people that are familiar with the language.

CakePHP is Beginner-Friendly

Though CakePHP is lacking in a many areas where Rails really excels, I think it definitely has its place in the world of web frameworks. I would definitely consider using it on smaller-scale projects where I don’t want to deal with the complicated setup of a Rails production environment. It’s also a little more accessible for beginners than Rails because of the vast PHP community. However, for a user requiring a more powerful framework, I currently don’t think there’s much out there that’s better than Rails.

Posted on April 6, 2008 8 Comments
Tagged with: , , , ,