MongoDB Tutorial Part 6: Finding One Unique Document

The Devil Is In The Details

 

In our last tutorial we improved our treasure list to sort our items. But the treasure list only includes the name, price and type of all of our items. What about the rest of the information we put in the database, like wand spells and weapon bonuses? We want to see that too! So today we’ll be adding a way to look at all the details relating to the treasures in our database.

 

If you remember back to the design document our end goal is to let users click on treasure names in order to visit a page that lists all the treasure information. So our two tasks for today are creating those links and creating the page they need to point to.

 

Uniquely Identifying MongoDB Documents

 

If we want to create a page that shows the full details of one specific treasure the first thing we’ll need is a way to identify which treasure we want the details on. We can’t just tell MongoDB to “give us a ring” because it might give us the wrong ring. And even working with someone more specific, like treasure names, might not be enough. What if we have more than one item with the same name, like two “Swords of Fire” with different prices and weapon bonuses? How do you make sure you get the right one out of the database?

 

The answer is simple. Remember the _id field from way back in tutorial part 3, that random looking string of letters and numbers that was automatically added to every item we pushed into the database? That special id will be unique for every item in your collection. So if you know an item’s _id you can find it in the database without having to worry about accidentally grabbing the wrong item. Even if you have multiple identicle “Swords of Fire” they will each have a unique _id to help you keep track of which is which.

 

Getting the _id

 

You access the _id of a MongoDB document the same way you access any other attribute. In fact, if you copied my sample index.php file you already have it! Does this line look familiar?

 

$treasureID = $document['_id'];

 

If you’ve been writing your own pages instead of copying mine then now is the time to add a line like this to your code. Our goal is to get that _id for every item in our list and then create a link with that _id.

 

This is an intermediate tutorial, so I trust you’re already familiar with creating links. In our case we want to connect to a page named “viewItem.php” and that we want to include an “id” in the URL. To create that sort of link in our treasure list we need to open index.php and find the line where we print the treasure name. We then want to replace the plain name of the treasure:

 

<td>$name</td>

 

With an option that wraps the name inside of link. Notice the id argument we’ve added after the main url:

 

<td><a href='viewItem.php?id=$treasureID'>$name</a></td>

 

If you don’t see where you need to make the change, scroll to the bottom of this tutorial for a complete updated version of index.php

Creating viewItem.php

 

A link to viewItem.php isn’t very useful without an actual viewItem.php file to visit. If you tried clicking on a treasure name right now you would just get a page not found error. So our next step should be to create an actual viewItem.php file inside the same folder as the rest of our code. And of course we’re going to start out by inclding our header and footer:

 

include('header.php');
//Item view code will go here
include('footer.php');

 

Searching Based On _id

 

To search our database for a specific _id we need to have an _id to search for. But if you wrote your links properly we should be able to grab it out of the URL as easy as:

 

$itemID = $_GET['id'];

 

But wait, there’s more! In PHP MongoDB ids actually have their own class and that string of numbers and letters won’t do you any good until you wrap them up in the MongoId class:

 

$searchableItemID = new MongoId($itemID);

 

We’re almost there. Now all we have to do is tell MongoDB that we want to do a specific search for an item with that _id.

 

Searching in MongoDB is easy and uses the find function that you’re already familiar with. All you have to do is create a set of key/value pairs and include them as an argument to find and you’re done.

 

Let’s start by creating our key/value pair:

 

$criteria['_id'] = $searchableItemID;

 

And now we feed that data into find:

 

$cursor=$c->treasurebag->treasure->find($criteria);

 

find VS findOne

 

Remember how find returns a cursor instead of actual data? And how we had to put the cursor inside a loop to read all the data? That’s kind of pointless when you know you’re only to get one result. Why run an entire while loop just to grab one line of data? Calling $cursor->next() even once feels like a waste.

 

Luckily for us there is findOne. It works just like find except that it returns one result and it directly returns result data instead of returning a cursor. This makes it really useful for those situations where you’re only expecting one result or where you only care about the first result in a list. So instead of creating a cursor it’s much more convenient to use findOne to grab the needed treasure data all in one place.

 

