Building an Object Collection Manager with the Standard PHP Library (SPL)

Introduction

The purpose of a collection is to store objects in an organized manner with specific access rules. We are going to build a collection class using the Standard PHP Library (SPL). Our final product will be capable of iterating, counting and access to objects via array. If you are not familiar with SPL you can find some additional information on the PHP SPL manual site. Unfortunately the manual is somewhat lacking. Here is a more detailed list of SPL classes.

We will call our class “Collection” (original, I know). This class will eventually implement the SPL classes: ArrayAccess, Countable and Iterator. If you are unfamiliar with SPL your first reaction may be that you don’t have any of these classes in your project. The beauty of SPL is that the classes are built into PHP (compiled by default). Once you implement a class you are exposed to a series of abstract function which you must implement. Once implemented the result is the ability to manipulate objects and create functionality you wouldn’t normally be able to code.

First we are going to do a quick introduction to the interfaces we will be using. After that we will jump right into code and examples. Please keep in mind that the collection class we are going to build is more about demonstrating the capabilities of these particular SPL interfaces then actual practical use, although you could easily turn this into a useful class.

ArrayAccess
ArrayAccess is an interface that lets you override array access of objects. Basically it lets you use an object as an array and use keys to access values stored within the class.

Abstract Functions

offsetExists($offset)
offsetGet($offset)
offsetSet($offset, $value)
offsetUnset($offset)

Countable
Countable lets you hook into the global count function and override it. We are going to use it to count the number of objects in our collection.

Abstract Functions

count()

Iterator
Iterator allows you to iterator over an object. In our example you will be able to directly loop over a collection and return the objects that are stored.

Abstract Functions

current()
key()
next()
rewind()
valid()

Implementation

The first thing we need to do is define our class. It should look something like this:

<?php
class collection {
   
}
?>

Next we will define a constructor and a private variable called $object that will store the objects we pass into this collection. The constructor will be one way to populate the collection.

class collection {
    private $objects = Array();
   
    public function __construct($objects) {
        if(!empty($objects)) {
            $this->objects = $objects;
        }
    }
}

At this point our class doesn’t do much but take an array of objects and store then in the class. Next we will implement the SPL classes we discussed earlier.

Implement ArrayAccess

class collection implements ArrayAccess {
    private $objects = Array();
   
    public function __construct($objects) { ... }
   
    /** ABSTRACT SPL ArrayAcess **/
    public final function offsetExists($offset) {
        if(isset($this->objects[$offset])){
            return true;
        } else {
            return false;
        }
    }
   
    public final function offsetGet($offset) {
        return $this->objects[$offset];
    }
   
    public final function offsetSet($offset, $value) {
        if($offset) {
            $this->objects[$offset] = $value;
        } else {
            $this->objects[] = $value;
        }
    }
   
    public final function offsetUnset($offset) {
        unset($this->objects[$offset]);
    }
    // End ArrayAccess Abstraction
}

Implement Countable
Countable is really simple to implement and only requires one function. There is an interesting note in the code comment that you should make note of.

class collection implements ArrayAccess, Countable {
    private $objects = Array();

    public function __construct($objects) { ... }

    /** ABSTRACT SPL ArrayAcess **/
    ...
    // End ArrayAccess Abstraction
   
   
    /** ABSTRACT SPL Countable **/
    public function count() {
        // Note from PHP manual: If var is not an array or an object with
        // implemented Countable interface, 1 will be returned.
       
        // Not sure why it returns one, but we need to make sure our
        // count is right and returns zero when there are no objects
        if(empty($this->objects)) {}
            return 0;
        } else {
            return count($this->objects);
        }
    }
    // End Countable Abstraction

Implement Iterator

class collection implements ArrayAccess, Countable, Iterator {
    private $objects = Array();
   
    public function __construct($objects) { ... }
   
