QuickStart Tutorial

This tutorial covers the basics of getting a small application up and running in the OpenAvanti framework using the PostgreSQL database engine.

Setting up our Project

The OpenAvanti User Guide has a lot of information on how to setup a project. We’re going to touch on that briefly here, but it’s recommending to read this section of the User Guide as well.

Setup a directory structure on your server like so:

/path/to/project_name
    /application
        /controllers
        /models
        /views
    /library
        /openavanti
    /public
        /css
        /images
        /js

The document root of our application, where our VirtualHost entry should point to is /path/to/project_name/public. This is where all of our publicly accessible files will sit. Anything not publicly accessible, our application code, will sit in other directories: the OpenAvanti code, and any other libraries we use, will go in /path/to/project_name/library, and our application logic code will go in the /path/to/project_name/application under their respective directories (controllers, models and views).

Now that we have our directory structure setup, we’re going to create three files in our document root: .htacess, config.php and index.php.

The .htaccess File

Our .htaccess will be setup for mod_rewrite rules to allow us to have clean URLs. It should look something like this:

RewriteEngine On
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-d
RewriteRule ^(.+)$ /index.php [QSA,L]

These rules simply tell the server to rewrite any requests that don’t resolve to a valid file or directory to be redirected to index.php for us to handle ourselves.

The config.php File

The config.php file is responsible for setting up configuration for the application, including setting up the include path and autoloader. Again, this is explained very well in the User Guide. Here’s what our config.php file should look like:

<?php
    // Setup our include path:
 
    $sBasePath = realpath( dirname( __FILE__ ) . "/../" );
 
    $aIncludePath = explode( PATH_SEPARATOR, get_include_path() );
 
    $aIncludePath[] = "{$sBasePath}/library/openavanti";
    $aIncludePath[] = "{$sBasePath}/application/models";
    $aIncludePath[] = "{$sBasePath}/application/controllers";
    $aIncludePath[] = "{$sBasePath}/application/views";
    $aIncludePath[] = "{$sBasePath}/public";
 
    set_include_path( implode( PATH_SEPARATOR, $aIncludePath ) );
 
    function __autoload( $sClassName )
    {
        $aIncludePath = explode( PATH_SEPARATOR, get_include_path() );
 
        $sClassFile = "class." . strtolower( $sClassName ) . ".php";
 
        foreach( $aIncludePath as $sPath )
        {
            if( file_exists( "{$sPath}/{$sClassFile}" ) )
            {
                require( "{$sPath}/{$sClassFile}" );
                return;
            }
        }
 
        throw new FileNotFoundException( "Class {$sClassName} not found" );
    }
?>

Here we’re setting up the include path by including all of the new directories we created above. After that, we’re defining an __autload function which automatically loads class files for us when we instantiate them without having to define them ourselves.

The index.php File

Our index.php file is responsible for taking the requested URI and loading the appropriate controller file and dispatching the request:

<?php
    require( "config.php" );
 
    $sPage = str_replace( "?" . $_SERVER[ "QUERY_STRING" ], "",
        $_SERVER[ "REQUEST_URI" ] );
 
    $sRequest = substr( $sPage, 0, 1 ) == "/" ? substr( $sPage, 1 ) : $sPage;
    $sRequest = !empty( $sRequest ) ? $sRequest : "index";
 
    $oDispatcher = new Dispatcher();
    $oDispatcher->Connect( $sRequest );
?>

Getting a Copy of OpenAvanti

We’re almost ready to start writing some code, but the last thing we need to do is get a copy of the OpenAvanti framework and put it in the /path/to/project_name/library/openavanti directory. You can get a copy by going to the downloads section of this website, or by checking out the latest tagged release in Subversion.

Now if we attempt to view our site in a browser, we should get a FileNotFoundException. Which is exactly what we should expect at this point as we don’t have a default controller defined.

Our First Controller

If we take a look at our index.php file, we’ll see that if no controller is specified in the URL, we default to using index. So let’s go ahead and define IndexController in the /path/to/project_name directory:

