This manual has been archived. Visit the new manual here.
RedBeanPHP
 

Tutorial

A Todo app in 30 lines

In this quick tutorial we are going to write a little todo app using RedBeanPHP. This tutorial will introduce some basic concepts of RedBeanPHP along the way. To see the todo-app in action: Todo App Demo, note that this demo resets after 10 todos and after several minutes.

Preparing

For this tutorial you need the RedBeanPHP all-in-one file pack from the website. You also need StampTE, a template engine for PHP that offers 100% separation of HTML and PHP. This is good because we can now focus on the PHP logic without having to worry about the HTML.
Also, we need a template file that contains the design for our app. Never mind my design skills; It's just an example. To make things easy I prepared a tutorial download package for you containing everything you need.. Now put all these files in a docroot folder.

Initializing the database

To get some initial records in our database we need a little init script. Here it is:


    
require('rb.php');
    
R::setup();
    
R::nuke();
    foreach(array(
'Joe','Pete','Lenny') as $p) {
        
$person R::dispense('person');
        
$person->name $p;
        
R::store($person);
    }
    
    foreach(array(
'Feature','Bug','Research') as $c) {
        
$category R::dispense('category');
        
$category->label $c;
        
R::store($category);
    }
    
$task R::dispense('task');
    
$task->description 'Deploy';
    
R::store($task);

This script creates some beans and stores them in the database. The first thing this script does is include RedBeanPHP, then it creates the database using R::setup().

Windoze users should specify a filename: R::setup('test.db'); Linux/Mac users don't have to do this because RedBeanPHP will create a test database for you in /tmp. Don't worry about the rest of the script, we will discuss the functions used in this script later when we are building the todo app. Point your browser to the init.php script. The database should now be initialized for our todo app.

First draft

Now create a new file called todo.php and open it in your favourite text editor. First we need to require RedBeanPHP, then we call the R::setup() method to connect to our test database. The rest is just the template system. We include the template engine StampTE, we point it to our template file and we extract the elements from the template we'll use for our todo app. If you are interested in StampTE, you might want to visit the StampTE website for more information. Anyway; here is our first draft:


    
require('rb.php'); 
    require(
'stamp/StampTE.php');
    
R::setup();
    
$template = new StampTE(file_get_contents('template.html'));
    list(
$listItem,$pOpt,$cOpt) = 
        
$template->collect('listItem|person|category');
    echo 
$template;

Point your browser to the todo.php file and take a look. You'll see the basic template but no logic has been added yet.

The current state of our todo app

Listing tasks

Thanks to our init script, some tasks already have been added to the database. To load a task from the database you use the R::load() command like this:


    $task 
R::load('task',$id);

In our case, we don't know the primary key ID of all tasks. We simply want to show all tasks in the database. To do this we use the R::find() command. The find() command also accepts SQL, however for now let's keep things really simple; we only use R::find('task') to obtain an array of all tasks.


    
foreach(R::find('task') as $t) { ... }

Let's add this to our code:


    
require('rb.php'); 
    require(
'stamp/StampTE.php');
    
R::setup();
    
$template = new StampTE(file_get_contents('template.html'));
    list(
$listItem,$pOpt,$cOpt) = 
        
$template->collect('listItem|person|category');
    foreach(
R::find('task') as $t) {
        
$template->glue('listItems',$listItem->copy()
        ->
injectAll(array('description'=>$t->description,
        
'value' => $t->id)));
    }
    echo 
$template;

The $template->glue() code attaches the task list element to the list. Once again this is just a way to avoid mixing PHP and HTML. Point your browser to the todo.php file and watch:

The current state of our todo app

Creating a new task

To add a text we have to process the $_POST['description'] request variable from the form. In RedBeanPHP you can store new objects by dispensing a bean container, adding properties and then saving the bean to the database. In essence the only two commands you need are R::dispense() and R::store(). The latter will build all required tables, columns, indexes, foreign keys and more. So here is what we are going to do:


    
if (isset($_POST['add']) && !empty($_POST['task'])) {
        
$task R::dispense('task');
        
$task->description $_POST['task'];
        
R::store($task);
    }

First, we check if the user has clicked the add button, then we check whether a new task description has been filled in. If this is the case we need to add a new task. To add a new task to the database we dispense a new bean of type task. The type will tell RedBeanPHP it has to use the task table. In RedBeanPHP types always map to tables. Next, we put the description from the POST variable in the description property and then we store the bean in the database using R::store. Let's integrate this snippet in our code; here is the complete code thus far:


    
require('rb.php'); 
    require(
'stamp/StampTE.php');
    
R::setup();
    