    /** ABSTRACT SPL ArrayAcess **/
    ...
    // End ArrayAccess Abstraction

   
    /** ABSTRACT SPL Countable **/
    ...
    // End Countable Abstraction
   
   
    /** ABSTRACT SPL Iterator **/
    public function current() {
        return current($this->objects);
    }

    public function key() {
        return key($this->objects);
    }
   
    public function next() {
        return next($this->objects);
    }
   
   
    public function rewind() {
        if(empty($this->objects)) {
            return false;
        }
           
        return reset($this->objects);
    }
   
   
    public function valid() {
        if(empty($this->objects)) {
            return false;
        } else if(current($this->objects) === false) {
            return false;
        } else {
            return true;
        }
    }
    // END Iterator Abstraction
}

Final Collection

Our completed class isnt all that complicated. But its extremely powerful if used the right way. Hopefully you were able to follow along and understand what we have built. The final code is posted below as well as a test case in the next section. I recommend running the test to really see what the class is capable of doing.

<?php

class collection implements ArrayAccess, Countable, Iterator {
    private $objects = Array();
   
    public function __construct($objects) {
        if(!empty($objects)) {
            $this->objects = $objects;
        }
    }
   
    /** ABSTRACT SPL ArrayAcess **/
    public final function offsetExists($offset) {
        if(isset($this->objects[$offset])){
            return true;
        } else {
            return false;
        }
    }
   
    public final function offsetGet($offset) {
        return $this->objects[$offset];
    }
   
    public final function offsetSet($offset, $value) {
        if($offset) {
            $this->objects[$offset] = $value;
        } else {
            $this->objects[] = $value;
        }
    }
   
    public final function offsetUnset($offset) {
        unset($this->objects[$offset]);
    }
    // End ArrayAccess Abstraction

   
    /** ABSTRACT SPL Countable **/
    public function count() {
        // Note from PHP manual: If var is not an array or an object with
        // implemented Countable interface, 1 will be returned.
       
        // Not sure why it returns one, but we need to make sure our
        // count is right and returns zero when there are no objects
        if(empty($this->objects)) {
            return 0;
        } else {
            return count($this->objects);
        }
    }
    // End Countable Abstraction
   
   
    /** ABSTRACT SPL Iterator **/
    public function current() {
        return current($this->objects);
    }

    public function key() {
        return key($this->objects);
    }
   
    public function next() {
        return next($this->objects);
    }
   
   
    public function rewind() {
        if(empty($this->objects)) {
            return false;
        }
           
        return reset($this->objects);
    }
   
   
    public function valid() {
        if(empty($this->objects)) {
            return false;
        } else if(current($this->objects) === false) {
            return false;
        } else {
            return true;
        }
    }
    // END Iterator Abstraction
}
?>

Testing and Demonstration

Test Code

<?php

/** FOO CLASS USED TO POPULATE OUR TEST COLLECTION */
class Foo {
    public $value;
   
    public function __construct($value) {
        $this->value = $value;
    }
}


/** DEMONSTRATION **/
$objects = Array();
for($x = 0; $x < 5; $x ++) {
    $objects[] = new Foo($x);
}

$collection = new Collection($objects);

// offsetExists
if(isset($collection[3])) {
    echo "Offset 3 exists\n";
} else {
    echo "Offset 3 doesnt exist\n\n";
}

// offsetGet
echo "Value of offset 1: " . $collection[1]->value . "\n";

// offsetSet
$collection[3] = new Foo('A');
echo "Offset 3 has been changed, the object value is now: " . $collection[3]->value . "\n";

// offsetUnset
unset($collection[2]);
echo "Offset 2 has been removed. ";

if(isset($collection[2])) {
    echo "Offset 2 exists\n";
} else {
    echo "Offset 2 doesnt exist\n\n";
}


// Countable
echo "This collection has " . count($collection) . " objects\n\n";


// Iterating
echo "Looping through our collection:\n";
foreach($collection as $key => $object) {
    echo "Index " . $key . " = " . $object->value . "\n";
}

?>

Test Output

