MongoDB Tutorial Part 9: Final Feature Is Search

Now Where Did I Put That Wand…

 

Way back in tutorial 3 we talked about how to use find and the command line tool to search for entire groups of items inside a collection. Now we’re going to do the same thing with PHP and connect it all to a user friendly form that makes searching for treasure as easy as clicking a button. This is the last major feature of Treasure Bag. After this we’ll be done and you will have successfully built a complete MongoDB web application.

 

Bad Design Warning

 

We’re going to create the treasure search form by copy pasting and then slightly modifying the JavaScript and HTML from the treasure creation form. And in order to print our search results we’re going to copy paste the treasure listing code from “index.php”.

 

In a big, professional project duplicating code like this is a major mistake. Duplicating code means you risk duplicating bugs. And even if you don’t accidentally copy and paste a bug all over your code you might decide you need to upgrade your program and now you’re stuck finding and rewriting every piece of duplicate code.

 

If you find yourself copy pasting a certain bit of code it’s usually a better idea to turn that code into a function or a class that only has to be written once but can be used by multiple files. Then fixing bugs and making upgrades is as simple as updating one piece of code.

 

But this isn’t a big, professional project. It’s just a tiny practice project and we’re not concerned about security bugs or clients demanding new features. So in this case we can get away with being a little sloppy and copy pasting a bit.

 

Just don’t let me catch you doing it anywhere else.

 

Creating The Search Form

 

Our search form is going to be called “search.php” and will be made up of two halves. The first half of the file will be a JavaScipt enhanced HTML form that lets the user tell us what he is searching for. The second half of the file will be a PHP script that takes the user’s treasure requirements, finds any items that match and then prints them to the screen.

 

Let’s start by creating a new “search.php” file. Since this is a MongoDB tutorial I’m just going to dump the boring form code on you with minimal explanation. It’s virtually identical to the treasure creation form in “add.php” with only three slight difference.

 

First, inputs that expect numbers have been split into a Max and Min field. So instead of just asking the user for “Price” they will be asked for “Max Price” and “Min Price”.

 

Second, the form is now of type “GET” instead of “POST”.

 

Third, the drop down value for not selecting a treasure type is now the empty string ” instead of ‘none’. As you read the rest of the code I’m sure you’ll be able to figure out why we made these changes.

 

<?php
include('header.php');
?>
<script>
	function addTypeSpecificAttributes(){
		var specificAttributesDiv =  document.getElementById('typeSpecificAttributesDiv');
		var typeSelect = document.getElementById('typeSelect');
		var currentType = typeSelect.options[typeSelect.selectedIndex].value;
		switch(currentType){
			case 'Ring':
				specificAttributesDiv.innerHTML="Special Attribute:<input type='text' name='Special Attribute'/><br/>";
				break;
			case 'Staff':
				specificAttributesDiv.innerHTML="Spells (One at a time):<input type='text' name='Spells'/><br/>Min Charges:<input type='text' name='minCharges'/><br/>Max Charges:<input type='text' name='maxCharges'/><br/>";
				break;
			case 'Wand':
				specificAttributesDiv.innerHTML="Spell:<input type='text' name='Spell'/><br/>Min Charges:<input type='text' name='minCharges'/><br/>Max Charges:<input type='text' name='maxCharges'/><br/>";
				break;
			case 'Weapon':
				specificAttributesDiv.innerHTML="Min Bonus:<input type='text' name='minBonus'/><br/>Max Bonus:<input type='text' name='maxBonus'/><br/>Special Attributes (One at a time):<input type='text' name='Special Attributes'/><br/>";
				break;
			default:
				specificAttributesDiv.innerHTML='';
		}
	}
</script>

<h1>Search Treasures</h1>
<form method='get' action='search.php'>
Name: <input name='name' type='text' /><br/>
Min Price: <input name='minPrice' type='text' /><br/>
Max Price: <input name='maxPrice' type='text' /><br/>
Type: <select name='type' id='typeSelect' onchange='addTypeSpecificAttributes();'>
			<option value=''>---</option>
			<option value='Ring'>Ring</option>
			<option value='Staff'>Staff</option>
			<option value='Wand'>Wand</option>
			<option value='Weapon'>Weapon</option>
		</select><br/>
<div id='typeSpecificAttributesDiv'>
</div>
<input type='submit' value='Search' />
</form>

<?php

//MongoDB code will go here

include('footer.php');
?>

 

The Search Flow

 

That’s it for the first half of “search.php”. Now for the second, MongoDB focused half.

 

Let’s start by reviewing what we want to happen when visiting the search page. If we don’t have any user search requirements we will just display every treasure in the collection. If we do have search requirements we only want to show treasures that match.

 

Now a quick reminder on how we can use the PHP version of find both to search for items and to just display everything.

 

In order to search a collection you have to pass find an array of key/value pairs.

 

If instead you just want to access every single item in a collection you can either give find zero arguments or you can pass it an empty array.

 

This means that the easiest way to do what we want to do is to create an empty array, fill it with user search requirements and then give it to the database. If there are no user search terms the array will stay empty and we’ll get every item out of the collection, just like we wanted. If there are search requirements then find will only give us matching items, which is also what we want.

 

So let’s it get our MongoDB focused code started like this:

 

$criteria = new array();

//Fill $criteria with search criteria here

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

//Print results here

 

Let’s Copy Paste Some Print Code

 

Before we start worrying about the user’s input let’s make sure our base case with no input and a blank array works properly. For this we’re going to grab the treasure table printing code form “index.php” and drop it in near the end of “search.php”, after the database call but before we include the footer.

 

echo '<h2>Search Results</h2>';
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>';

 

If everything is working properly you should be able to refresh “search.php” and see a compelete listing of the treasures in your treasure collection.

 

Checking For Input

 

Now that you know the empty case is working it’s time to start working on what our code should do if the user has actually entered some search criteria. All the rest of the code in this tutorial belongs after declaring the empty $criteria array but before we make our call to the database, so make some space in your code and let’s get started!

 

Between our four different treasure types and all their unique attributes there are a lot of different pieces of data that might or might not be inside of the $_GET array. And for every single one of those pieces of data we’re going to do the same thing.

 

if( !empty( $_GET['ATTRIBUTE NAME'] ) ){
   //Add search term to criteria area
}

 

For example:

 

if(!empty($_GET['name'])){
   //Add name to search criteria
}

 

Using this example go ahead and write the basic if statement for all the other possible attributes. You can figure out what they are from examining the HTML form and form modification script, or you can just use this handy list (case is important): name, minPrice, maxPrice, type, Special_Attribute, minCharges, maxCharges, Spells, Spell, minBonus, maxBonus, Special_Attributes.

 

Searching For Exact Matches With MongoDB and PHP

 

Now that we can figure out which search terms the user has or hasn’t filled out we can finally start building the search criteria array. There are three major types of searches we might run into as part of Treasure Bag and I’m going to give each one their own mini-section.

 

The easiest and most straightforward search is when we are looking for an exact match. The user only wants treasures named “Orc Poker” or the user only wants items of type “Ring”. To search MongoDB for an exact match all you have to do is add the key value pair to the criteria array. Let’s use “name” as an example of how to do this in code:

 

if(!empty($_GET['name']) ){
   $criteria['Name'] = $_GET['name'];
}

 

It’s pretty simple. When we see that the user is asking for a specific name all we do is add a ‘Name’ entry to our array and assign it whatever value the user is looking for. And that’s all you need to know in order to fill out all the other exact match entries: type, Special_Attribute and Spell.

 