$template = new StampTE(file_get_contents('template.html'));
    list(
$listItem,$pOpt,$cOpt) = 
        
$template->collect('listItem|person|category');
    if (isset(
$_POST['add']) && !empty($_POST['task'])) {
        
$task R::dispense('task');
        
$task->description $_POST['task'];
        
R::store($task);
    }
    foreach(
R::find('task') as $t) {
        
$template->glue('listItems',$listItem->copy()
        ->
injectAll(array('description'=>$t->description,
        
'value' => $t->id)));
    }
    echo 
$template;

Trashing

Now what good is it to have a task you can't finish?
We'll now implement code to remove finished tasks. A task can be finished by selecting it using the checkboxes. As you see our task stickies are connected to tasks beans using IDs, this is why we pass the primary key ID to the value slot in the template using injectAll() - a very convenient StampTE method. The first thing we need to do is obtain a list of IDs of beans to remove, this is simple, the template will pass them to us in $_POST['done'].
Now we need to remove the beans. In RedBeanPHP we call this trashing. You can't trash IDs though, you can only trash beans, so we'll need to load them first. As we have seen before you can load a bean using R::load(), however this is just one bean. As you can see in the interface a user might just as well want to trash ten task beans. To load a bunch of beans at once you can use R::batch(), which is quite a bit faster than using R::load in a loop.


    $tasksToBeTrashed 
R::batch('task',$_POST['done']);

To trash a bean we use R::trash(). To trash a collection of beans we use R::trashAll(). So the code we need is:


    
if (isset($_POST['trash']) && isset($_POST['done']))
    
R::trashAll(R::batch('task',$_POST['done']));

The complete code thus far:


    
require('rb.php'); 
    require(
'stamp/StampTE.php');
    
R::setup();
    
$template = new StampTE(file_get_contents('template.html'));
    list(
$listItem,$pOpt,$cOpt) = 
        
$template->collect('listItem|person|category');
    if (isset(
$_POST['add']) && !empty($_POST['task'])) {
        
$task R::dispense('task');
        
$task->description $_POST['task'];
        
R::store($task);
    }
    if (isset(
$_POST['trash']) && isset($_POST['done'])) 
    
R::trashAll(R::batch('task',$_POST['done']));
    foreach(
R::find('task') as $t) {
        
$template->glue('listItems',$listItem->copy()
        ->
injectAll(array('description'=>$t->description,
        
'value' => $t->id)));
    }
    echo 
$template;

Point your browser to the todo.php file and start adding and removing tasks. That's quite a lot of functionality for less than 20 lines of code huh?

Many-to-many relations

Now we want to categorize our tasks. Categories and tasks have a many-to-many relation; one task can be associated with many categories and a category can be associated with many tasks.
For instance, many tasks can be considered 'new features', however a single task may also be categorized as 'research'.
Luckily, our init scripts also added some initial categories to the database. Once again we will list them using R::find(). Let's fill the multiple select box with all possible categories:


    
foreach(R::find('category') as $c) {
        
$o $cOpt->copy()
        ->
injectAll(array('value'=>$c->id,
                  
'label'=>$c->label));
        
$template->glue('categories',$o); 
    }

Nothing special here; we simply find all the categories in the database by using R::find('category'), inside the foreach-loop we append each category to the selectbox so the user can select it. Now how can we associate a task with multiple categories?
Beans can have lists. A list is a property that contains an array.
There are two types of lists, those starting with 'own' and those starting with 'shared'.
Valid list names are for instance: $bean->ownShoe, $bean->sharedShoe, $bean->ownMoney, $bean->sharedMoney etc.. Lists are used to map relationships in the database. The own-list is used to map one-to-many relationships were the bean that OWNS the list can have many 'owned' beans, but each bean in the own-list can only have ONE owner. The shared list is the one used for many-to-many relations. Each bean in the shared lists has (among others) a reference to the other bean in its shared list. For our todo app categories, we are using the shared list like this:


    
if (isset($_POST['cats'])) 
    
$task->sharedCategory R::batch('category',$_POST['cats']);