Offset 3 exists
Value of offset 1: 1
Offset 3 has been changed, the object value is now: A
Offset 2 has been removed. Offset 2 doesnt exist

This collection has 4 objects

Looping through our collection:
Index 0 = 0
Index 1 = 1
Index 3 = A
Index 4 = 4

Android is going to beat the iPhone

There is little argument that the iPhone is the best smartphone on the market. With 85,000 application available at the AppStore, there is significantly more interest in this mobile platform then any other. The iPhone has fundamentally changed how users interact with their mobile devices. The last year has seen explosive growth. Right now, the iPhone is the phone to beat. But I don’t think it will last…

1) My first point will represent my weakest. AT&T sucks. Period. The service is abysmal. I’ve heard numbers that say 30% of New York calls end in a drop. My NY experience has likely been around there, not to mention all the text/voicemail delays and straight up service outages. The point is AT&T cant handle its current subscribers, let alone continuous growth. In their defense, no one could have predicted that iPhone users would consume six times more data than typical mobile users and eat up all the bandwidth. This doesn’t only affect iPhone users, all AT&T subscribers suffer.

Apparently AT&T is spending $17 billion dollars this year to upgrade their infrastructure. This sounds impressive, except last year they spent something like $20 billion and every other year they spend a ton of money as well. So don’t be fooled by this large sum. Whats interesting is that AT&T is always playing catch up. Years ago when the GSM network was released, a ton of their subscribers jumped ship because of the poor service. It took them years before they get things sorted out. Now it’s happening all over again. If they don’t get their act together I think you will start seeing subscribers run to the exit signs. Keep in mind all second generation iPhone users are still under a two year contract till at least June. It will be interesting to see what happens as contracts start to expire and the market starts to offers iPhone alternatives.

2) Apple appears to not be learning from their mistakes. I’m talking about the PC wars from back in the 80’s. Long story short, Apple lost because they wanted to control everything. You are seeing the same thing happen with the iPhone. They don’t want to give up control and open up the platform. I’m not even talking open source. But developers and users don’t feel like they have control of their devices. Apple just doesn’t trust it’s users. Because of Apple and AT&T we may never see the full potential of the device because they hamper innovation so much. To be fair its not just AT&T, nothing is worse for innovation then mobile network providers.

There hasn’t yet been a back lash from the developer community yet. But you can clearly hear there frustration. The reason people are putting up with it is because there is no where else to go. Right now if you want to build mobile apps, you go where the people are. And the people are on the iPhone. Plus no other platform has an equal equivalent to the AppStore. But the AppStore isn’t what iTunes is to music, it can be reproduced and potentially successful.

3) Some predictions say Android will be the most popular mobile OS within 5 years. Most popular aside, I predict there will be more Android devices then iPhone devices within two years. Android took 5% of the smartphone market share 3 months after launch. As far as I’m concerned the g1 was Googles way of tasting the market. Now that there is interest, I foresee an outbreak of Android devices… There are over 15 announced and rumored Android devices coming out in the next 6 – 12 months.

I’m not predicting that any one handset will out perform the iPhone, although I’m super excited for the Motorola Droid. I am predicting that Android devices as a whole will eventually consume more market share then the iPhone. More devices + more carriers + more countries = more potential customers. If things continue as they are, I think Android is positioned to eventually rival Nokia as the default smartphone OS. Once developers realize theres is or will be more Android users. You will start to see significant more development and innovation for the Android OS. The only way I see the iPhone fighting for a contending spot is by dropping their bad habits. Learn from your mistakes and stop being an over protected parent that doesn’t trust its kids. And for gods sake get on some other networks, stat.

CollegeHumor is hiring a PHP Engineer

Connected Ventures is seeking an ambitious, well-rounded PHP engineer. You will work closely with our team of developers on a number of large and high profile websites. The Connected Ventures properties include: CollegeHumor, Bustedtees, TodaysBIGthing and SportsPickle. Collectively our sites reach over 12 million worldwide unique monthly users. Connected Ventures is part of InterActiveCorp (IAC), a leading internet company with more than 50 fast-growing web properties.

