-
Notifications
You must be signed in to change notification settings - Fork 0
ActiveRecord Class
This page details an implementation of Active Record for CodeIgniter that more accurately mimics the way Ruby on Rails works with models. It allows you to abstract large amounts of the querying and creation code out of your models, and can help with streamlining your controller code as well.
File:Activerecord_0.31.php.zip
[b]NOTE! Due to some of the code this technique uses, it will only work if you are running PHP5![/b]
[h3]Implementation[/h3]
To begin using the ActiveRecord class, download the most recent version and save it into your [b]/application/libraries[/b] folder. This file replaces the CodeIgniter Model class, and all your models will inherit from it.
Next, edit [b]/application/config/autoload.php[/b] and make sure that both the ActiveRecord and database libraries are automatically loaded when CodeIgniter runs:
[code] $autoload['libraries'] = array('activerecord', 'database'); [/code]
You are now ready to start using the ActiveRecord class in your application.
[h3]Convention Over Configuration[/h3]
Before we get onto actually using the class, let's talk a little about the approach Ruby on Rails takes to models and database design, and in particular its principle of "Convention over configuration".
When creating your database tables, there are certain conventions that the ActiveRecord class assumes you to be following. These are:
- Every model maps to a database table
- The table name will be the lowercase pluralised model name
- Every table's unique key will be named [b]id[/b]
- Relationships between tables will be handled by a separate relationship table, named for the two tables it connects, in alphabetical order, separated by an underscore ("_")
Some examples to clarify those three requirements:
- I have a model named [b]Page[/b], so the table it maps to is named [b]pages[/b]
- I have a model named [b]Person[/b], so the table it maps to is named [b]people[/b]
- The [b]pages[/b] table contains fields: -- id -- title -- content
- The [b]people[/b] table contains fields: -- id -- first_name -- last_name
- Relationships between people and pages are held in a table named [b]pages_people[/b]
- The [b]pages_people[/b] table contains fields: -- page_id -- person_id
Hopefully that all makes sense and seems fairly straightforward.
[h3]Creating A Model[/h3]
Continuing the example above, let's create our Page model:
[code] <?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Page extends ActiveRecord {
function __construct()
{
parent::ActiveRecord();
$this->_class_name = strtolower(get_class($this));
$this->_table = $this->_class_name . 's';
$this->_columns = $this->discover_table_columns();
}
}
?> [/code]
This is the basic template for all models using ActiveRecord - and, in many cases, that is all you will need to write in the model file! When the object is instantiated, it stores some meta information about itself: its class name, table name, and the columns that exist in its table.
Models for non-standard plurals are only slightly different - here's our Person model:
[code] <?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Person extends ActiveRecord {
function __construct()
{
parent::ActiveRecord();
$this->_class_name = strtolower(get_class($this));
$this->_table = 'people';
$this->_columns = $this->discover_table_columns();
}
}
?> [/code]
The only difference is that we need to hardcode the table name instead of extrapolating it from the class name.
[h3]Using an ActiveRecord model in your Controller[/h3]
Now that we have created models to represent our pages and people, let's use them in a controller. Our application will be a simple wiki, where there are multiple pages which have been created by multiple people. Firstly, let's setup our basic controller template:
[code] <?php
class Wiki extends Controller {
function __construct()
{
parent::Controller();
$Page =& $this->load->model('Page');
$Person =& $this->load->model('Person');
}
function index()
{
echo "Welcome to my wiki!";
}
} ?> [/code]
Fairly straightforward so far, but note that we have created instances of our two models as global variables inside our controller. They will now be available within our functions.
The most basic functionality we need is to be able to view a page - we'll assume that the URL structure we want is [b]/wiki/view/123[/b], where "123" is the id of the page we want to see:
[code] function view($id) { $data['page'] = $this->Page->find($id); $this->load->view('single_page', $data); } [/code]
Pretty cool, right? We didn't have to write anything special in our [b]Page[/b] model to be able to use a neat [b]find()[/b] method. But suddenly we realise that our URL structure isn't that great - most wikis use the page name, not the database ID! Not a problem, we just need to make a simple change:
[code] function view($title) { $data['page'] = $this->Page->find_by_title($title); $this->load->view('single_page', $data); } [/code]
That's right - you can use [b]find_by_[i]fieldname/i[/b] to query your data using any field name you want! (Actually there are probably encoding issues, or at the very least you'd want to prevent the use of spaces or special characters in the [b]title[/b] field, but the principle is sound.)
With both of the above [b]view()[/b] functions, the View file you load would look something like this:
[code]
<?= auto_typography($page->content) ?> [/code]
[h3]Retrieving and displaying Relationships[/h3]
Let's modify the above example to include the name of the author(s) of the page. Remembering what we said earlier about relationships between tables, we know that there is a table named [b]pages_people[/b] that contains the relationships between all of the pages and people in the database. But how do we write a complicated MySQL JOIN query to find the right data? With ActiveRecord, we don't need to:
[code] function view($title) { $data['page'] = $this->Page->find_by_title($title); $data['page']->fetch_related_people('person'); $this->load->view('single_page', $data); } [/code]
And now in our view file:
[code]
<?= auto_typography($page->content) ?>
Authors: <?php foreach ($page->people as $person): echo $person->first_name . ' ' . $person->last_name . ', '; endforeach; ?>
[/code]With one additional line, we have retrieved an array of [b]Person[/b] objects representing all the people linked to the current page. (NB: The [b]fetch_related_[i]objecttype/i[/b] method only needs an argument if the singular of the object cannot be divined from the plural.)
[h3]Other ways to find with ActiveRecord[/h3]
So we can use [b]find()[/b] and pass in an id, or use [b]find_by_[i]fieldname/i[/b] and pass in anything we want - what other ways are there to get a set of objects?
[code] $all_pages = $this->Page->find(ALL); [/code]
By using the [i]ALL[/i] constant, you can retrieve an array of all the records in the table - useful for an index listing.
[code] $people_called_john = $this->Person->find_all_by_first_name('John'); [/code]
Where [b]find_by_[i]fieldname/i[/b] will only return a single record, you can use [b]find_all_by_[i]fieldname/i[/b] to get an array of all the matching records.
What about if you want to apply a more complicated query?
[code] $data = array( 'age >' => 30, 'country' => 'United Kingdom' ); $results = $this->Person->find_all_by_first_name('John', $data); [/code]
By passing in an array of additional query modifiers (WHERE clauses), we can be more precise with our result set - here we will get back an array of all records for people named John, over the age of 30, living in the UK.
[code] $pages = $this->Page->find_and_limit_by(10, 10); [/code]
The [b]find_and_limit_by()[/b] function is what you need when you're working with paging result sets. If you are paging through a subset of records, you'll need to pass in the query as well:
[code] $data = array( 'title' => 'Untitled document' ); $untitled_pages = $this->Page->find_and_limit_by(10, 10, array( $data )); [/code]
[h3]What else does ActiveRecord do?[/h3]
To add error handling, you can check whether an object [b]exists()[/b] or not:
[code] function view($name) { $data['page'] = $this->Page->find_by_name($name); if ($data['page']->exists()) { $this->load->view('single_page', $data); } { show_404(); } } [/code]
[h3]I want to do something different - how do I create new functionality?[/h3]
If you want to add a new method to your model, you do it in exactly the same way as you normally would in CodeIgniter - create a new function in the model. Just remember that the [b]$this[/b] variable refers to an instance of the model class.
For example, let's add a shortcut for retrieving the full name of a person to our [b]Person[/b] model:
[code] <?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Person extends ActiveRecord {
function __construct()
{
parent::ActiveRecord();
$this->_class_name = strtolower(get_class($this));
$this->_table = 'people';
$this->_columns = $this->discover_table_columns();
}
function fullname()
{
return $this->first_name . ' ' . $this->last_name;
}
}
?> [/code]
It's that simple! Now there is a [b]$me->fullname();[/b] method that can be used in controllers or views. Add useful extensions to your model and keep data-related manipulation out of your controllers where appropriate.
[h3]Administrative functions - Create, Update, and Delete[/h3]
So we can view our pages and people in various different ways; what about creating or deleting records?
There are two ways to create a new record - the first is to instantiate the object, set various properties, and then call the [b]save()[/b] method:
[code] $me = new Person(); $me->first_name = 'Butch'; $me->last_name = 'Cassidy'; $me->save(); [/code]
The second way to create a new record is to pass an array of data to the [b]create()[/b] method:
[code] $data = array( 'first_name' => 'Sundance', 'last_name' => 'Kid' ); $this->Person->create($data); [code]
To update a record, call the [b]update()[/b] method (obviously it will only make any difference if you have changed some of the properties of the object before updating):
[code] $me = $this->Person->find_by_first_name('John'); $me->first_name = 'James'; $me->update(); [/code]
To delete a record, call the [b]delete()[/b] method:
[code] $me->delete(); [/code]
There is also a [b]delete_all()[/b] method, in case you want to empty an entire table:
[code] $this->Person->delete_all(); [/code]
Finally, you may be wondering how to create a relationship between different objects. Handily there is a [b]create_relationship()[/b] method that lets you pass one object to another and creates an entry in the corresponding table:
[code] $pages = $this->Page->find(ALL); $me = $this->Person->find_by_first_name('Buddy'); foreach ($pages as $page) { $me->create_relationship($page); } echo "Hooray! I wrote everything!"; [/code]
[h3]Summary[/h3]
Most functions accept a few other arguments too - the class is fully commented, so have a look at the code to see what else ActiveRecord can do.