Greater Than And Less Than In MongoDB with PHP

 

The second type of search we’ll run into is numeric searches. These are a little more difficult because they include entire ranges or values instead of just a single exact value. For example, the user might be looking for items worth 500 or more.

 

In order to ask MongoDB to do a comparison search we actually have to put an array inside our criteria array. The keys to these inner array will be the names of special MongoDB comparison instructions and the value will be the base number we are comparing against.

 

The special MongoDB comparison instructions and what they mean are:

$gt = greater than

$gte = greater than or equal

$lt = less than

$lte = less than or equal

$ne = not equal

 

Here are a few quick samples to show how this works

//Examples only. Do not put in search.php!

//All documents with an item value greater than 500
$criteria['Price'] = array ( '$gt' => 500 );

//All documents with an item value equal to 10000 or less
$criteria['Price'] = array( '$lte' => 10000 );

//All documents with an item value between 300 and 5000
$criteria['Price'] = array ( '$gt' => 500, '$lt' => 5000);

 

But that’s not the only way to build an array inside an array in PHP. Consider this:

 

//These two pieces of code will both result in the same MongoDB search
$criteria1['Price'] = array( '$gte' => 500 );
$criteria2['Price']['$gte'] = 500;

 

For our code we are going to use the second method. This will make it easy to add a maximum search criteria to an array that already has minimum search criteria.

 

Having trouble understanding how that will work? Maybe this code will help clear things up:

 

if(!empty($_GET['minPrice'])){
   $criteria['Price']['$gte'] =intval($_GET['minPrice'])
}

if(!empty($_GET['maxPrice'])){
   $criteria['Price']['$lte'] = intval($_GET['maxPrice']);
}

 

Now follow this same pattern to fill in the code for minBonus, maxBonus, minCharges and maxCharges.

 

Searching MongoDB When Documents Have Arrays

 

The last kind of search we will run into deals with values that actually have more than one value. For example, Staffs have a “Spells” attribute that can hold multiple items. When we search for a Staff with a specific spell we don’t want to limit ourselves to Staffs that only have that one exact spell. We want to find any Staff that has that spell anywhere in it’s Spells list.

 

MongoDB has lots of different array searching tricks for the different ways you might want to search through arrays inside of documents inside of collections. Finding any array that features a particular item at least once, like we want to do, is actually one of the easiest searches to program. It even uses the exact same syntax as looking for an exact match. Here’s an example using Spells:

 

if(!empty($_GET['Spells'])){
   $criteria['Spells']=$_GET['Spells'];
}

 

Even though this code looks like something we’ve already done it’s important to know to understand that MongoDB will treat it differently . When a keyword only has one value this syntax means “Find an exact match”. But when keyword has an entire array of values this syntax means “Matches any array that has this value no matter what else is in the array”.

 

Now based off this pattern you can fill in the Special_Attributes if statement, which should be your last one.

 

No Results

 

If you’ve been messing around with your new search form you might have noticed that some searches have zero results. When this happens your code will still print the beginning of the treasure list table, it just won’t have anything besides the column names. This is pretty ugly looking. Why not change the code so that an empty result set generates an apologetic warning instead of a mutilated table?

 

In order to generate an empty result warning we’re going to need some way to tell when our results are empty. Forunately, MongoDB has us covered. Remember the cursor class that we’ve been using to cycle through search results? Well that class also has a handy little function called count that lets you know exactly how many results you have to work with. So checking for empty results is as easy as checking whether count is returning zero.

 

if( $cursor->count() == 0 ){
   echo "<h2>No Matching Treasure Found</h2>";
}
else{
   //Inventory table code you already wrote goes here
}

 

And that’s it! You’ve successfully implemented every use case in our design document. Or at least, you should have. It’s time to test whether your code actually works.

 

Testing

 

There are tons of use cases for search. Here are a few to think about:

  • Search by item type. Make sure no items of the wrong type show up. Make sure that all the items of the right type do show up.
  • For minimum and maximum values make sure that items on the edge show up. If the user asks for treasures with a minimum price of 1000 then items worth exactly 1000 should still show up.
  • Create multiple staffs that all share one spell but otherwise have different Spells arrays. Make sure that they all show up when you search for that one spell. Make sure they don’t all show up if you choose a spell that only one staff has.
  • Search for multiple attributes at the same time and make sure only items that fit all requirements show up. If you choose item type “ring” and max price 500 you should have no non-ring or above 500 items.

Screen Shots

Searching all treasure types at once to find item inside a specific price range

Searching all treasure types at once to find item inside a specific price range

The Dark Staff has multiple spells, but because at least one of those is "Darkness" it shows up for this search

The Dark Staff has multiple spells, but because at least one of those is “Darkness” it shows up for this search

Complete Code

Here is what my final “search.php” file looked like:

 

<?php
include('header.php');
?>
<script>
    function addTypeSpecificAttributes(){
        var specificAttributesDiv =  document.getElementById('typeSpecificAttributesDiv');
        var typeSelect = document.getElementById('typeSelect');
        var currentType = typeSelect.options[typeSelect.selectedIndex].value;
        switch(currentType){
            case 'Ring':
                specificAttributesDiv.innerHTML="Special Attribute:<input type='text' name='Special Attribute'/><br/>";
                break;
            case 'Staff':
                specificAttributesDiv.innerHTML="Spells (One at a time):<input type='text' name='Spells'/><br/>Min Charges:<input type='text' name='minCharges'/><br/>Max Charges:<input type='text' name='maxCharges'/><br/>";
                break;
            case 'Wand':
                specificAttributesDiv.innerHTML="Spell:<input type='text' name='Spell'/><br/>Min Charges:<input type='text' name='minCharges'/><br/>Max Charges:<input type='text' name='maxCharges'/><br/>";
                break;
            case 'Weapon':
                specificAttributesDiv.innerHTML="Min Bonus:<input type='text' name='minBonus'/><br/>Max Bonus:<input type='text' name='maxBonus'/><br/>Special Attributes (One at a time):<input type='text' name='Special Attributes'/><br/>";
                break;
            default:
                specificAttributesDiv.innerHTML='';
        }
    }
</script>

<h1>Search Treasures</h1>
<form method='get' action='search.php'>
Name: <input name='name' type='text' /><br/>
Min Price: <input name='minPrice' type='text' /><br/>
Max Price: <input name='maxPrice' type='text' /><br/>
Type: <select name='type' id='typeSelect' onchange='addTypeSpecificAttributes();'>
            <option value=''>---</option>
            <option value='Ring'>Ring</option>
            <option value='Staff'>Staff</option>
            <option value='Wand'>Wand</option>
            <option value='Weapon'>Weapon</option>
        </select><br/>
<div id='typeSpecificAttributesDiv'>
</div>
<input type='submit' value='Search' />
</form>

<?php

$criteria = array();
if(!empty($_GET['name'])){
    $criteria['Name']=$_GET['name'];
}
if(!empty($_GET['minPrice'])){
    $criteria['Price']['$gte'] =intval($_GET['minPrice']);
}
if(!empty($_GET['maxPrice'])){
    $criteria['Price']['$lte'] = intval($_GET['maxPrice']);
}
if(!empty($_GET['type'])){
    $criteria['Type']=$_GET['type'];
}
if(!empty($_GET['Special_Attribute'])){
    $criteria['Special Attribute']=$_GET['Special_Attribute'];
}
if(!empty($_GET['minCharges'])){
    $criteria['Charges']['$gte']=intval($_GET['minCharges']);
}
if(!empty($_GET['maxCharges'])){
    $criteria['Charges']['$lte']=intval($_GET['maxCharges']);
}
if(!empty($_GET['Spells'])){
    $criteria['Spells']=$_GET['Spells'];
}
if(!empty($_GET['Spell'])){
    $criteria['Spell']=$_GET['Spell'];
}
if(!empty($_GET['minBonus'])){
    $criteria['Bonus']['$gte']=intval($_GET['minBonus']);
}
if(!empty($_GET['maxBonus'])){
    $criteria['Bonus']['$lte']=intval($_GET['maxBonus']);
}
if(!empty($_GET['Special_Attributes'])){
    $criteria['Special Attributes']=$_GET['Special_Attributes'];
}

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