We’re searching for someone who has:

* Fluency in PHP 5 and the LAMP stack
* MySQL with performance tuning and scalability
* Strong understanding of Object Oriented Programming
* Hands on experience with MVC Frameworks
* Caching / Scaling (Memcached, APC, etc.)
* Thorough understanding of HTML, Javascript, CSS and XML
* An understanding of the issues surrounding large-volume websites and scalability
* A preoccupation with staying on top of industry trends and technologies

Bonus Points:

* Experience with SPL a major plus
* e-commerce, credit card authorizations, and billing feature experience
* Linux or Solaris
* jQuery or Prototype
* Content Delivery Networks
* Version Control Systems (we use Subversion)
* Experience building scalable frameworks

Responsibilities:

* Implement major new features and feature improvements
* Track and squash bugs
* Optimize code and queries for better performance
* Database Architecting
* Work hard and play hard!

This is an on site, full time position at our office in Union Square, New York City. Please email your resume and code samples to techjobs@connectedventures.com. Please no recruiters or development shops.

Forcing files to download from Amazon S3

I have been messing around with Amazon S3 for hosting files the last few days. One of the things I wanted to do was force a file to download instead of the browser attempting to open it (jpgs, txt, mp3, etc.). A quick Google search didn’t return anything useful. There is a whole bunch of people trying to use PHP headers and read in the file from Amazon. This is a horrible idea. It requires your server to download the file first, then deliver it to the client. Double work, bad solution.

There is a much easier way… S3 allows you to set request headers on a per object basis. In order to force a file download dialog you just need to set the “Content-Disposition” header.

Here’s an example using the Undesigned Amazon S3 PHP class (highly recommended). This header should easily integrate into whatever system you are using to interact with the S3 API. If you need additional information take a look at the S3 documentation.

S3::putObject(S3::inputFile($FILE), $bucket, $uri, S3::ACL_PUBLIC_READ, array(), array('Content-Disposition' => 'attachment; filename=' . $filename));

How to destory a Subversion repository in one line

One of our developers recently taught us how to hose a subversion repository. I wasn’t even aware you could actually cause this much damage. He accidentally ran this “svn remove –force .“. At first glance I made the assumption that this would delete everything in his working copy. That much was true, but it went as far up as the repo trunk. It deleted everything, we weren’t even able to do a roll back. When I ran an “svn update” in my working copy is said the repository didn’t exist. We had to create a whole new repository and import all the settings, tickets and commit data by hand. My advise: never run that command. I’m also surprised Subversion allows for such a command without better validation or permission control.

Installing Flash 10 64bit plugin for Firefox on Ubuntu

I recently setup Ubuntu 9.04 on my 64bit AMD machine. The biggest headache aside from the ATI driver issues was getting Flash 10 to work in Firefox. At the time of this writing Flash 10 64bit for Firefox 64bit is still in Alpha.

1) Remove all pre existing flash installations and files

sudo apt-get purge flashplugin-nonfree gnash gnash-common mozilla-plugin-gnash nspluginwrapper swfdec-mozilla

2) Go to http://labs.adobe.com/downloads/flashplayer10.html and find the most up to date release. (http://download.macromedia.com/pub/labs/flashplayer10/libflashplayer-10.0.32.18.linux-x86_64.so.tar.gz)

3) Download and extract it

wget http://download.macromedia.com/pub/labs/flashplayer10/libflashplayer-10.0.32.18.linux-x86_64.so.tar.gz && tar xvfz libflashplayer-10.0.32.18.linux-x86_64.so.tar.gz

4) Move the plugin file

sudo mv libflashplayer.so /usr/lib/mozilla/plugins/libflashplayer.so

5) Restart Firefox and browse to “about:plugins” to confirm that it installed correctly.

Extension Development Survey

