MongoDB + CodeIgniter 101 (Part 1)

So I’ve decided that I want to properly document my MongoDB exploration and I may as well help others to learn with me.

Big disclaimer: I’m no MongoDB expert, I really am starting from scratch so if I get something wrong or you know of a better a way I’d love to hear from you =)

What I’m going to do here in part 1 is install MongoDB, install the PECL PHP extension and just play around a bit.

So lets get going.

Installing MongoDB

First we need to install Mongo (if you haven’t already). I’m working on a Ubuntu 9.04 server so I added the 10gen repository to my Aptitude repo list.

nano /etc/apt/sources.list

Add the following to a new line

deb http://downloads.mongodb.org/distros/ubuntu 9.4 10gen

(Take a look here to see some alternative Ubuntu + Debain sources http://www.mongodb.org/display/DOCS/Ubuntu+and+Debian+packages)

Save the file (Control + X)

Then run

apt-get update
apt-get install mongodb-stable

Wait a few minutes and MongoDB is installed! For reference, the Mongo database path is located at /var/lib/mongodb/ and the configuration script is at /etc/mongodb.conf

Installing MongoDB PECL extension

Install the PHP developer package (if you haven’t already)

apt-get install php5-dev

Then run

pecl install mongo

Working directory setup

Set up your working directory and install the latest stable CodeIgniter snapshot

cd /public_html
mkdir mongo101
cd mongo101
svn co http://svn.ellislab.com/CodeIgniter/trunk/
mv trunk/* ./
rm -r trunk

Lets go!

Open your favourite text editor (mine is Coda) and setup a new project etc etc.

Open /application/config/config.php and change the $config[‘base_url’] to wherever your install is.

Open /application/config/route.php and change the default controller to blog -$route[‘default_controller’] = “blog”;

Delete the welcome.php controller in the application/contollers folder

Delete the welcome.php view in the application/views folder

Create a new controller called blog.php

<?php

class Blog extends Controller {

 function Blog()
 {
   parent::Controller();
 }

 function index()
 {
 }

}

In your index function write

function index()
{
 // Connect to Mongo
 $connection = new Mongo('localhost:27017');

 // Select a database
 $db = $connection->blog;

 // Select a collection
 $posts = $db->posts;
}

Save the file and go to http://your-server/index.php/blog

If you’ve got a blank screen then it means you’ve successfully connected to Mongo, selected a database and selected a collection within the database.

Right, time for some concepts. So in Mongo a database is as it sounds, a database, and a collection is like a table in MySQL in that it holds rows (except rows are documents in Mongo terms). The PHP Mongo extension is to some extent an object relationship mapper which means we could have quite easily gone $posts = $connection->blog->posts instead.

Now that we’ve covered the basics and we know everything is working lets turn this into something a bit more useful.

Edit the Blog (constructor) function in your controller.

function Blog()
{
parent::Controller();
$this->load->helper('url');
// Connect to Mongo
$this->connection = new Mongo('localhost:27017');
// Select a database
$this->db = $this->connection->blog;
// Select a collection
$this->posts = $this->db->posts;
}

Here we’re making the posts collection globally available so we don’t have to repeat ourselves in every function with the connection code.

Add a new function to your controller:

function add()
{
  $data = array();

  // If we've submitted the form...
  if($this->input->post('add'))
  {
  // Build the document
  $document = array(
    'id' => ($this->posts->count() + 1),
    'title' => $this->input->post('title'),
    'body' => $this->input->post('body'),
    'author' => $this->input->post('author'),
    'date' => time()
  );

  // Insert the document
  $this->posts->insert($document);

  $data['inserted'] = TRUE;
  }

  // Load the form
  $this->load->view('add', $data);
}

When the form is submitted we build an array and then insert that into our collection. You’ll see to generate the ID we asked Mongo to return the number of existing documents in the collection and added one to it.

Create a new view file add.php

<!DOCTYPE html>
<html>
 <head>
 <title>MongoDB 101 (Part 1)</title>
 </head>
 <body>

 <h1>Add a new post</h1>

 <?php
 if($inserted):
 ?>
 <p><strong>Post inserted successfully!</strong></p>
 <?php
 endif;
 ?>

 <form method="post">

 <p>
 <label for="title">Title</label><br/>
 <input type="text" name="title" value="" />
 </p>

 <p>
 <label for="body">Body</label><br/>
 <textarea name="body"></textarea>
 </p>

 <p>
 <label for="author">Author</label><br/>
 <input type="text" name="author" value="" />
 </p>

 <p>
 <input type="submit" name="add" value="Add" />
 </p>

 </form>

 <p><a href="<?php echo siteurl(array('blog', 'add')); ?>">Add a post</a></p>
 <p><a href="<?php echo siteurl(array('blog', 'index')); ?>">View all posts</a></p>

 </body>
</html>

Now go to http://your-server/index.php/blog/add and you should be able to add a new post (you’ll see a success message if it worked). Just add the one post for now.

Add another function to your blog.php controller:

function view()
 {
 $this->load->helper('date');
 $this->load->helper('typography');

 // Load the document
 $document = $this->posts->findOne();

 $data = array(
 'title' => $document['title'],
 'body' => $document['body'],
 'author' => $document['author'],
 'date' => $document['date']
 );

 $this->load->view('view', $data);
}

What is happening here is we are asking Mongo to return just one document (which is returned as an array). We then pass this onto a new view.php file

<!DOCTYPE html>
<html>
 <head>
 <title>MongoDB 101 (Part 1)</title>
 </head>
 <body>

 <h1><?php echo $title; ?></h1>

 <?php echo auto_typography( $body ); ?>

 <p><em>Posted by <?php echo $author; ?> on <?php echo unix_to_human( $date ); ?></em></p>

 <p><a href="<?php echo siteurl(array('blog', 'add')); ?>">Add a post</a></p>
 <p><a href="<?php echo siteurl(array('blog', 'index')); ?>">View all posts</a></p>
 </body>
</html>

Now go to http://your-server/index.php/blog/view and you’ll see the post you just added.

Let’s adapt the view function so it can return a specific blog post.

function view()
 {
 $this->load->helper('date');
 $this->load->helper('typography');

 // Load the document
 $document = $this->posts->findOne(array('id' => (int)$this->uri->segment(3)));

 $data = array(
 'id' => $document['id'],
 'title' => $document['title'],
 'body' => $document['body'],
 'author' => $document['author'],
 'date' => $document['date']
 );

 $this->load->view('view', $data);
}

Here we added a query array into the findOne function. Note we typecasted the uri segment as an integer, this is because internally Mongo stores documents as BSON (binary JSON) and as our id field was is an integer we need make sure we’re being strict and querying it as an integer.

If you now go to http://your-server/index.php/blog/view/1 you should see your blog post.

To round this brief introduction to MongoDB off we’re going to edit the original index controller function to list the most recent five blog posts in date descending order.

function index()
 {
 $this->load->helper('date');
 $this->load->helper('typography');

 // Find 5 documents
 $documents = $this->posts->find()->limit(5);

 // Sort them by time descending
 $documents = $documents->sort(array('date' => -1));

 $data = array(
 'documents' => array()
 );

 // While we have results
 while($documents->hasNext())
 {
 // Get the next result
 $document = $documents->getNext();

 $data['documents'][] = array(
 'id' => $document['id'],
 'title' => $document['title'],
 'body' => $document['body'],
 'author' => $document['author'],
 'date' => $document['date']
 );
 }

 $this->load->view('index', $data);
}

Some thing to note here, up until the point you start iterating over results (the while loop) you can play with the query to your hearts content. Also when you sort things, to sort ascending replace ASC with 1, and DESC with -1.

This is the index.php view file:

<!DOCTYPE html>
<html>
 <head>
 <title>MongoDB 101 (Part 1)</title>
 </head>
 <body>

 <?php
 if(count($documents) > 0):
 foreach($documents as $document => $vars):
 ?>

 <h1><a href="<?php echo site_url(array('blog','view', $vars['id'])); ?>"><?php echo $vars['title']; ?></a></h1>

 <?php echo auto_typography( $vars['body'] ); ?>

 <p><em>Posted by <?php echo $vars['author']; ?> on <?php echo unix_to_human( $vars['date'] ); ?></em></p>

 <hr />

 <?php
 endforeach;
 endif;
 ?>

 <p><a href="<?php echo site_url(array('blog', 'add')); ?>">Add a post</a></p>
 <p><a href="<?php echo site_url(array('blog', 'index')); ?>">View all posts</a></p>
 </body>
</html>

Awesome

So to wrap-up we’ve installed MongoDB and the PHP extension for it. Then we’ve created a basic blog using the CodeIgniter work. Time for a beer!

For more information on the PHP Mongo functions see http://uk.php.net/manual/en/book.mongo.php

For more information on MongoDB see http://www.mongodb.org/

To download my code go to http://alexbilbie.com/mongo101.zip

Interview with MongoDB developer [podcast recommendation]

On the way to Norfolk today I listened to a podcast I’d not heard of before called Techzing. Episode 39 (the most recent) featured a fantastically geeky interview with one of the 10gen developers who works on MongoDB which I talked about in my last post.

If you’re interested in web development, scalability, databases and the NoSQL movement then I’d definitely recommend listening if you get a chance.

You can listen/download the episode here http://techzinglive.com/?p=192 and this is the iTunes podcast subscription link http://itunes.apple.com/gb/podcast/techzing/id318567721