$itemAttributes=$c->treasurebag->treasure->findOne($criteria);

 

Testing

 

Let’s give this a try and make sure our code is working by creating a page that grabs and id from the URL, creates a MongoID with it, searches for that id and then dumps the results to screen. Give it a try on your own first.

 

Ready to see my answer?

 

<?php
include('header.php');
$itemID = $_GET['id'];
$criteria['_id'] = new MongoId($itemID);
$c = new Mongo();

$itemAttributes=$c->treasurebag->treasure->findOne($criteria);

print_r($itemAttributes);

include('footer.php');
?>

 

If you’ve done everything right then this code should produce a simple page that dumps out all the information related to whatever treasure is in the URL. Feel free to play around with it by going back to the index and clicking on the viewItem links for other items and making sure that they dump different data. When you’re sure it’s working we can move on.

 

Display Item Details All Pretty Like

 

Grabbing the id and searching the database were the hard part. Now we just have the busy work of putting that information on the screen in a way that is prettier than a pure data dump.

 

Thinking back to our design document we notice that every single item is guaranteed to have a name, price and type. So the first thing we should probably do is print that to the screen:

echo '<b>Name:</b> '.$itemAttributes['Name'].'<br/>';
echo '<b>Type:</b> '.$itemAttributes['Type'].'<br/>';
echo '<b>Price:</b> '.$itemAttributes['Price'].'<br/>';

 

After printing the generic data for the item the next step is to print the type specific information. What information goes with each type is listed in our design document, so if you need a reminder just go back to tutorial 2.

 

The only sort-of tricky question here is how to handle attributes that can have more than one value. Printing a weapon bonus is as simple as just printing the weapon bonus but what do we do for the multiple Spells inside of each staff? The obvious answer is to put them into an unordered list, which we can build pretty easily with a foreach loop.

 

Now let’s take a shot at the type specific information printer. I’m using a switch statement here to choose which information to print based on the type of the item:

 

switch($itemAttributes['Type']){
  case 'Ring':
   echo '<b>Special Attribute:</b>'.$itemAttributes['Special Attribute'].'<br/>';
      break;
  case 'Weapon':
      echo '<b>Bonus:</b> '.$itemAttributes['Bonus'].'<br/>';
      echo '<b>Special Attributes:</b>';
      echo '<ul>';
      foreach($itemAttributes['Special Attributes'] as $specialAttribute){
          echo "<li>$specialAttribute</li>";
      }
      echo '</ul>';
      break;
  case 'Wand':
      echo '<b>Charges:</b> '.$itemAttributes['Charges'].'<br/>';
      echo '<b>Spell:</b> '.$itemAttributes['Spell'].'<br/>';
      break;
  case 'Staff':
      echo '<b>Charges:</b> '.$itemAttributes['Charges'].'<br/>';
      echo '<b>Spells:</b>';
      echo '<ul>';
          foreach($itemAttributes['Spells'] as $spell){
              echo "<li>$spell</li>";
          }
      echo '</ul>';
      break;
}

 

And that should be enough code to properly display the full attributes of every treasure in the system.

 

Screen Shots

See all those weird letters and numbers in the URL? That's the Mongo ID

See all those weird letters and numbers in the URL? That’s the Mongo ID

 

This sword has more than one special attribute

This sword has more than one special attribute

Complete Code

Updated index.php

<?php
include('header.php');

$connection = new Mongo();
$cursor = $connection->treasurebag->treasure->find();
if(isset($_GET['sortby'])){

    if($_GET['sortby']=='Price'){
        $cursor->sort(array('Price'=>1));
    }
    elseif($_GET['sortby']=='Name'){
        $cursor->sort(array('Name'=>1));
    }
}

echo '<h2>Inventory</h2>';
echo '<button onclick="window.location.href=\'index.php?sortby=Name\'">
                           Sort By Name</button>';