I was recently asked to participate in a Mozilla extension development survey being conducted by professor Amrit Tiwana at the Iowa State University. You can download the preliminary results here. The final results will be released in the fall. If your an extension developer its worth the read. The results are released under the Creative Commons license, so feel free to share.

A few fun findings

  • 59% of developers are unlikely to port to competing commercial browsers, while less than half will consider porting to competing open-source browsers.
  • 85% of extensions in the study were open source
  • Most (66%) extensions have an intermediate degree of modularity with the browser; only one in three have
    high modularity.
  • Major extension upgrades rarely trigger integration and browser stability problems.

CollegeHumor is hiring!

We are looking for an exceptional PHP developer to join our talented team here at Connected Ventures. You will be working across our network of sites which include; CollegeHumor, Bustedtees and TodaysBIGthing. Combined they do over 5 million page views a day and reach over 500,000 visitors. This is an on site, full time position at our office in Union Square, New York City. Email your resume and PHP code samples to techjobs@connectedventures.com. Please no recruiters or development shops.

Requirements:
* PHP
* MySQL
* Strong understanding of Object Oriented Programming
* MVC Frameworks
* Caching / Scaling (Memcached, APC)
* Subversion
* E-commerce
* Javascript, HTML/CSS, XML
* Linux or Solaris experience a plus

What’s it like to work here?
It’s confusing when you can’t tell the difference between “work” and “play;” “friends and co-workers;” “office” and “cool place to hang out.” Confusing is about as bad as it gets here at Connected Ventures, where Nerf-gun fights happen, ‘casual Friday’ starts Monday at 10 and ends Friday at 6, and “work” means collaborating with your buddy at the dual screen computer next to yours. Now, don’t assume, however, that it’s all just fun and games here: the CollegeHumor family labors diligently all the time, they just happen to enjoy their business more than most people.

PriceAdvance Upgrades

PriceAdvance Screenshot

We launched some major changes to PriceAdvance yesterday. We improved merchant coverage from 15 websites to over 150 (full list), added support for shipping price, stock availability and rebate offers. Download it for Firefox (or IE) and leave a review!

How we cache at CollegeHumor

CollegeHumor, like many websites that want to reduce database requests and speed up processing, uses memcached as a caching layer. This article will explain our software implementation and discuss a number of things we learned along the way. If you read my blog you will know I’m a PHP guy. CollegeHumor is also coded in PHP. None of the concepts covered in this post will be specific to any particular programming language. There will be a few simple PHP examples.

Let’s jump right into the class setup… Our cache class is a completely static class and can’t be instantiated on its own. When our application starts up a create_instance function is called which will create a connection to memcache in a static object stored in our class. Shortly after the instance is created, our config file is loaded and we add the servers that make up our memcache pool. Really nothing fancy happening thus far…

Besides the create_instance and the add_server functions our class only has three main public functions; get, set and delete. When I say main, those are really the only three functions we use throughout our application, everything else is for internal use by the cache class. There are also a few logging and stats functions which aren’t important for this article.

Set Function

public static function set($key, $value, $expire = -1) {
    if ($expire == -1)
        return true;

    // there was no key passed, to be safe we will create one
    if (!strlen($key))
        $key = md5($value);

    $value = self::falsify($value);

    self::instance_set($key, $value); // set the instance cache
    self::$memcache->set($key, self::dogpile_set($value, $expire), MEMCACHE_COMPRESSED, $expire + self::EXTENDED_LIFE); // set memcache
}

Let me explain what’s going on and why… The set function takes 3 parameters: $key, $value and $expire.

1) Check the $expire value. If the expiration is less than zero (we default to -1) we assume this wasn’t meant to be cached and we just return true.

2) Make sure there was a $key passed. If there isn’t a key, we play it safe and set $key equal to the MD5 of the $value. Otherwise, there is a chance of key collisions. We definitely don’t want that.