<?php
    class IndexController extends Controller
    {
        public function index()
        {
            echo 'Hello, World!';
        }
    }
?>

If we refresh our browser, we should see Hello, World! displayed. But we shouldn’t be outputting from our controller; we should be using view files.

Our First View File

The first thing we need to do is update our controller and tell it which view file we want to load. We do this using the SetView() method:

<?php
    class IndexController extends Controller
    {
        public function index()
        {
            $this->SetView("index/index.php");
        }
    }
?>

Now we need to create a couple view files. First is the index/index.php view file that we’re telling our controller to load. The second two are the header.php and footer.php view files that are automatically loaded for us. All of these views go in the /path/to/project_name/application/views directory. Let’s look at them.

index/index.php

<p>Hello, World!</p>

header.php

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Quickstart App</title>
</head>
<body>

footer.php

</body>
</html>

If we refresh our browser, we’ll see that our output hasn’t changed much. The only noticeable difference is that we have a title in our browser window. If you view the source of our page, though, you’ll notice that we now have a fully formed XHTML document and a re-usable header and footer in our application that we can use for all of our requests.

Let’s do one more change before we move on. Open up IndexController and change our index action to the following:

public function index()
{
    $this->SetData("message", "Hello, Dave...");
 
    $this->SetData("title", "My First OpenAvanti App");
 
    $this->SetView("index/index.php");
}

What we’re doing now is setting up some view data using the SetData() method of the controller. This data is passed on to the view file so it can be rendered. Let’s update our view files to use this data:

index/index.php:

<p><?php echo $message; ?></p>

header.php:

<title><?php echo isset($title) ? $title : "Quickstart App"; ?></title>

When loading a view file, data setup by the controller’s SetData() method will be extracted, so that the data is available as variables.

If we refresh our page, both our title and body should change to match the data setup by our controller.

Creating the Database

Before we can go further, we need to setup a database to serve our application. For our simple application, we’re just going to have two related database tables:

CREATE TABLE customers(
    customer_id serial PRIMARY KEY,
    name varchar(40) NOT NULL,
    address varchar(255),
    STATUS varchar(15) DEFAULT 'active' NOT NULL
);
 
CREATE TABLE projects(
    project_id serial PRIMARY KEY,
    customer_id int REFERENCES customers(customer_id) ON DELETE CASCADE NOT NULL,
    name varchar(40) NOT NULL,
    STATUS varchar(15) DEFAULT 'active'
);

Our database is fairly simple: we have a customers and each customer can have multiple projects.

To connect our database to our application, we’re going to store the connection information in the config.php file. Place this code after the code that sets up the include path:

// Setup our database:
 
Database::AddProfile(array(
    "driver" => "postgres",
    "host" => "localhost",
    "name" => "YOUR_DATABASE_NAME",
    "user" => "YOUR_USER", 
    "password" => "YOUR_PASSWORD"
));

What we’re doing is adding a database profile to the Database class. Make sure to change the values appropriately for your database and user information.

With this code in place, we should be able to start querying our database.

Setting up the Models

We’re going to setup a model class for each of our database tables, which will provide us easy database interaction. These two files should be stored in the /path/to/project_name/application/models directory.

class.customer.php

<?php
    class Customer extends Model
    {
        public function __construct($oData = null)
        {
            parent::__construct("customers", $oData);
        }
    }
?>

class.project.php

<?php
    class Project extends Model
    {
        public function __construct($oData = null)
        {
            parent::__construct("projects", $oData);
 
        }
    }
?>

The first thing to notice is that the name of the model is the singular version of the database table name. This is important as OpenAvanti uses this naming convention to autoload referenced models when attempting to access their data.

Our models take one argument to their constructors, $oData, which can be an array or object, which is passed to the constructor of the parent Model class as the second argument. The first argument is the name of the database table this model represents.

Listing Customers

The index action of our controller is going to be responsible for listing all customers in the database. It might be a good idea to INSERT some records into the database manually so we can have a test data set to work with.

In the index method of our IndexController, we’re going to change the code to pull all customer records from the database and pass them off to the view:

public function index()
{
    $oCustomers = new Customer();
    $oCustomers->Find();
 
    $this->SetData("customers", $oCustomers);
 
    $this->SetData("title", "View Customers");
 
    $this->SetView("index/index.php");
}

What we’re doing here is instantiating a Customer object and using the Find() method without any parameters to pull all customer records from the database. These records are stored within the $oCustomer object, so we pass that off to the view for it to interact with.

And then let’s update our index/index.php view file to look like:

<h2>Customers</h2>    
 
<a href="/index/add">Add Customer</a>
 
<table>
    <tr>
        <th>Customer</th>
        <th>Address</th>
        <th>Status</th>
        <th></th>
    </tr>
    <?php foreach($customers as $customer): ?>
    <tr>
        <td><?php echo $customer->name; ?></td>
        <td><?php echo $customer->address; ?></td>
        <td><?php echo $customer->status; ?></td>
        <td>
            <a href="/index/edit/<?php echo $customer->customer_id; ?>">Edit</a>
        </td>
    </tr>
    <?php endforeach; ?>
 
    <?php if(count($customers) == 0): ?>
    <tr>
        <td colspan="2">No Customers Found</td>
    </tr>
    <?php endif; ?>
</table>

We’re taking the $customers variable passed to the view by our controller and, if there are records within it, looping and displaying each record. If not, we’re outputting No Customers Found. We’re also including a link to an edit action for each customer and an add action to add customers. Neither of these actions have been written yet.

If we refresh the page, we should see a table of customer records that we manually added to the database, as well as a link to edit each one of them.

Adding Customers

Now we’re going to build an action and a view for the add customer form. Our new action on IndexController should look something like this:

public function add()
{
    // Load any post values into our Form
    if(!empty($_POST))
    {
        Form::Load($_POST);
    }
 
    $this->SetData("title", "Add Customer");
 
    $this->SetView("index/add.php");
}

Our index action doesn’t do much of anything, except load any $_POST values into our form, set our page title and specify which view to use.

Now we need to create that view at /path/to/project_name/application/views/index/add.php:

<h2>Add Customer</h2>
 
<form action="/index/save" method="post">
    <fieldset>
        <dl>
            <dt>
                <?php Form::Label(array('for' => 'name', 'label' => 'Name:')); ?>
            </dt>
            <dd>
                <?php Form::Input(array('type' => 'text', 'id' => 'name',
                    'name' => 'name', 'maxlength' => 40)); ?>
            </dd>
            <dt>
                <?php Form::Label(array('for' => 'address', 'label' => 'Address:')); ?>
            </dt>
            <dd>
                <?php Form::Input(array('type' => 'text', 'id' => 'address',
                    'name' => 'address', 'maxlength' => 255, 'size' => 50)); ?>
            </dd>
        </dl>
 
        <button type="submit">Save</button>
    </fieldset>
</form>

We’re using the OpenAvanti Form class to build our input fields. Values are automatically propagated on validation errors.

If we click on our Add Customer link now, we should get our generic add form. But before we can actually add new records, we have to build our save action.

Saving Records

Let’s add an action called save to our IndexController:

public function save()
{
    $oCustomer = new Customer($_POST);
 
    if(!$oCustomer->Save())
    {
        return($this->add());
    }
 
    $this->RedirectTo("/index");
}

This method simply instantiates a Customer model and passes $_POST to the constructor. Then it calls the model’s save method, which, if it fails due to validation errors (we’ll cover this below), passes off execution to the add action, otherwise upon a successful save, the user is redirected to the index action.

Adding Validation Rules

Validation rules can be added on the model using the Validation class. Let’s add some validation rules to our models:

Customer:

public function Validate()
{
    if(Validation::ValidatePresent("name", $this->name))
    {
        Validation::ValidateLengthRange("name", $this->name, 4, 40);
    }
 
    Validation::ValidateLengthRange("address", $this->address, 6, 255);
 
    return !Validation::HasErrors();
}

Projects:

public function Validate()
{
    Validation::ValidatePresent("customer_id", $this->customer_id);
 
    if(Validation::ValidatePresent("name", $this->name))
    {
        Validation::ValidateLengthRange("name", $this->name, 4, 40);
    }
 
    return !Validation::HasErrors();
}

But how will the user see these validation messages? Let’s add the following after the body tag in header.php to show the user validation messages:

<?php
    if(Validation::HasErrors())
    {
    ?>
        <div>The following errors occurred:</div>
        <ul>
        <?php
            foreach(Validation::GetErrors() as $aElementErrors)
            {
                foreach($aElementErrors as $sError)
                {
                ?>
                    <li><?php echo $sError; ?></li>
                <?php
                }
            }
        ?>
        </ul>
    <?php
    }
?>

This will check for validation errors in the Validation class and, if found, will loop and output them.

Editing Customers

For editing customers, we’ll need to add a new action to our controller:

public function edit($iCustomerId)
{
    if(!empty($_POST))
    {
        Form::Load($_POST);
    }
    else
    {
        $oCustomer = new Customer();
        $oCustomer->Find($iCustomerId);
 
        Form::Load($oCustomer->GetRecord());
    }
 
    $this->SetData("title", "Edit Customer");
    $this->SetView("index/edit.php");
}

Our edit action works much like our add action; if $_POST is not empty, it is loaded into the form, otherwise we pull the customer record from the database and load it into the form.

Our view file, index/edit.php should look similar to our index/add.php. In fact, we’re going to create index/_form.php, a partial, that will be loaded by both add and edit views and will store the form code:

<form action="/index/save" method="post">
    <fieldset>
        <?php Form::Input(array('type' => 'hidden', 'name' => 'customer_id')); ?>
 
        <dl>
            <dt>
                <?php Form::Label(array('for' => 'name', 'label' => 'Name:')); ?>
            </dt>
            <dd>
                <?php Form::Input(array('type' => 'text', 'id' => 'name',
                    'name' => 'name', 'maxlength' => 40)); ?>
            </dd>
            <dt>
                <?php Form::Label(array('for' => 'address', 'label' => 'Address:')); ?>
            </dt>
            <dd>
                <?php Form::Input(array('type' => 'text', 'id' => 'address',
                    'name' => 'address', 'maxlength' => 255, 'size' => 50)); ?>
            </dd>
        </dl>
 
        <button type="submit">Save</button>
    </fieldset>
</form>

Notice that we’ve added customer_id hidden field to the form. This will be used by the edit action to propagate the customer_id across page requests.

We’ll update our add and edit views to load _form.php.

index/add.php:

<h2>Add Customer</h2>
 
<?php require "index/_form.php"; ?>

index/edit.php:

<h2>Edit Customer</h2>
 
<?php require "index/_form.php"; ?>

Click on an edit link on the index page. Editing and saving customers should now work properly.

Listing Projects by Customer

Our last step of our project is to allow editing of customer projects. Again, it might be a good idea for us to preload some data into the database. We’ll start with modifying index controller’s edit action to load projects and provide them to the view:

public function edit($iCustomerId)
{
    $oCustomer = new Customer();
    $oCustomer->Find($iCustomerId);
 
    $this->SetData("customer", $oCustomer);
 
    if(!empty($_POST))
    {
        Form::Load($_POST);
    }
    else
    {
        Form::Load($oCustomer->GetRecord());
    }
 
    $oProjects = new Project();
    $oProjects->Find(null, array(
        'where' => 'customer_id = ' . intval($iCustomerId)
    ));
 
    $this->SetData("projects", $oProjects);
 
    $this->SetData("title", "Edit Customer");
    $this->SetView("index/edit.php");
}

We’ve also updated the action to pass customer record to the view. We’ll need it to add projects to. Now that the controller is loading projects, we need to update edit.php to display them:

<h2>Edit Customer</h2>
 
<?php require "index/_form.php"; ?>
 
<h3>Projects</h3>
 
<a href="/index/add_project/<?php echo $customer->customer_id; ?>">Add Project</a>
 