echo '<button onclick="window.location.href=\'index.php?sortby=Price\'">
                           Sort By Price</button>';
echo '<table><tr><td>Name</td><td>Price</td><td>Type</td></tr>';
while($document = $cursor->getNext()){

    $treasureID = $document['_id'];
    $name = $document['Name'];
    $price = $document['Price'];
    $type = $document['Type'];

    echo "<tr><td><a href='viewItem.php?id=$treasureID'>$name</a></td>
                <td>$price</td>
                <td>$type</td></tr>";

}
echo '</table>';

include('footer.php');
?>

Complete viewItem.php

<?php
include('header.php');
$itemID = $_GET['id'];
$criteria['_id'] = new MongoId($itemID);

$c = new Mongo();

$itemAttributes=$c->treasurebag->treasure->findOne($criteria);

echo '<b>Name:</b> '.$itemAttributes['Name'].'<br/>';
echo '<b>Type:</b> '.$itemAttributes['Type'].'<br/>';
echo '<b>Price:</b> '.$itemAttributes['Price'].'<br/>';

switch($itemAttributes['Type']){
 case 'Ring':
  echo '<b>Special Attribute:</b> '.$itemAttributes['Special Attribute'].'<br/>';
   break;
 case 'Weapon':
   echo '<b>Bonus:</b> '.$itemAttributes['Bonus'].'<br/>';
   echo '<b>Special Attributes:</b>';
   echo '<ul>';
      foreach($itemAttributes['Special Attributes'] as $specialAttribute){
          echo "<li>$specialAttribute</li>";
      }
   echo '</ul>';
        break;
 case 'Wand':
   echo '<b>Charges:</b> '.$itemAttributes['Charges'].'<br/>';
   echo '<b>Spell:</b> '.$itemAttributes['Spell'].'<br/>';
   break;
 case 'Staff':
   echo '<b>Charges:</b> '.$itemAttributes['Charges'].'<br/>';
   echo '<b>Spells:</b>';
   echo '<ul>';
      foreach($itemAttributes['Spells'] as $spell){
         echo "<li>$spell</li>";
      }
   echo '</ul>';
   break;
}

include('footer.php');
?>

Conclusion

So now we can sort and study all the treasure in our database. But at the moment the only way to put treasure into the system in the first place is to type it by hand straight into MongoDB. That’s not very user friendly! So join me next time as we write a page for entering treasure information through your browser instead of the command line.

 

BONUS!

 

What happens to itemView.php if you don’t include and id in the URL or if you type in an id not in the system? Improve the system so that if it can’t find a matching _id it displays a polite error message about not being able to locate the requested treasure.

 

BONUS BONUS!!

 

If you’ve been doing the bonus work so far you should already be storing adventurers in your database and have a page that shows them all in a list. Now add a characterView.php file to your application that lets you click on an adventurer and see his detailed stats. If you don’t have any detailed stats, then add some. A bio field with a quick character description might be an easy extra detail to add.

MongoDB Tutorial Part 5: Sorting Data

Last week we put together the main page of Treasure Bag and managed to get it to successfully list all the treasure in our database. You might also remember that we included some useless sorting buttons. Well this week we’re going to get those buttons working!

 

Sorting With MongoDB in PHP

 

Unsurprisingly, you sort using a function named sort. The only tricky part is knowing where to use it and what arguments to pass it. MongoDB’s sort function belongs to the cursor class. That’s the same cursor class that is returned by the find function. Remember this line of code?

 

$cursor = $connection->treasurebag->treasure->find();

 

That line gave us a cursor pointing at all our treasure. Now we just have to tell the cursor that we want the data sorted. Here’s a quick example of how to sort our data based off of treasure name.

 

$cursor->sort(array('Name'=>1));

 

Now let’s take this apart. $cursor->sort tells us that we’re calling the sort function built into the cursor class. We then tell the sort function how to sort by giving it an array holding a key value pair where the key is the attribute we want to sort by and the value is positive 1 for sorting forwards and -1 for sorting in reverse.

 