3) We run the $value through a function called “falsify”. We found that storing the actual value of “false” caused a number of issues. The first being that we interface very closely with our database layer. If our get function returns false, the database layer assumes it didn’t find a value in cache and does a query (more on the solution to this in the get section). Second, the get function will return false when it fails to find data. The solution was the falsify function. It looks at the value and if it’s false the value is turned into a special string we can later identify. Something like “<-F4LS3->”. Basically a value that would never normally be stored. Later on in the get function we will use this to identify the difference between a stored false and not finding results.

4) We created a local instance cache that stores any data we get/set in a static array. This helps prevent unnecessary subsequent requests for data we may have already retrieved. We do this by passing the key/value to an instance_set function which stores the data by its key.

5) Sites with high loads are often subject to the dogpile effect. Basically, if something falls out of cache and two or more users make the same request and receive a miss from memcache, they end up performing the same operation (usually a database query) to refresh the data. For smaller sites the extra database work is fairly minor. But larger sites under heavy load often can’t afford this. Think about something falling out of cache on the homepage of a large site. This could generate hundreds of the same query, locking up tables and eventually bring the database to a crawl. I have seen it, it’s not that awesome.

The way we deal with this problem is by passing the value and expiration time to a function called set_dogpile. This function is simple and you will understand its purpose later on… All this function does is create an array with the key “v” and stores the $value and a key called “t” where we store the $expire and then returns the array.

private static function dogpile_set($data, $expire) {
    return array('t' => time() + $expire, 'v' => $data);
}

6) Finally we can write our data to memcache! We set the key to the $key we were originally passed and the value to the array that was returned from dogpile_set. The expiration time is slightly more complicated. Our cache class has a constant variable called “extended_life”. We use this in conjunction with the dogpile effect prevention. Our extended life is set to 300 seconds. This will be better explained in the get section below, bare with me for now. Set the expiration to the $expire value passed in + the extended_life.

*Its important to note that the version of data stored in the instance cache (Step 4) is the original data, not the dogpile array.

Get Function

public static function get($key, &$found = null) {
    if(is_array($key)) {
        // Not used for the purpose of this article
        // $value = self::get_complex($key);
        } else {
            $value = self::get_simple($key);
        }

        if($value === false) {
            $found = false;
           
            return false;
        } else {
   
        $found = true;

        if(is_array($value))
            return $value; // complex response (not used in this article)
        else
            return self::defalsify($value); // simple response
    }
}

The get function takes two parameters: The first is the $key and the second is an optional referenced parameter called &$found (default to null).

Our get function can take two types of keys. Either a single key request as a string or multiple key requests as an array. The first thing we do in the get function is determine the key type. If it’s a string we send the key off to the “get_simple” function. If it’s an array it goes to the “get_complex” function. For simplicity reasons we are only going to focus on the get_basic function.

If you do explore get_complex on your own, keep in mind that memcache can take an array of keys and retrieve those results in a single request. You will also need to keep track of what is and isn’t found from the instance cache. That way we can do lookups from memcache on the missed keys. There is an example of get_complex at the very end of this article, it should be easy to understand after you grasp get_basic.

Get Simple

private static function get_simple($key) {
    $value = self::instance_get($key);

    // instance cache returned nothing, look it up
    if(!$value) {
        $value = self::dogpile_get($key, self::$memcache->get($key));

    if(!empty($value)) {
        // results were found in memcache, lets add it to the instance cache
        self::instance_set($key, $value);
    } else {
        // nothing was found, return
        return false;
        }
    }

    return $value;
}

1) First the key is passed to the “get_instance” function. This function checks our static instance array to see if we have already performed a get or set for that key. If data is found, return it, otherwise return false.

We are back in the get_basic function now. If the value returned from get_instance isn’t false, we return the value back to “get”.

2) If the value returned from get_instance is false we need to ask memcache for the data. You should do that now.

Whatever the response is from memcache we will pass the key and the data to the dogpile_get function.