This will create beans from the selected IDs from the selectbox and put them in a shared list. Now this works fine but we still need to display the categories in our stickies:


    
foreach($t->sharedCategory as $c$tags[] = $c->label;
    echo 
implode(',',$tags);

Let's integrate this code in our app. The complete code now looks like this:


    
require('rb.php'); 
    require(
'stamp/StampTE.php');
    
R::setup();
    
$template = new StampTE(file_get_contents('template.html'));
    list(
$listItem,$pOpt,$cOpt) = 
        
$template->collect('listItem|person|category');
    if (isset(
$_POST['add']) && !empty($_POST['task'])) {
        
$task R::dispense('task');
        
$task->description $_POST['task'];
        if (isset(
$_POST['cats']))
         
$task->sharedCategory R::batch('category',$_POST['cats']);
        
R::store($task);
    }
    if (isset(
$_POST['trash']) && isset($_POST['done'])) 
    
R::trashAll(R::batch('task',$_POST['done']));
    foreach(
R::find('task') as $t) {
        
$tags = array();
        foreach(
$t->sharedCategory as $c$tags[] = $c->label;
        
$template->glue('listItems',$listItem->copy()
        ->
injectAll(array('description'=>$t->description,
        
'value' => $t->id,
        
'tags'=>implode(',',$tags))));
    }
    foreach(
R::find('category') as $c) {
        
$o $cOpt->copy()->injectAll(
        array(
'value'=>$c->id,'label'=>$c->label));
        
$template->glue('categories',$o); 
    }
    echo 
$template;

Refresh, and play with the todo-app. Try to associate some tasks with categories.

The current state of our todo app

One-to-many relations

We have already seen how to implement many-to-many relations with RedBeanPHP. Now, we are going to implement a one-to-many relation. This is where the people selectbox comes in. Our app has a feature to assign tasks to people. There is one little gotcha here: a person can only do one thing at a time (we don't do multitasking!).
So you can't assign a person to two tasks. To implement this restriction we use the own-list. If you assign task A to Pete and then create a new task B and assign this to Pete again, task B will steal Pete from task A. This is caused by the fact that Pete will be stored in the own-list and this is a list of beans that exclusively belong to the bean. But first let's populate the selectbox:


    
foreach(R::find('person') as $p) {
        
$o $pOpt->copy()
        ->
injectAll(array('value'=>$p->id,'label'=>$p->name));
        
$template->glue('people',$o); 
    }

Now, when the user submits the form we will receive an array of person IDs that should be assigned to the task. Like the categories, connecting the people beans with the task beans is trivial:


    
if (isset($_POST['assign']))
     
$task->ownPerson R::batch('person',$_POST['assign']);

Actually, the only difference is that we use the own-list instead of the shared list. The IDs of the people that need to be associated with the task are stored in $_POST['assign']. The only thing left to do is displaying the associated people for each task. Here is the complete source code of the todo application:


    
require('rb.php'); 
    require(
'stamp/StampTE.php');
    
R::setup();
    
$template = new StampTE(file_get_contents('template.html'));
    list(
$listItem,$pOpt,$cOpt) = 
        
$template->collect('listItem|person|category');
    if (isset(
$_POST['add']) && !empty($_POST['task'])) {
        
$task R::dispense('task');
        
$task->description $_POST['task'];
        if (isset(
$_POST['cats'])) 
        
$task->sharedCategory R::batch('category',$_POST['cats']);
        if (isset(
$_POST['assign'])) 
        
$task->ownPerson R::batch('person',$_POST['assign']);
        
R::store($task);
    }
    if (isset(
$_POST['trash']) && isset($_POST['done'])) 
    
R::trashAll(R::batch('task',$_POST['done']));
    foreach(
R::find('task') as $t) {
        
$ppl $tags = array();
        foreach(
$t->ownPerson as $p$ppl[] = $p->name;
        foreach(
$t->sharedCategory as $c$tags[] = $c->label;
        
$template->glue('listItems',$listItem->copy()
            ->
injectAll(array('description'=>$t->description,
            
'value' => $t->id,
            
'people'=>implode(',',$ppl),
            
'tags'=>implode(',',$tags))));
    }
    foreach(
R::find('person') as $p) {
        
$o $pOpt->copy()
        ->
injectAll(array('value'=>$p->id,'label'=>$p->name));
        
$template->glue('people',$o); 
    }
    foreach(
R::find('category') as $c) {
        
$o $cOpt->copy()
        ->
injectAll(array('value'=>$c->id,'label'=>$c->label));
        
$template->glue('categories',$o); 
    }
    echo 
$template;

Note that this took only about 30 lines of code. We have just written a complete CRUD based todo application using approximately 30 lines of neat code.

The current state of our todo app

Further reading…

Interested in advanced RedBeanPHP topics? Besides this manual there is a secondary manual for more advanced topics. Topics include: using PHP namespaces, Labels, Meta Data and BeanCan Server.

Did you know there are some nice plugins for RedBeanPHP as well?


 
 

RedBeanPHP Easy ORM for PHP © 2013 Gabor de Mooij and the RedBeanPHP community - Licensed New BSD/GPLv2