Did you catch all that? Here’s a quick question to check. How would you sort a cursor based off of price, starting with the most expensive item?

 

Do you have your answer ready? Let’s see if you got it right:

 

$cursor->sort(array('Price'=>-1));

 

Feel fry to try this out by adding a sort line into your Tresure Bag index.php file. Just make sure that you put it after the call to find (or else you won’t have a cursor to sort) but before the getNext while loop (or else you’ll print the data before sorting it).

 

Letting PHP Know Which Sort of Sort We Want

 

Hard coding a sort is OK if you always want the list to show up in the same order. But we want to be able to switch our sort order between name and price. That means we’re going to need some way to mark which sort we want to happen.

 

The easiest way is probably to just include the kind of sort we want as a variable in the URL and then grab it using the PHP $_GET array. So if we want to sort by name we would change the URL to this:

 

index.php?sortby=Name

 

And if we wanted to sort by price we would use:

 

index.php?sortby=Price

 

Now instead of hard coding our sort we can change our sort based on the URL. I’m sure you’ve all seen this PHP pattern before:

 

if(isset($_GET['sortby'])){

    if($_GET['sortby']=='Price'){
        $cursor->sort(array('Price'=>1));
    }
    elseif($_GET['sortby']=='Name'){
        $cursor->sort(array('Name'=>1));
    }
}

 

Nothing too amazing here. We check whether or not the user gave us a “sortby” variable and then based off of that we choose which kind of sort to run on our cursor. If the user didn’t ask for a sort or asked for a sort we don’t support (sortby=something_else) then we don’t call sort at all and just display the treasure in whatever order the database feels like giving them to us in.

 

You can test this out by hand typing in the following urls:

 

No sorting: index.php

Sort by name: index.php?sortby=Name

Sort by price: index.php?sortby=Price

 

Once you’ve made sure that you can successfully control your sorts by passing URL variables we’re ready for the last step.

 

Making The Buttons Useful

 

No MongoDB here. Just a tiny bit of Javascript. We’re going to add an “onclick” event to our buttons that will navigate the user to a URL with the desired sort parameters. Just find the button printing code in index.php and replace them with this:

 

echo '<button onclick="window.location.href=\'index.php?sortby=Name\'">
                           Sort By Name</button>';
echo '<button onclick="window.location.href=\'index.php?sortby=Price\'">
                           Sort By Price</button>';

 

If everything worked out you should be able to click on the two “Sort By” buttons to switch between Name order and Price order. If you want to see the treasure in their original non-sorted order just click on the “Inventory” link in the top menu.

 

All our treasure, sorted by name

All our treasure, sorted by name

All our treasure, cheapest to most expensive

All our treasure, cheapest to most expensive

Complete Updated Index.php

 <?php
include('header.php');

$connection = new Mongo();
$cursor = $connection->treasurebag->treasure->find();
if(isset($_GET['sortby'])){

    if($_GET['sortby']=='Price'){
        $cursor->sort(array('Price'=>1));
    }
    elseif($_GET['sortby']=='Name'){
        $cursor->sort(array('Name'=>1));
    }
}

echo '<h2>Inventory</h2>';
echo '<button onclick="window.location.href=\'index.php?sortby=Name\'">
                           Sort By Name</button>';
echo '<button onclick="window.location.href=\'index.php?sortby=Price\'">
                           Sort By Price</button>';
echo '<table><tr><td>Name</td><td>Price</td><td>Type</td></tr>';
while($document = $cursor->getNext()){

    $treasureID = $document['_id'];
    $name = $document['Name'];
    $price = $document['Price'];
    $type = $document['Type'];

    echo "<tr><td>$name</td>
                <td>$price</td>
                <td>$type</td></tr>";

}
echo '</table>';

include('footer.php');
?>

That’s it for sorting. A quick short lesson. Join me next week as we add a whole new page that shows detailed information on a single treasure.

MongoDB Tutorial Part 4: Reading The Database With PHP

Finally Some Code!