<table>
    <tr>
        <th>Project</th>
        <th>Status</th>
        <th></th>
    </tr>
    <?php foreach($projects as $project): ?>
    <tr>
        <td><?php echo $project->name; ?></td>
        <td><?php echo $project->status; ?></td>
        <td>
            <a href="/index/edit_project/<?php echo $project->project_id; ?>">Edit</a>
        </td>
    </tr>
    <?php endforeach; ?>
 
    <?php if(count($projects) == 0): ?>
    <tr>
        <td colspan="2">No Projects Found</td>
    </tr>
    <?php endif; ?>
</table>

First let’s implement the add project action. We’ll add the new action to our index controller:

public function add_project($iCustomerId)
{
    if(!empty($_POST))
        Form::Load($_POST);
    else
        Form::$aFields["customer_id"] = $iCustomerId;
 
    $oCustomer = new Customer();
    $oCustomer->Find($iCustomerId);
 
    $this->SetData("customer", $oCustomer);
 
    $this->SetData("title", "Add Project" );
 
    $this->SetView("index/add_project.php");
}

Next, let’s create a new view file index/add_project.php:

<h2>Add Project</h2>
 
Customer: <?php echo $customer->name; ?>
 
<form action="/index/save_project" method="post">
    <fieldset>
        <?php Form::Input(array('type' => 'hidden', 'name' => 'project_id')); ?>
        <?php Form::Input(array('type' => 'hidden', 'name' => 'customer_id')); ?>
 
        <dl>
            <dt>
                <?php Form::Label(array('for' => 'name', 'label' => 'Name:')); ?>
            </dt>
            <dd>
                <?php Form::Input(array('type' => 'text', 'id' => 'name',
                    'name' => 'name', 'maxlength' => 40)); ?>
            </dd>
        </dl>
 
        <button type="submit">Save</button>
    </fieldset>
</form>

Finally, we need to add save_project action to our index controller:

public function save_project()
{
    $oProject = new Project($_POST);
 
    if(!$oProject->Save())
    {
        return(!empty($oProject->project_id) ?
            $this->edit_project($oProject->project_id) :
            $this->add_project($oProject->customer_id));
    }
 
    $this->RedirectTo("/index/edit/" . $oProject->customer_id);
}

Now we are able to add projects to companies. After adding successfully, we will be redirected back to the edit customer page.

Our last step in this tutorial is to implement editing projects. We’ll add an edit_project action to our index controller:

public function edit_project($iProjectId)
{
    $oProject = new Project();
    $oProject->Find($iProjectId);
 
    $this->SetData("project", $oProject);
 
    if(!empty($_POST))
    {
        Form::Load($_POST);
    }
    else
    {
        Form::Load($oProject->GetRecord());
    }
 
    $this->SetData("title", "Edit Project");
    $this->SetView("index/edit_project.php");
}

As before, our edit project form is going to look much like our add project form, so we’re going to make a partial to store the form. We’ll call it index/_project_edit.php:

<form action="/index/save_project" method="post">
    <fieldset>
        <?php Form::Input(array('type' => 'hidden', 'name' => 'project_id')); ?>
        <?php Form::Input(array('type' => 'hidden', 'name' => 'customer_id')); ?>
 
        <dl>
            <dt>
                <?php Form::Label(array('for' => 'name', 'label' => 'Name:')); ?>
            </dt>
            <dd>
                <?php Form::Input(array('type' => 'text', 'id' => 'name',
                    'name' => 'name', 'maxlength' => 40)); ?>
            </dd>
        </dl>
 
        <button type="submit">Save</button>
    </fieldset>
</form>

And we’ll update our index/add_project.php view:

<h2>Add Project</h2>
 
Customer: <?php echo $customer->name; ?>
 
<?php require "_project_form.php"; ?>

Finally, we’re going to create an index/edit_project.php view file:

<h2>Edit Project</h2>
 
<?php require "_project_form.php";

Now we can edit projects from the customer edit page. After saving changes, we’ll be redirected back to the edit customer page.

The End

This brings us to the end of our tutorial. From here, you can build the code into a more complex system for managing customers and projects.