private static function dogpile_get($key, $data) {
    if(!empty($data)) {
        $value = $data['v'];

        if($data['t'] > 0) {
            if(time() >= $data['t']) {
                $data['t'] = time() + self::DELAY; // Update the cache time

                // set the stale value back to memcache for a short 'delay' so no one else tries to write the same data
                self::$memcache->set($key, $data, MEMCACHE_COMPRESSED, self::DELAY);

                return false;
            }
        }

        return $value;
    }

    return false;
}

a. First we need to make sure the value isn’t an empty array. If it fails that check we assume the data is corrupt or snuck into memcache and we return false. If everything looks good, extract the value from the “v” index and the time from the “t” index.

b. Make sure the “t” value is greater then 0. If its not, return the “v” value.

c. Check if the current time is greater than or equal to the “t” value. This is where our dogpile magic happens. The extended_life we set earlier gives us an extra few minutes to let the application determine what’s stale and what’s valid. The “t” value is the actual time we want the data to expire even thought we told memcache to store it for an extra few minutes. Since we got this far, it means the data is meant to expire. To prevent everyone and their mother from reloading the cache we are going to temporarily store the old stale data back in memcache while this user has the opportunity to refresh it.

We need to update the “t” value of the data array that was passed in to be the current time + $delay. Delay is a constant integer; we have out set to 30 seconds (change as needed). This delay is our dogpile buildup prevention. You will see how in a moment… Now we write the stale data back to memcache. Set the key to the $key that was passed in, the value to our updated $data array and the expiration to $delay. Now for the next 30 seconds all subsequent requests will receive the stale data (shouldn’t be a big deal for most sites). The user who found the stale data now has 30 seconds to complete an update. If she fails to do so, the next user to make the request will have that opportunity. Return false, because we want to the request to think we found nothing and fetch fresh data.

3) Now that we have our response and have dealt with dogpiling we need to analyze the response. If the value from get_simple is false we need to set &$found to false and return false. The found variable does exactly as you probably guessed. It tells us if the get function really found something (even if the value was stored as false). It’s a referenced variable, so we can easily use it outside of the cache class.

If the response is not false we set &$found to true and pass the data off to the defalsify function, which compares the value to our constant “false identifier” (<-F4LS3->). If the value equals the identifier, return false. Now the “get” function can finally return the results.

Delete Function

The delete functionality is simple compared to the get/set functionality. The goal is to delete the data from the instance cache and from memcache. This is easily shown with an example.

public static function delete($keys) {
    if(!is_array($keys))
        $keys = array($keys);

    if($keys) {
        foreach($keys as $key) {
            self::$memcache->delete($key);
            self::instance_delete($key);
        }
    }
}

Conclusion

I hope this article gives you a better insight into some more advanced caching techniques. I, nor CollegeHumor claim to have invented any of these techniques or methods. This was a high level overview of how our caching layer functions and I hope someone learned something from it. I know it might be difficult to follow all the operations, unfortunately I cant post the entire class code. But, if you have any questions about implementation, caching or anything PHP, feel free to contact me or post a comment.

Get Complex

private static function get_complex($keys) {
    $results = array();
    $missing = array();
    $num_keys = count($keys);

    // lets see if we can find any of these in the instance cache first
    for($x = 0; $x < $num_keys; $x++) {
        $key = $keys[$x];
        $value = self::instance_get($key);
       
        if(!$value) {          
            // nothing was found in the instance cache, lets create a list to look them up in memcache         
            $missing[] = $key;     
        } else {           
            // we found what we were looking for in instance cache!            
            $results[$key] = self::defalsify($value);      
        }  
    }  
   
    if(empty($missing))        
        return $results; // we found everything we need in instance cache  
       
    // look up anything thats missing in memcache  
    $values = self::$memcache->get($missing);

    if(!empty($values)) {
        foreach($values as $key => $value) {
            $value = self::dogpile_get($key, $value);

            if($value) {
                $results[$key] = self::defalsify($value);

                self::instance_set($key, $value);
            }

        }
    } else {
        // we didnt find what we needed in memcache
        return false;
    }

    return $results;
}