Last week we ran through a couple exercises to help get you familiar with the data structure of MongoDB. This week we’re going to start learning how to access MongoDB using PHP.

 

Simple Page Template

 

There are a few chores we need to get out of the way before we can start doing actual database work. Like creating a space on our server for out project. I just dropped a “treasurebag” directory right into my web server’s www folder.

 

Next, if you remember back to the document design you’ll see that every single page is supposed to have the same same title and navigation menu, so it makes sense to create a single file to hold all this common data.

 

We’re also going to need some simple style rules to make the application look a little better. We could place this in a separate CSS file, but since we only have a few dozen lines of style rules we might as well just include it in the same page as the header. And since this isn’t a design tutorial I’m not really going to say much about the styling. We give the page a parchment color background to fit the fantasy theme, add some black outlines to tables and center some text. Nothing complex or beautiful but it does make everything a little more readable.

 

Header

 

This should all go into a file named “header.php”.

<!DOCTYPE html>
<html>
<head>
    <title>Treasure Bag</title>
    <style>
        body{
            background:#F1F1D4
        }
        #header-wrapper{
            width:800px;
            text-align:center;
            border: solid black 3px;
        }
        #header-wrapper table{
            width:100%;
        }
        #header-wrapper td{
            width:33%;
            text-align:center;
            padding: 20px;
        }

        #content-wrapper{
            width: 800px;
            padding: 20px;
        }

        #content-wrapper table{
            border-collapse:collapse;
            width:500px;
            margin-top:10px;
        } 

        #content-wrapper td{
            border: solid black 1px;
            padding: 5px;
            margin: collapse;
        }
    </style>
</head>
<body>
<div id="header-wrapper">
<h1>Treasure Bag</h1>
<table>
    <tr>
        <td><a href="index.php">Inventory</a></td>
        <td><a href="add.php">Add Treasure</a></td>
        <td><a href="search.php">Search</a></td>
    </tr>
</table>
</div>
<div id="content-wrapper">

Footer

 

There really isn’t anything we need to display at the bottom of every page, but the header has a lot of HTML elements that never get closed. So we’ll create a very simple footer file that clears all that up so that we don’t have to worry about it.

 

Please put these three lines into a file named “footer.php”.

        </div>
</body>
</html>

Using Our Template

 

Using our page template is very simple. You just insert the header, add your content and then use the footer to finish everything up.

 

<?php
include('header.php');
//YOUR CONTENT HERE
include('footer.php');
?>

 

Preparing PHP For MongoDB

 

Now that that’s out of the way we can get back to thinking about databases. But before we can start using MongoDB in our PHP applications we have to install a few extra PHP features. Just like with installing MongoDB itself this process will be a little different for every system and it’s up to you to figure it out. You probably want to start with the official page on MongoDB PHP support.

 

Connecting to MongoDB With PHP

 

I’m going to assume at this point that you’ve successfully installed the MongoDB PHP drivers and are ready to finally start coding. You might want to create a “sample.php” file to help you test the next few lines of code. Or you can wait until the end of tutorial when we create the first real page of our Treasure Bag application.

 

The first step in pulling data out of the database is to connect to it. So any PHP script that plans on talking to MongoDB needs this somewhere near the top:

 

$connection = new Mongo();

 

The reason this is so simple is because PHP assumes that you’re running MongoDB on “localhost” and with the default port. So you only have to tell PHP where to find your database if you’re doing something more complex, like trying to connect to a remote database. In that case you’ll have to give PHP a little more information about where to look.

 

