Mission: Find all the items in a users shopping cart that expired 3 minutes ago.

If you want to cheat just scroll to the last code block. ;) Now for the setup.

This is a common query for any ecommerce website that wants to keep things “fresh” or run some analytics. Private sales sites are fond of this because an item in a shopping cart means that a customer is holding up inventory. That of course is a bad thing. This is just one of MANY reason why searching by date range is important.

So how do we handle this with Lithium our RAD PHP 5.3+ framework and MongoDB? To answer this lets use the mission above as our basis and spit out some code. Since you didn’t skip ahead lets build up our example just a little.

Let’s start in a BaseModel that will hold our core code. We will first assume that we want to query for some standard time values. To get things started we can setup a human readable protected array:

	protected $_dates = array(
		'now' => 0,
		'-1min' => -60,
		'-3min' => -180,
		'-5min' => -300,
		'15min' => 900
That times above are second that we will either add or subtract from the php method time(). Our calculation will basically be time() + $_dates['key']. The result of that calculation needs to be converted into a MongoDate object. This is important for several reasons. One good reason is that its easier for us to query using dates if Mongo knows we are looking for date. We may also save ourselves some trouble if more native MongoDB temporal calculations are added to the core.

Using the MongoDate object you can do things like greater than ($gt), less than ($lt) etc, etc and well etc. So how do we create this object? We need to instantiate the date with the Mongo Driver PHP MongoDate class. Since we are using static methods in our Model we’ll use the following code to create and return our MongoDate based on the name-key we pass the method. We can place this method in the BaseModel as well.
	public static function dates($name) {
	     return new MongoDate(time() + static::_object()->_dates[$name]);
That done, we can now save and query for data. The code below demonstrates how we can add some created and expire MongoDates to a cart document. The really important part to glean here is that we are calling our static method to create the MongoDate. This method will live in our CartModel which can extend the BaseModel.
	public static function addDates($product, array $options = array()) {
		$product->expires = static::dates('15min');
		$product->created = static::dates('now');
		return static::_object()->save($product);

Note: The MongoDate object will look something like “Thu Aug 05 2010 01:32:41 GMT-0400 (EDT)”. This throws a few folks off thinking its just a string.

Now for our mission. Don’t worry I’ll repeat it here to save you from looking: Find all the items in a users shopping cart that expired 3 minutes ago. How would we do that in the CartsController? All we need to do is call a static method in the CartModel that does the search and let it know what range we are looking for:

    $cart = Cart::search(array('time' => '-3min'));

We’re going to let our model do the heavy lifting to get the data.

	public static function search($params = null) {
		$time = (!empty($params['time'])) ? $params['time'] : 'now';
		$user = Session::read('userLogin');
		return static::all(array(
			'conditions' => array(
				'session' => Session::key(),
				'expires' => array('$gte' => static::dates($time)),
				'user' => $user['_id'])),
			'order' => array('expires' => 'ASC')

There is a bit going on here so lets break it down. First we are checking to make sure that the time is there and set it if it’s not. Then for the sake of finding a specific users cart item we doing some ID grabbing from the PHP Session. Next is the query part: Give me all the user cart items that are greater than X date.

Notice in the code above that we are calling static::dates($time) to fetch our MongoDate. This will let MongoDB in turn know that we are searching for a date and query for it appropriately. This helps to cut out all the epoch timestamp manipulation in our code to properly search for values.

Whew! Now you are an expert and have enough ammunition to use MongoDates to the full.

I’ve been tweeting quite a bit about MongoDB over the past few weeks and its time to blog.

About 2 months ago we decided to install and mess around with MongoDB. We went from messing around to serious adoption about 2 weeks ago when we realized the power of working with it and PHP. It was Mitch Pirtle of www.spacemonkeylabs.com that first pointed us in this direction. Mitch was very enthusiastic about Mongo as he explained the great potential. Yet, it was only until I added an array of data from PHP into Mongo that my eyes started to open up.

Go ahead, open another tab in your browser (KEEPING THIS ONE OPEN) and Google MongoDB if you haven’t already (I made it easy providing the link). You’ll find sufficient documentation to give you the warm and fuzzy that 10gen isn’t messing around. They are in the database game for the long haul.

During our play with MongoDB we realized how true the term NoSQL is. For all you SQL grease monkeys out there it’s liberating. No Joins, No Select, just plain NoSQL.

As an example, let’s start off with putting uploaded files “directly” into MongoDB via GridFS (the storage specification for handling large files). Here is how simple it is:

First, we are going to assume you already have the code in place to handle the upload itself. We first tested this using SWFUpload which provides quite a bit of flexibility if you like to control how your page looks during upload. From a PHP perspective, the uploaded file will be accessible via the predefined variable $_FILES.

Here is what you have to do to get the upload into MongoDB:


$m = new Mongo(); //connect
$db = $m->selectDB("example"); //select Mongo Database

$grid = $db->getGridFS(); //use GridFS class for handling files

$name = $_FILES['Filedata']['name']; //Optional - capture the name of the uploaded file
$id = $grid->storeUpload('Filedata',$name) //load file into MongoDB


Yep, that was it. One thing that took me a “second” to realize is that you actually pass the literal name of the file_post_name (in the example ‘Filedata’) to MongoDB. It does all the heavy lifting of getting the data from the system and storing it. Also, take note that you get an $id back which is the MongoID that acts as the “primary key”. That makes it easy for you to reference the file right away if you need to.

So that’s a rather quick look and tip for MongoDB. Stay tuned  as we continue to put out our MongoDB tricks and tips for PHP. We’re in it for the long haul too.