if($cursor->count()==0){
    echo "<h2>No Matching Treasure Found</h2>";
}
else{
    echo '<h2>Search Results</h2>';
    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');
?>

 

Why Did We Use MongoDB?

 

Now that we’ve finished up the search interface and you’ve proved to yourself that it works let’s take a moment to appreciate the flexibility of MongoDB. Do a couple searches based on minimum and maximum price. Notice how the results (probably) include a mix of all four different types of items.

 

I’ll let that sink in.

 

This entire project uses only one MongoDB collection, but we were able to keep track of four different kinds of items with it. And because we are only using one collection we can search and sort all of our treasures with only one database call.

 

To do the same thing in MySQL we would probably have to have a different table for every different type of treasure. Creating, sorting or searching a list of all of our treasures would probably require joining multiple tables together.

 

MongoDB isn’t a miracle cure and it has a few weaknesses that make it a poor choice for certain applications. But this program also shows that there are certain things that only MongoDB can do. Next time you find yourself struggling to design a database that can work well with sets of irregular data, remember the flexibility of MongoDB.

 

Conclusion

Congratulations! You’ve successfully completed my intermediate MongoDB tutorial and have proved yourself capable of using PHP and MongoDB. If you feel like you still need more practice why not go back to the start of the tutorial and complete all the BONUS exercises I included at the end of each tutorial.

 

If you’ve already completed all the BONUS exercises, I have nothing left to teach you. Go forth, programmer, and show your MongoDB prowess unto the world!

 

BONUS!

All you character database enthusiasts probably know what’s coming. Build a search form that lets you search your character database based off of character name, class, level or whatever it is you’ve been storing during your exercises.

 

BONUS BONUS!!

This has nothing to do with MongoDB, but wouldn’t it be nice if our search form automatically filled itself with the user’s current search terms instead of resetting itself every time the user hit’s search? Add some PHP to the form so that it can detect $_GET variables and fill itself out accordingly. Nothing too hard here, just tedious.

MongoDB Tutorial Part 8: Deleting Items

Here One Day, Gone The Next

 

Practically every application that lets you add items to a database is also going to need a way to remove them. A dealership just sold a car and needs it removed from their inventory database. An employee quit and needs to be removed from the staff database. Or in our case, a player has sold or lost one of his treasures and wants to remove it from his Treasure Bag.

 

Deleting data is important!

 

Fortunately, this tutorial on removing items from a MongoDB database is going to be really short and simple. If you remember back to tutorial 3 the syntax for finding items and deleting items is almost identical. Which means that if you know how to find a specific item you also know how to delete a specific item. And we already covered finding one specific item back in tutorial 6 with “viewItem.php”.

 

With that in mind, let’s review “viewItem.php” really quickly. Remember the steps we used to find a specific item based off of it’s id?

 

$itemID = $_GET['id'];
$criteria['_id'] = new MongoId($itemID);

$c = new Mongo();

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

 

delete.php

 

The code for “delete.php” is going to look very similar to the item viewing code we just reviewed. Open your editor, create a file named something like “delete.php” and type in the following code:

 

$itemID = $_GET['id'];
$criteria['_id'] = new MongoId($itemID);

$c = new Mongo();

$c->treasurebag->treasure->remove($criteria);

 

It’s identical to our item viewing code except for that last line. Instead of using findOne, like we did for item viewing, we’re using the remove function. You just give remove a set of key/value pairs and it will remove any items in the collection that match.

 

In our case, we’re passing it a unique match between _id and a specific MongoID, so it should only ever delete one item. But you can also use this function to delete multiple items all at once. For example, you could delete all Weapon type items with a call like:

 

//Deletes all weapons. Do not include in delete.php!!
$newCriteria['type']='Weapon';
$c->treasurebag->treasure->remove($newCriteria);

 

So when writing code that will use remove always double check what information you’re passing it. It’s not fun to accidentally wipe out half a collection when you actually only wanted to remove three items.

 

Finishing Up delete.php

 

Calling remove deletes the item… but now what do we do with the user? Well, if you check back to our design document we decided that after deleting an item the user should be returned to the main index. So at the very end of “delete.php” we should include this line:

 

header('Location:index.php');

 

That’s it! We’re done with delete.php.

 

Getting To Delete.php

 

You can now delete any item by visiting “delete.php?id=mongoID_of_item_to_delete”. But manually finding and typing in MongoIDs would be a nightmare. It would be much better to automatically generate delete URLs for the user.

 

Actually, if you look at the design document we originally planned on having a delete link at the bottom of every item’s detailed view. So let’s open up “itemView.php” and put it in there! All you have to do is go down near the bottom of the code, after the switch statement that prints the item but before the call to include(‘footer.php’). Then add this simple link generator:

 

echo "<br/><a href='delete.php?id=$itemID'>DELETE</a><br/>";

 

This code takes the $itemID we used to generate the detailed item view and creates a new link for deleting that item. Now deleting an item is as simple as finding on the main page, clicking its name for a detailed view and then clicking the delete link at the bottom.

 

Testing

 

Testing this is pretty straightforward. Find or create a treasure that you want to delete. Click on it’s name in the index to see the detailed view. Click on delete and make sure you get automatically returned to the index. Double check the index and make sure the treasure is gone.

 

Complete Code

 

Updated “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/>';
        if( !empty($itemAttributes['Special Attributes']) ){
            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;
}

echo "<br/><a href='delete.php?id=$itemID'>DELETE</a><br/>";

include('footer.php');
?>

“delete.php”

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

$c = new Mongo();

$c->treasurebag->treasure->remove($criteria);

header('Location:index.php');

?>

Conclusion

 

Treasure Bag is starting to feel like a real piece of software. We can add treasures, delete them, sort them and examine them. The only thing left is to build a useful search tool. So join me next time as we tackle our final challenge.

 

BONUS!

 

If you’ve been diligently doing the bonus challenges you should have a character system that lets you create and view various heroes and villains. Now create a “deleteCharacter.php” script so you can clean out unused characters in the same way you delete unneeded treasures.

MongoDB Tutorial Part 7: User Friendly Data Input

Let’s Be User Friendly

 

Our current Treasure Box web application lets us sort and examine our treasure, but we still have to use the MongoDB command line tool to manually insert new items. There are a lot of obvious problems with managing our database by hand, mostly the fact that it’s just not very user friendly. Non-programmers would be overwhelmed by the idea of typing in MongoDB commands just to add a new treasure and even us programmers tend to make mistakes when we have to type really long insert commands.

 

What we need is a nice friendly user interface. A compact little web form that prompts the user to enter treasure information and then makes sure only good data makes it into the database. Something simple enough a non-programmer can use it and smart enough that we don’t have to worry about what happens if someone has a typo. Something like the treasure creation form we prototyped way back in the design document. Today we’re going to build that.

 

Creating a Treasure Entry Form

 

If you look at our header, we already have a link for “Add Treasure” pointing to a non-existant page called “add.php”. That’s where we want to put our treasure creation form, so create a new “add.php” form and get ready to fill it with code. Our goal for the page is pretty simple. Every treasure has a name, price and type so the page needs an input field for each of those. The treasure type should be a dropdown list and once the user has chosen a specific treasure type we should add input fields for the extra attributes related to that type. So if the user chooses “Wand” from the type dropdown the form should add a “Charges” and “Spell” field.

 

Now this is a MongoDB tutorial, not an HTML or Javascript tutorial, so I’m just going to give you a simple add.php page instead of walking you through it line by line. It’s not the prettiest thing ever, nor is it the best Javascript ever built but it gets the job done and is sufficient for a practice program. Feel free to write your own input form if you need the practice or just can’t stand using anything other than perfect standards-compliant web forms.

 <?php
include('header.php');
?>
<script>
    function addTypeSpecificAttributes(){
        var specificAttributesDiv =  document.getElementById('typeSpecificAttributesDiv');
        var typeSelect = document.getElementById('typeSelect');
        var currentType = typeSelect.options[typeSelect.selectedIndex].value;
        switch(currentType){
            case 'Ring':
                specificAttributesDiv.innerHTML="Special Attribute:<input type='text' name='Special Attribute'/><br/>";
                break;
            case 'Staff':
                specificAttributesDiv.innerHTML="Spells (Comma Seperated):<input type='text' name='Spells'/><br/>Charges:<input type='text' name='Charges'/><br/>";
                break;
            case 'Wand':
                specificAttributesDiv.innerHTML="Spell:<input type='text' name='Spell'/><br/>Charges:<input type='text' name='Charges'/><br/>";
                break;
            case 'Weapon':
                specificAttributesDiv.innerHTML="Bonus:<input type='text' name='Bonus'/><br/>Special Attributes (Optional, Comma Seperated):<input type='text' name='Special Attributes'/><br/>";
                break;
            default:
                specificAttributesDiv.innerHTML='';
        }
    }
</script>

<h1>Add Treasure</h1>
<form method='post' action='processNewTreasure.php'>
Name: <input name='name' type='text' /><br/>
Price: <input name='price' type='text' /><br/>
Type: <select name='type' id='typeSelect' onchange='addTypeSpecificAttributes();'>
            <option value='none'>---</option>
            <option value='Ring'>Ring</option>
            <option value='Staff'>Staff</option>
            <option value='Wand'>Wand</option>
            <option value='Weapon'>Weapon</option>
        </select><br/>
<div id='typeSpecificAttributesDiv'>
</div>
<input type='submit' value='Add Treasure' />
</form>

<?php
include('footer.php');
?>

 

Adding Data To MongoDB With PHP

 

Do you remember how to add data to MongoDB from the command line? If not, go back to tutorial 3. You should see something a lot like this:

 

use treasurebag
db.treasure.insert( { “Name” : ”The Precious”, 
         “Price” : 10000, “Type” : “Ring”, 
         “Special Attribute” : “Invisibility”} )

 

It’s almost the same in PHP:

 

$connection->treasurebag->treasure->insert( array( “Name” => “The Precious”, 
                         “Price” => 10000, “Type” => “Ring”, 
                         “Special Attribute” => “Invisibility” ) );

 

I’m sure you can see the pattern. Choose the right database, choose the right collection and then insert a bunch of key/value pairs. Simple stuff.

 

One important thing to know is that as long as you have an array of key/value pairs it doesn’t matter whether you create the array on the same line as the insert or prepare it earlier. For example, this code will work the exact the same as the PHP sample above:

 

$insertData[“Name”] = “The Precious”;
$insertData[“Price”] = 10000;
$insertData[“Type”] = “Ring”;
$insertData[“Special Attribute”] = “Invisibility”;

$connection->treasurebag->treasure->insert( $insertData );

 

Building the key/value array separately like this can be a useful trick for keeping code clean and flexible.

 

Processing Treasures With processNewTreasure.php

 

If you look closely at the input form for add.php you’ll notice that the form sends it’s data to processNewTreasure.php. That’s the file where we’re going to be doing all of our real work so fire up your favorite code editor and create a blank processNewTreasure.php to work with.

 

Processing user input is a three step process. First, you get the user input. Second, you make sure that the input is clean and complete. If any information is missing or wrong you want to avoid putting it in the database and warn the user that they need to try again. Third, once you’re sure the data is OK, you finally insert it into the database.

 

A professional grade solution would have some heavy duty data and error checking to make sure that all user input was perfect. It would double check that things that are supposed to be numbers are numbers and that strings follow acceptable patterns. It would also have layers of error checking around all database calls to make sure that nothing went wrong while inserting or retrieving data.

 

But we’re only writing practice code, so we’re going to cut a few corners. We will make sure that the user fully filled out the treasure form, but we’re not going to obsess over exactly what they typed in. We will make sure that numbers are numbers… but if they aren’t we’ll just set them to 0 and move on instead of failing and warning the user. And we’re just going to pretend that databases never fail because on a project this small and local they usually don’t.

 

Just remember that if you were building this sort of project for a professional client you would want to kick things up a notch. I don’t want to hear any complaints about people using this sort of code on a production server and then getting in trouble when their application crashes and loses a million dollars worth if information.

 

Printing Errors

 

As I mentioned above, if the user makes an error we want to let them know so they can try again. Printing a simple message is pretty easy. We just

 
include('header.php');
//Error message here
include('footer.php');

 

That’s just three lines to create a nice looking page with our error front and center. But there are tons of places in our code where we might need to print an error message and typing the same three lines over and over again can get boring.

 

Plus, in the future we might change how we want to show errors. Maybe we don’t want to print the entire ‘header.php’ file and instead create a special new page style just for errors. In that case it would be a nightmare to have to track down and modify ever single place we displayed an error.

 

Instead we’re going to start off processNewTreasure.php with a single function that prints an error. It should accept an error message, print a page with that error message and then exit the program to make sure nothing else happens after the error. For example, if the user forgot to give a treasure a name we don’t want to bother trying to insert it into the database. We should stop right there and let the user know.

 

function printAndExit($message){
   include('header.php');
   echo $message;
   include('footer.php');
   exit();
}

 

Now every time we have an error we can rely on one quick function to warn the user and close our code before anything important can break.

 

Verifying Input

 

Every treasure has to have a name, price and type. Making sure that the user included all this information is a logical first step for processNewTreasure.php.

 

We’re going to use the PHP function empty for this task. Empty does two things for us: it checks whether a variable exists and it makes sure it has an actual value. By using this to check POST variables we can make sure that the user put an actual value into every field we care about. We’ll just do a quick check for whether name, price, or type is empty and then print an error message if they are:

 

if( empty($_POST['name']) or empty($_POST['price']) or empty($_POST['type']) ){
   printAndExit('<h1>INVALID INPUT</h1><h2>Missing treasure name, price or type</h2>');
}

 

Pretty easy to follow. If any of the three major variables are empty we print a simple HTML warning and then exit the program.

 

If none of the variables are empty, we can start building the key/value array that we’re going to insert into MongoDB. Notice that as part of creating the array we’re using the PHP function intval to make sure that price is an integer. This function will turn a string number like “123” into an actual number and will turn anything else into 0. So this way even if a user tries to create an item with a non-number price (like Price: “Pinapple”) we still end up with a nice numeric 0.

 

And while we’re at it we might as well set up a MongoDB connection:

 

$inputData['Name'] = $_POST['name'];
$inputData['Price'] = intval($_POST['price']);
$inputData['Type'] = $_POST['type'];

$connection = new Mongo();

 

Type Specific Input

 

After we’ve made sure the user has given us a name, price and type the next step is to figure out what kind of treasure the user is trying to create and double check that they included the right data types for that treasure. Ex: If they are creating a Ring they better have included a Special Attribute value.

 

Just like with itemView.php we’re going to use a switch statement to decide which code to run for which treasure types. And as a bonus, we can use the default case to catch errors where the user is trying to create a treasure with an improper type.

 

switch($_POST['type']){
   case 'Ring':
      //Process Ring
      break;
   case 'Staff':
      //Process Staff
      break;
   case 'Wand':
      //Process Wand
      break;
   case 'Weapon':
      //Process Weapon
      break;
   default:
      printAndExit('<h1>INVALID TREASURE TYPE</h1><h2>UNABLE TO ADD NEW TREASURE TO DATABASE</h2>');
}

 

Processing A Staff

 

So what exactly do we need to put into those case statements to make everything work? Let’s use treasure type Staff as an example. Staff treasures have two extra attributes besides the normal name, price and type: Charges and Spells. Charges is a number and Spells is an array of different magic spell names. So the first thing we need to do is make sure that the user included both of these bits of data.

 

if( empty($_POST['Charges']) or empty($_POST['Spells']) ){
   printAndExit('<h1>INVALID INPUT</h1><h2>Missing staff charges or spells</h2>');
}

 

Just like before, we print an error message if any of the POST variables are empty.

 

Once we know we have the data, the next step is to include it inside the key/value array we started building in the first section of the script. Because “Charges” is a number we will use intval to make sure it winds up as an integer. We will also use explode to take the user’s comma separated list of spells (ex: “healing,raise dead,light”) and expand it into the array that the database is expecting:

 
$inputData['Charges']=intval($_POST['Charges']);
$inputData['Spells']=explode(',',$_POST['Spells']);

 

The $inputData array now has a key/value pair for every Staff attribute: Name, Price, Type, Charges and Spells. All that’s left to do is push the array into the database and let the user know everything went well.

 

$connection->treasurebag->treasure->insert($inputData);
printAndExit('<h1>NEW STAFF ADDED TO TREASURE BAG</h1>');

 

Our complete case statement looks like this:

case 'Staff':
   if( empty($_POST['Charges']) or empty($_POST['Spells']) ){
      printAndExit('<h1>INVALID INPUT</h1><h2>Missing staff charges or spells</h2>');
   }
   $inputData['Charges']=intval($_POST['Charges']);
   $inputData['Spells']=explode(',',$_POST['Spells']);
   $connection->treasurebag->treasure->insert($inputData);
   printAndExit('<h1>NEW STAFF ADDED TO TREASURE BAG</h1>');
   break;

 

Processing a Weapon

 

For out next example, let’s take a look at Weapons. The Weapon type is very similar to the Staff type. Both have a numeric value (Weapons have Bonus and Staffs have Charges) and an array value (Weapons have Special Attributes and Staffs have Spells). But there is one big difference. Every Staff has to have a list of Spells, but Weapon Special Attributes are optional. We shouldn’t throw an error if the user didn’t include Special Attributes and we shouldn’t create a Special Attributes entry in our input array unless we actually have something to put there. So the Weapon case statement looks a little different:

case 'Weapon':
   if( empty($_POST['Bonus']) ){
      printAndExit('<h1>INVALID INPUT</h1><h2>Missing weapon bonus</h2>');
   }
   $inputData['Bonus']=intval($_POST['Bonus']);
   if( !empty($_POST['Special_Attributes']) ){
      $inputData['Special Attributes']=explode(',',$_POST['Special_Attributes']);
   }
   $connection->treasurebag->treasure->insert($inputData);
   printAndExit('<h1>NEW WEAPON ADDED TO TREASURE BAG</h1>');
   break;

 

Processing Rings and Wands

 

You have two examples to look at now, so I’m going to let you write the Ring and Wand case statements on your own. If you need a little extra help I have included a complete copy of my processingNewTreasure.php file down at the bottom of the page. Which shouldn’t be too far away because now that we’ve filled in all four case statements we’re basically done.

 

Testing

 

If everything went well you should be able to click on the “Add Treasure” link in the Treasure Bag menu and be taken to our simple treasure form. The form should properly adjust itself when you choose a treasure type and it should properly process new treasures.

 

To make sure that treasure processing really works try adding at least one new version of every type of treasure. You should also purposely submit some bad data to make sure we really are catching and reporting user input errors. Try to create treasures with no names, wands without spells, weapons without bonuses and so on.

 

Screen Shots

 

The treasure input form. Note how choosing "Staff" in the dropdown has added input fields for Charges and Spells

The treasure input form. Note how choosing “Staff” in the dropdown has added input fields for Charges and Spells

Whoops! The user forgot to fill out some important parts of this form.

Whoops! The user forgot to fill out some important parts of this form.

Submitting an incomplete form results in a warning screen

Submitting an incomplete form results in a warning screen

Complete Code

I already listed the complete code for “add.php” near the start of the tutorial. Here’s the complete code for “processNewTreasure.php”.

<?php

function printAndExit($message){
    include('header.php');
    echo $message;
    include('footer.php');
    exit();
}

if( empty($_POST['name']) or empty($_POST['price']) or empty($_POST['type']) ){
    printAndExit('<h1>INVALID INPUT</h1><h2>Missing treasure name, price or type</h2>');
}

$inputData['Name'] = $_POST['name'];
$inputData['Price'] = intval($_POST['price']);
$inputData['Type'] = $_POST['type'];

$connection = new Mongo();

switch($_POST['type']){
    case 'Ring':
        if( empty($_POST['Special_Attribute']) ){
            printAndExit('<h1>INVALID INPUT</h1><h2>Missing ring special attribute</h2>');
        }
        $inputData['Special Attribute']=$_POST['Special_Attribute'];
        $connection->treasurebag->treasure->insert($inputData);
        printAndExit('<h1>NEW RING ADDED TO TREASURE BAG</h1>');
        break;
    case 'Staff':
        if( empty($_POST['Charges']) or empty($_POST['Spells']) ){
            printAndExit('<h1>INVALID INPUT</h1><h2>Missing staff charges or spells</h2>');
        }
        $inputData['Charges']=intval($_POST['Charges']);
        $inputData['Spells']=explode(',',$_POST['Spells']);
        $connection->treasurebag->treasure->insert($inputData);
        printAndExit('<h1>NEW STAFF ADDED TO TREASURE BAG</h1>');
        break;
    case 'Wand':
        if( empty($_POST['Charges']) or empty($_POST['Spell']) ){
            printAndExit('<h1>INVALID INPUT</h1><h2>Missing wand charges or spell</h2>');
        }
        $inputData['Charges']=intval($_POST['Charges']);
        $inputData['Spell']=$_POST['Spell'];
        $connection->treasurebag->treasure->insert($inputData);
        printAndExit('<h1>NEW WAND ADDED TO TREASURE BAG</h1>');
        break;
    case 'Weapon':
        if( empty($_POST['Bonus']) ){
            printAndExit('<h1>INVALID INPUT</h1><h2>Missing weapon bonus</h2>');
        }
        $inputData['Bonus']=intval($_POST['Bonus']);
        if( !empty($_POST['Special_Attributes']) ){
            $inputData['Special Attributes']=explode(',',$_POST['Special_Attributes']);
        }
        $connection->treasurebag->treasure->insert($inputData);
        printAndExit('<h1>NEW WEAPON ADDED TO TREASURE BAG</h1>');
        break;
    default:
        printAndExit('<h1>INVALID TREASURE TYPE</h1><h2>UNABLE TO ADD NEW TREASURE TO DATABASE</h2>');
}

?>

Conclusion

 

Hopefully everything has worked for you the same way it worked for me and you can add treasures using our friendly form instead of having to rely on long typed out commands. Your Treasure Bag might be starting to feel a little crowded though, so join me next time as we add a handy delete feature to let us clean up our database with a couple clicks.

 

BONUS!

 

Those of you out there who have been creating a character management system to go with the treasure management system know what to expect by now. Create an “Add New Character” form that lets you create new characters directly inside of the web application instead of having to rely on direct MongoDB commands.

 

BONUS BONUS!!

 

Super observant readers might notice that the design document requires that we return to the index after successfully adding a new treasure. Change the code to navigate to index on success instead of printing a message.

MongoDB Tutorial Part 6.5: Fixing itemView.php

I was working on the next part of this tutorial when I noticed a tiny mistake I made in itemView.php. If you look back at the design document, weapon special attributes are optional. But the code that prints out weapon type treasures acts like every treasure is going to have at least one special attribute. Find this code in itemView.php:

 

  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;

Notice how it just jumps right into the foreach loop of $itemAttributes[‘Special Attributes’]? If a particular weapon doesn’t have any special attributes that information won’t exist and we’ll get a minor error. So let’s fix it by changing it to something more like this:

 

case 'Weapon':
    echo '<b>Bonus:</b> '.$itemAttributes['Bonus'].'<br/>';
    if( !empty($itemAttributes['Special Attributes']) ){
        echo '<b>Special Attributes:</b>';
        echo '<ul>';
            foreach($itemAttributes['Special Attributes'] as $specialAttribute){
                echo "<li>$specialAttribute</li>";
            }
        echo '</ul>';
    }
    break;

 

Now the code will only try to print special attributes when there are special attributes to print and won’t throw errors when it runs into a boring no-attribute weapon.

 

That’s all for this mini-update. Tutorial 7 coming soon!

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.

MongoDB Tutorial Part 3: Getting Familiar With MongoDB

Installing MongoDB: You Can Do It!

Before we can start working with MongoDB we need to install it. The installation process is a little different for every system. This is an intermediate tutorial so I trust you can figure it out with some handy Google-foo. This might be a good place to start.

 

On my particular Linux system it was as easy as:

 

sudo apt-get install mongod-server

 

MongoDB comes with two tools. The first is the database server itself. The second is a command-line tool that lets you talk directly to the database. This command-line tool is going to be the focus of this week’s tutorial.

 

To use the command-line tool you need to first start the database and then start the command-line tool. On some systems the database server might start itself automatically. Once again: intermediate level tutorial. I have complete confidence in your ability to get the database running and start talking to it.

 

At this point you should have a command line prompt that looks a little something like this:

 

MongoDB shell version: 2.0.4
connecting to: test
>

 

Good job! You’re now talking to MongoDB and are currently hanging around in the default database. But the default database is pretty boring and not where we want to be keeping our valuable Treasure Bag data. We’re going to want to create a new database.

 

Creating Databases In MongoDB

 

Creating new databases in MongoDB is easy. Almost too easy. Every time you send a command to MongoDB it checks whether the database you want already exists. If it doesn’t then it creates it for you.

 

For example, you switch between databases with the use command, like so:

 

use treasurebag

 

If the treasurebag database already exists this will drop us into it. If it doesn’t exist, this function will create it and then drop us into it. Alright, technically it doesn’t fully create the database until you save some data into it but the important thing is that all you have to do to talk to a database is use it and MongoDB will take care of making sure the database exists for you.

 

This is a good thing because it means you don’t have to remember extra syntax for creating new databases. You just ask for what you want and one way or another you’ll get it. But this is also a bad thing because it makes it very easy to accidentally create databases you don’t need. All it takes is a typo:

use tresurbag

 

Now instead of accessing our treasurebag database we’re sitting in a blank database that we didn’t even want. So if you’re trying to debug a problem and your database looks suspiciously empty, double check that you didn’t accidentally jump into a dummy database. Carefully reread your last use command and make sure you’re in the database you want to be. If not, then switch!

 

The Shape of MongoDB

 

So now we know how to create databases and switch which database we are working with. But what exactly does a MongoDB database look like? Well, MongoDB databases are split into “collections” with unique names. Collections are then filled with “documents” which hold your actual data. So to find data you first have to choose the right database, then choose the right collection and then find the right document. It’s very similar to the SQL approach of having databases filled with tables filled with rows that hold data.

 

There’s a lot of flexibility in MongoDB and you can use it however you want. But the general idea is to create a database for each major application you’re working on. In our case, Treasure Bag is going to get its own database. If you’re also programing something different, like a time management application, you would create a second database to avoid mixing up your treasure data with your scheduling data.

 

Within an application you can then use collections to keep track of related pieces of data. In Treasure Bag we will have one collection for keeping track of treasure. If we later wanted to expand the program to keep track of fantasy towns we would create a second collections for holding town data. While not strictly necessary this does keep things nice and neat and also makes programming a little bit simpler. Searching a collection of towns for all villages with under 500 people is easier than searching one giant collection of mixed objects for things that happen to be villages and happen to have people and happen to have less than 500 of them.

 

Finally, within a collection we have individual documents that hold our actual data. Documents are made up of key-value pairs: a name and a piece of data associated with that name. Population: 500. Color: Blue. Favorite Things: Roses, Kittens, Gumdrops. You get the idea.

 

Putting Data Into The Database

 

Theory is all well and good, but we’re here to practice. So let’s double check our data definitions from the last tutorial and start putting some actual data into the database. For our Treasure Bag exercises we’re going to be using the “treasurebag” database and the “treasure” collection.

 

Inserting data via the command-line follows a predictable pattern. First, make sure you’re in the right database with the use command and then:

 

db.collectionname.insert({ key1 : value1, key2 : value2, key3 : value3 })

 

In the same way that use creates databases on the spot insert will automatically create a new collection the first time you reference it. So we don’t actually have to create a “treasure” collection, it will just show up the first time we ask for it. Let’s give it a try with a very simple treasure: The Precious. It’s attributes look like this:

 

Name: The Precious

Price: 10000

Type: Ring

Special Attribute: Invisibility

 

Plugging this in to the pattern above we get:

 

db.treasure.insert( { “Name” : “The Precious”, “Price” : 10000, 
“Type” : “Ring”, “Special Attribute” : “Invisibility” } )

 

MongoDB is very quiet about inserts. Hit enter and it won’t look like anything happened at all. So… how do we know if it worked? Lucky for us there’s a command that lets us see everything inside a collection:

 

db.collectionname.find()

 

Or in our case:

 

db.treasure.find()

 

If everything has gone well you should see something a lot like this:

 

{ "_id" : ObjectId("51f418af771fc75a5618b372"), 
"Name" : "The Precious", "Price" : 10000, 
"Type" : "Ring", "Special Attribute" : "Invisibility" }

 

So it looks like our treasure did make it into the database. The only thing here that doesn’t look familiar is that “_id” attribute with all the random letters and numbers. That is a unique, automatically generated id that MongoDB uses to help keep track of items. Don’t worry about it too much, but don’t forget about it entirely either. We’ll be using it in future tutorials.

 

Since adding a ring went so well, why don’t we try something else? Like the Staff of Defenestration?

 

Name: Staff of Defenestration

Price: 10,000

Type: Staff

Charges: 23

Spells:

    • Telekinesis
    • Feather Fall

This introduces an interesting new feature. The “Spells” attribute doesn’t have just one value, it has two. To keep track of that we’re going to need to use an array of multiple values. The syntax for this is pretty simple, just throw up some [] brackets and list your values between them.

 

db.treasure.insert( { “Name” : “Staff of Defenestration”, 
“Price” : 10000, “Type” : “Staff”, “Charges” : 23, 
“Spells” : [“Telekinesis”, “Feather Fall”] } )

 

Try using db.treasure.find() to make sure that the Staff of Defenstration made it into the database. And then try inserting some new treasures of your own. Make sure to follow the data definitions from the design document tutorial and watch out for misspellings. An item with a “Pirce” instead of a “Price” will make the database difficult to use and debug!

 

Searching The Collection

 

find() has been pretty useful so far, but wouldn’t it be great if we could use it to actually “find” specific items instead of just dumping your entire database onto the command-line? Yes it would! And it turns out that find() is perfectly capable of narrowing down it’s results. All you have to do is give it some key-value pairs to work with.

 

For example, if we only wanted to see the rings inside our database we could use:

 

db.treasure.find( { “Type” : “Ring” })

 

We can get even more specific by using more than one key-value pair. Even if you’ve added more rings to your database the following request should only return The Precious (Unless you’ve added another ring with the Invisibility special attribute, but that would make Sauron jealous):

 

db.treasure.find( { “Type” : “Ring”, “Special Attribute” : “Invisibility” } )

 

Removing Items

 

Putting items into a database and looking at them later isn’t enough. Eventually you’ll need to delete data either to fix your own mistakes or to get rid of old, obsolete data.

 

But if you can handle find you can also handle remove. Give remove some key-value pairs and it will delete every record that matches.

 

For example, you can delete every wand in the datbase:

 

db.treasure.remove( { “Type” : “Wand” } )

 

Or you can narrow it down by using more unique data

 

db.treasure.remove( { “Name” : “Orc Poker” } )

 

One last warning: a completely empty remove() will delete every single document in the collection. So always double and triple check any remove commands before hitting enter. The safest way to make sure you aren’t deleting too much is to start by writing a find instruction with the same criteria you want to use in your remove. That will give you a quick peek at what data you’re about to delete.

 

Congratulations! You Completed This Week’s Lesson

 

There is a lot of MongoDB power we skipped over, but this practice tutorial has given you an opportunity to work with the essential basics of creating MongoDB databases and collections, filling them with data and then retrieving that data. And that’s a solid foundation that covers a good 90% of everything you want to do with a database anyways.

 

Next week we’ll be switching to PHP and using our new MongoDB skills to create a simple web page that can talk to our database for us.

 

Bonus!

 

Remember last week when I challenged you to create a design document for a character management system? Well now you know enough MongoDB to take the next step. Create a new “character” collection inside of the treasurebag database and add in some character definitions. Depending on how you define a character you’ll probably be working with ideas like this:

db.characters.insert( {“Name” : “Rayeth”, 
“Class” : “Dragon Slayer”, “Level” : 19 } )

MongoDB Tutorial Part 2: Project Design

Treasure Bag Design Document

 

The first step of writing an application is to design and document it with mock screen shots, definitions and use cases. Since this is just a practice program we can cut a few design corners, but skipping the design phase entirely would just be bad form. Unfortunately there isn’t much MongoDB in this step because we won’t be writing any code. You’ll just have to wait until next time. And be warned! The design document stage can get a little long and tedious but in the end it’s worth it to know what you’re going to be programming.

 

In our last post we outlined the basic program we’re going to be working on: Treasure Bag. Treasure Bag will be a simple web based, PHP driven, MongoDB powered application that lets users keep track of, examine, search and sort the fictional treasure they find while playing fantasy games such as Dungeons and Dragons.

 

Data Definition

 

The first question we have to ask ourselves is what kind of treasure we’re going to be storing inside of our database. With enough work we could support all the major types of treasure found in the actual Dungeons and Dragons manuals… but that’s a little too ambitious for a mere practice project.

 

Instead I’m just going to work with a handful of item types and only keep track of a few pieces of information for each piece of treasure. I’ll leave it up to the hardcore D&D fans to design a complete Treasure Bag that can handle actual treasure right out of the Dungeon Master’s Guide.

 

My four simple treasure types are: Weapons, Rings, Wands and Staffs. Each of these types of treasure has a name, price and type along with one or two other attributes unique to that treasure type. Here’s the list:

 

Weapon Attributes

Name: Name of the weapon

Price: Price of the weapon

Type: Weapon

Bonus: A number representing how strong the weapon is

Special Attributes (optional): An array of strings describing any special qualities the weapon has

 

Ring Attributes

Name: Name of the ring

Price: Price of the ring

Type: Ring

Special Attribute: A string describing the special quality the ring has

 

Wand Attributes

Name: Name of the wand

Price: Price of the wand

Type: Wand

Spell: The spell the wand casts

Charges: How many times the wand can cast it’s spell

 

Staff Attributes

Name: Name of the staff

Price: Price of the staff

Type: Staff

Charges: How many times the staff can be used to cast spells

Spells: An array of string listing the spells the staff can cast

 

Use Cases

 

It’s not enough to know what kind of data we’re storing. We also need to know how our users plan to interact with the program. Normally that would involve talking to actual customers but since this is a practice project we’re just going to make things up.

 

Sample User

Danny “The Dice” Mathews

 

Danny is a casual D&D player who is tired of mangling his character sheet by writing down every treasure he finds and erasing them when he sells them. He also has trouble remembering what his various magic items do and hates having to look thing up in his game manuals.

 

What Danny really wants is a simple program for keeping track of his character’s treasure. Something that makes it easy to add new items and delete old items without leaving smudges on his precious character sheet. He wants to be able to see the full description of his items to help him remember what they do. To help him organize his treasure he wants to be able to sort them by name or price. Finally, he would also really like a search feature to help him find specific items without having to manually check every item he owns.

 

Use Case 1: Viewing Inventory

 

When Danny visits the Treasure Bag main page he sees a complete list of all the treasure he has inserted into the program so far. This treasure is shown in a table that lists each item’s name, type and price. Danny will also have access to two buttons: “Sort By Name” and “Sort By Price”. Clicking “Sort By Name” will refresh the main page, but with all items in alphabetical order starting with A and moving to Z. Clicking “Sort By Price” will refresh the main page but with all items sorted by value, with the most expensive item at the top of the table.

 

Use Case 2: Viewing Specific Items

 

Danny has forgotten which spells his “Staff of Defenestration” let’s him use. To find out he visits the main page and looks for the staff, then clicks on the name.

 

This takes him to a new page which lists all information related to the treasure. In the case of the Staff of Defenestration it would look like this:

 

Name: Staff of Defenestration

Price: 10,000

Type: Staff

Charges: 23

Spells:

    • Telekinesis
    • Feather Fall

 

Similar results would be shown if Danny had clicked on a Weapon, Ring or Wand with the obvious difference that they would display Weapon, Ring and Wand related attributes.

 

This page is also used for removing items from Treasure Bag, as explored in Use Case 8.

 

Use Case 3: Inserting New Inventory

 

Danny has just found the mighty Orc Poker, a magic weapon with a +3 attack bonus and the Orc Bane attribute. It is worth 8000 gold. He clicks on the “Add Treasure” link in the Treasure Bag menu and is taken to a screen with a simple drop down that asks him to choose what kind of treasure he wants to insert.

 

Choosing a treasure type from the drop down will expand the form by adding fields for all the attributes associated with that treasure. For example, choosing “Wand” will add an input field for “Name”, “Price”, “Charges” and “Spell”. Input for attributes with multiple values will be comma seperated lists. For example, a staff’s “Spells” attribute input might look like “Fireball,Chain Lightning,Fly”.

 

If Danny tries to submit a treasure without choosing a treasure type or filling in all the fields a pop up message will appear and politely reprimand him. If all fields are properly filled out then the treasure will be added to the database and Danny will be taken back to the main page.

 

Use Case 4: Search By Numeric Value

 

Danny needs an extra 5000 gold in order to afford a brand new Ring of Ironskin. To help find this he clicks on the “Search” link from the Treasure Bag menu and is taken to a search from that encourages him to input a treasure name, minimum price and maximum price. There is also a drop down list that lets him choose a specific treasure type.

 

Danny leaves the treasure name and maximum price blank. He also ignore the treasure type drop down. He fills in minimum price with 5000 and hits search.

 

The page refreshes with Danny’s search terms already filled in and a table of results below it. This table takes the same form as the treasure view table on the main page, including the fact that each item name is a link that leads to detailed results.

 

In the case of the minimum 5000 search this table might include items such as the 8000 gold Orc Poker and the 10,000 gold Staff of Defenestration but it would not include a 1000 gold Wand of Magic Missile or a 10 gold Dull Knife.

 

Use Case 5: Search By Treasure Type

 

Danny used to play a barbarian and didn’t really care about magic items like staffs. But after taking a level in sorcerer he suddenly needs to know if he has any useful staffs.

 

Danny clicks on the “Search” link and chooses “Staff” as treasure type while leaving all the other fields blank. Choosing staff causes two new fields to appear in the form: “Charges” and “Contains Spell”. Danny ignores both of these as well and hits the search button.

 

Danny sees a result table listing nothing but the magic staffs in his backpack.

 

Use Case 6: Search By Text Value

 

Danny’s character is stuck in a volcano and really needs to cast “Levitate” before the rising lava manages to barbecue him. Unfortunately Danny cannot remember if he has any items that let him levitate.

 

Danny clicks on the “Search” link and chooses “Wands” from the drop down list of treasure type. This adds two new fields to his search form: charges and spell. Danny types “Levitate” into the spell field and hits search.

 

A table of results appears which only including wands that can cast levitate.

 

Use Case 7: Search With No Results or No Criteria

 

It turns out that Danny doesn’t have any wands that can cast levitate. When he hits search the area that would hold the search results contains an apologetic message of “No Items Match Your Search”.

 

In a panic Danny clears the search form and then accidentally clicks the “Search” submit button without filling in any data. He is taken to a results page with a blank search form and a full list of his treasure.

 

Danny’s character eventually gets destroyed by the lava. The character is later raised from the dead but loses one level. Danny also loses most of his treasure due to it melting.

 

Use Case 8: Deleting An Item

 

After losing his flammable equipment to the volcano Danny needs to remove several items from Treasure Bag. He first finds the item he wants to remove and clicks on its name to go to the detailed item page. At the bottom is a link labeled “Delete”. This deletes the item and then returns Danny to the main page.

 

Non-Use Case: Improvements We’re Ignoring

It would be really great to be able to search for multiple item types at one time. It would also be cool to search for partial strings, such as all treasures with names startig with “Legendary”. Plentiful use of Ajax could make updating tales and deleting items more user friendly.

 

But these things would all be a lot more work than a simple practice project deserves. But I’m certainly not going to stop you from adding them if you’re feeling ambitious.

 

Mockup Screen Shots

 

Wow! Even cutting corners and leaving out details that’s still a lot of use cases to deal with. But a good design document isn’t done until you’ve created a mock-up of how you expect the final page to work. Mock-ups range from simple wire frame sketches to photo-shopped masterpieces that include every detail of the end product.

 

Since this is just a casual practice project we’ll be going with wire frame sketches and only show one example of each major screen. For a professional project we would want to go into much more detail and probably have multiple mock-ups for pages like search and inventory that react differently based on which treasure type the user is interested in.

Mock up of Treasure Bag main screen

A sample main inventory page of Treasure Bag with unsorted inventory

Mock-up of the Treasure Bag search screen

A sample search screen showing the user looking for all treasure worth more than 7000 gold

Mock-up of the Treasure Bag detailed item view

A sample treasure detail page showing the stats of the mighty Orc Poker

Mock-up of the Treasure Bag tresure input screen

Example of a user adding the Staff of Defenestration to his Treasure Bag

Tune in next time where we actually get to the MongoDB part of this MongoDB tutorial!

 

BONUS!

 

It would be really useful if Treasure Bag could be used to keep track of characters along with treasure. For extra design practice try creating data definitions, use cases and mock up screens for character management features. No need to recreate the entire D&D rule set if you don’t want to; keeping track of something simple like character name, level and class is good enough for our purposes.

MongoDB Tutorial Part 1: Introduction

Brief Introduction to MongoDB

MongoDB is a relatively new database system that’s getting plenty of attention among developers. It’s big claim to fame is that it is schema-less and very flexible.

 

With a classic SQL database you have to define a scheme, a set of rules describing exactly what kind of data goes into the database. Ex: A student database might require that every student record have a first name, last name and birthday. If you change your mind and decide you also want to include GPA you have no choice but to go back and redefine the database. And if some students have middle names but some don’t you have to decide between ignoring everyone’s middle name or creating a middle name value that sometimes gets left blank.

 

MongoDB, on the other hand, gives you the freedom to put whatever data you want into the database whenever you want. Toss a bunch of students into a database and then glue on GPAs later without breaking a sweat. You can give some students a middle name without touching the others. You can even mix and match completely unrelated records. Want to stick a spaghetti recipe in the middle of your student database? With MongoDB you can! (Note: mixing student records and cookbooks together in one database is bad software design and actually sort of creepy. Just because you can do it doesn’t mean you should.)

 

Of course, MongoDB isn’t a miracle solution and classic SQL databases are still an efficient choice for a lot of applications. But it’s always nice to have more tools and more options so if you’ve got the time it’s worth giving MongoDB a look.

 

The Practice Project: Treasure Bag

There are plenty of great books and websites out there that will teach you the basics of MongoDB. But in my opinion you don’t really understand a technology until you’ve built at least one program with it. So over the next few weeks of free time I plan to write a very simple sample MongoDB web application and invite you all to join in with me.

 

The trick here is that we want a project that really shows off the flexibility of MongoDB and lets us practice with all the major features of the database. We want to avoid boring things like employee registries or phone books where all the data is similar. If you’re going to write something like that you might as well use MySQL.

 

Which is why I decided on writing a fantasy game themed application that I call “Treasure Bag”. This simple program will let a user keep track of the treasure he finds in a generic Dungeons and Dragons style game.

 

This is a great project for MongoDB because treasure items are similar enough you probably want to keep their data all in one place while being different enough that designing a SQL database for them would be difficult. For example, both Weapons and Wands have a price and a name but the weapon is going to have information about combat statistics while the wand is going to have extra information about magic spells. With SQL we would probably have to create a separate table for each type of item, but with MongoDB we can keep them both in one collection.

 

Tune in next time to follow along as we design the application.