$connection = new Mongo(“mongodb://yourremoteurlhere.com”);

 

Reading An Entire Database

 

Now that we have a connection, what do we do with it? How about we dump the contents of the entire “treasure” collection onto our page? After last week’s tutorial there should be at least a few documents in there we can look at.

 

You might remember that we were able to dump data on the command line by first choosing which database we wanted to use, then choosing a collection and finally calling find:

 

use treasurebag
db.treasure.find()

 

The PHP solution is very similar:

 

$db = $connection->treasurebag;
$db->treasure->find();

 

Alternatively, you can choose the database and the collection both in the same line like this:

 

$connection->treasurebag->treasure->find();

 

That’s not so bad. We’re using arrows instead of dots but otherwise everything looks the same. But wait! There’s one big difference. Using find in MongoDB just immediately dumped all of our treasure documents onto our screen. But in PHP find will give us a cursor, a special object that will give us documents one at a time. To dump everything on the screen we will have to take that cursor and keep asking for more documents by using getNext():

 

$cursor = $connection->treasurebag->treasure->find()

while($document = $cursor->getNext()){
   print_r($document);
}

 

If you’ve been following along then visiting your “sample.php” page should show you a big lump of text with whatever data is still in your “treasure” collection from the last tutorial. If you get an error or just aren’t seeing anything try these troubleshooting tips:

    • Make sure there is data in the “treasure” collection of the “treasurebag” database by firing up the Mongo console tool like we talked about last week.
    • Make sure that you remembered to connect to MongoDB before trying to create the cursor. $connection = new Mongo() needs be the first thing in your script!
    • Double check your spelling for database and collection names. One tiny typo is all it takes to start looking through a blank database instead of the real “treasurebag” database.

 

Accessing Individual Pieces Of Data

 

Just dumping our documents onto the page isn’t very practical. What if we only want to see part of a document? Like the name of a treasure or its price? The answer is pretty easy and I bet most of you figured it out just by looking at the print_r output from the example above.

 

For those of you who didn’t, remember that a MongoDB document is made up of key value pairs. To get a specific piece of data you just ask for the key you want and get back the value stored with it:

$document['key'];

 

So if you want to print out the name of an item all you have to do is:

echo $document['Name'];

 

Or if you need to store a price for later:

$price = $document['Price'];

 

Creating the Treasure Bag View Page

 

That’s everything you need to know to create the main “index.php” page of our Treasure Bag application. We have the page template, the MongoDB connection and we know how to use the cursor. All that’s left is to put it together. I’ve included my complete solution right below but you might want to try creating your own “index.php” page first. If you look at the design document you’ll see that we want to put all the treasures into a table that shows their name, price and type. And don’t forget to use our header and footer files!

 

Anyways, here’s my “index.php” file, which I saved to the same directory as “header.php” and “footer.php”:

<?php
include('header.php');

$connection = new Mongo();
$cursor = $connection->treasurebag->treasure->find();

echo '<h2>Inventory</h2>';
echo '<button>Sort By Name</button>';
echo '<button>Sort By Price</button>';
echo '<table><tr><td>Name</td><td>Price</td><td>Type</td></tr>';
while($document = $cursor->getNext()){

	$name = $document['Name'];
	$price = $document['Price'];
	$type = $document['Type'];

	echo "<tr><td>$name</td>
                  <td>$price</td>
                  <td>$type</td>
              </tr>";

}
echo '</table>';

include('footer.php');
?>

Screenshot of Success!

A screenshot of the index of the Treasure Bag web application

It’s Alive!

 

Let’s walk through this screenshot really quick. Up at the top you can see the title and the navigation links from “header.php”. Underneath that we have the actual inventory table created using data from our MongoDB cursor. You’ll also notice that we added two buttons for sorting right above the table. Right now they don’t actually do anything, but our design document tells us that we need them for later so we might as well add them now.

 

And that’s it for this week! Try using the MongoDB console to add new items to the “treasure” collection and then refresh “index.php” to see them show up in all their glory. Then tune in next week as we make the sort buttons actually sort and add a page for viewing item details.

 

BONUS!

 

If you’ve been following along with the bonus activities you should have designed a character view page and have a character collection with some sample data. Create a “characterlist.php” file that connects to this collection and puts all your characters into a table or list. You will also probably want to edit “header.php” to include links to this page. You might have to alter some of the style rules to make the menu work well with four links instead of three, but I’m sure a bonus obsessed programmer like you can handle it.