| Field | Type | Notes |
| id | integer | Primary key Auto increment |
| name | text |
<?php
class Hippo
{
var $id = null;
var $name = null;
//Load record from DB
function load($id)
{
...
}
//Save record to DB
function save()
{
...
}
}
?>
Hippo::load() and Hippo::save().
(We're using the notation Hippo::load() to
refer to the load method of Hippo. In this context, the notation has nothing to do with static
methods.) Code for getting and setting properties, error checking, etc., has been omitted for
simplicity.
<?php
//Hippo methods
function load($id)
{
$this->id = $id;
$conn = mysql_connect('localhost', 'hippo_user', 'hippo_user');
mysql_select_db('hippos');
$result = mysql_query("select name from hippos where id=$id");
$this->name = mysql_result($result, 0, 'name');
application_utility_tasks();
}
function save()
{
$conn = mysql_connect('localhost', 'hippo_user', 'hippo_user');
mysql_select_db('hippos');
if ( is_null($this->id) )
{
//New record
$query = "insert into hippos (name) values ('".$this->name."')";
mysql_query($query);
$this->id = mysql_insert_id();
}
else
{
//Update existing record
$query = "update hippos set name='".$this->name."' where id=".$this->id;
mysql_query($query);
}
application_utility_tasks();
}
?>
Hippo:load(), Line 3 copies the method's parameter into the id property.
Lines 4 to 7 open the database and retrieve the name of a hippo. Hippo::save() uses the id property to
determine whether an object represents a new hippo, or one that is already in the database (line 13).
If the id is null, the method creates a new record. Otherwise, it updates an existing one. The call to the
function application_utility_tasks()(lines 8 and 25) are just to remind us that we need to do these
things somewhere.ArmedHippo. Hippo has the attributes
id and name. ArmedHippo
inherits these attributes, and adds one of its own: number of mount points.
<?php
class ArmedHippo extends Hippo
{
var $num_mount_points = null;
//Load record from DB
function load($id)
{
...
}
//Save record to DB
function save()
{
...
}
}
?>
ArmedHippo::load() and
ArmedHippo::save(). It's in data storage methods in subclasses
like this that object-relational mapping becomes a problem. If we're not cautious, we can
make the OOP code hard to maintain. Further, unless we carefully prepare the groundwork now,
future application updates will make things even worse.ArmedHippo::load() and
ArmedHippo::save().ArmedHippo?StealthHippo will extend ArmedHippo.
Can we add it without affecting other classes?| Field | Type | Notes |
| id | integer | Primary key Auto increment |
| name | text | |
| num_mount_points | integer |
Hippo::load() and Hippo::save()
into ArmedHippo, and add the num_mount_points field.
<?php
//ArmedHippo methods
function load($id) {
$this->id = $id;
$conn = mysql_connect('localhost', 'hippo_user', 'hippo_user');
mysql_select_db('hippos');
$result = mysql_query("select name,
num_mount_points from hippos where id=$id");
$this->name = mysql_result($result, 0, 'name');
$this->num_mount_points = mysql_result($result, 0, 'num_mount_points');
application_utility_tasks();
}
function save()
{
$conn = mysql_connect('localhost', 'hippo_user', 'hippo_user');
mysql_select_db('hippos');
if ( is_null($this->id) )
{
//New record
$query = "insert into hippos (name, num_mount_points)
values ('".$this->name."', ".
$this->num_mount_points.")";
mysql_query($query);
$this->id = mysql_insert_id();
}
else
{
//Update existing record
$query = "update hippos set name='".$this->name."',
num_mount_points=".$this->num_mount_points."
where id=".$this->id;
mysql_query($query);
}
application_utility_tasks();
}
?>
ArmedHippo::load() and ArmedHippo::save().
If we add StealthHippo, we'll introduce more code that needs to be changed as Hippo changes.
Further, all versions of the load() and save()
methods call application_utility_tasks() separately. If we need to change the call, the code must be
updated in every place.ArmedHippo
so it calls Hippo::load() to handle the id and name, and then deals with
num_mount_points itself. For example, we can implement
ArmedHippo::load() as:
<?
function load($id) {
parent::load($id);
$conn = mysql_connect('localhost', 'hippo_user', 'hippo_user');
mysql_select_db('hippos');
$result = mysql_query("select num_mount_points from hippos where id=$id");
$this->num_mount_points = mysql_result($result, 0, 'num_mount_points');
application_utility_tasks();
}
?>
Hippo::load() to get id and name from the database. The
method then opens the database, executes an SQL statement, and fetches num_mount_points.ArmedHippo::load() calls Hippo::load(),
which open the database and runs an SQL statement. Then ArmedHippo::load()
connects to the database again, and runs another SQL statement. Add
StealthHippo, and things get worse. Caching and connection
pooling will reduce the performance hit, but not eliminate it. Further,
application_utility_tasks() is still called all over the place.ArmedHippo class:| Field | Type | Notes |
| id | integer | Primary key Auto increment |
| num_mount_points | integer |
BusinessBase,
and make it the base class of all business classes. BusinessBase performs the application
utility tasks (permissions, transaction logging, etc.). It also handles every SQL statement.
None of the business classes ever connect to a database directly. Instead, they use a data
structure to exchange information with BusinessBase.
<?php
class Hippo extends BusinessBase
{
var $id = null;
var $name = null;
//Constructor
function Hippo()
{
$this->table_name = 'hippos';
$this->addField('id', new DataField(DataField::TYPE_NUMERIC(), true) );
$this->addField('name', new DataField(DataField::TYPE_STRING(), false) );
}
//Load record from DB
function load($id)
{
parent::load($id);
$this->id = $this->getDBValue('id');
$this->name = $this->getDBValue('name');
}
//Save record to DB
function save() {
$this->setDBValue('id', $this->id );
$this->setDBValue('name', $this->name );
parent::save();
//Get id number supplied by INSERT
$this->id = $this->getDBValue('id');
}
}
?>
Hippo::load() passes a record id to
BusinessBase::load() (line 12), then extracts the values it needs
(id and name in this case, in lines 13 and 14). Hippo::save()
stores the values it wants to save (lines 18 and 19), and calls BusinessBase::save()
(line 20). If Hippo::save() is creating a new record,
BusinessBase::save() supplies its id number, which
Hippo::save() then retrieves (line 22).
<?php
class ArmedHippo extends Hippo
{
var $num_mount_points;
//Constructor
function ArmedHippo()
{
parent::Hippo();
$this->addField('num_mount_points',
new DataField(DataField::TYPE_NUMERIC(), true) );
}
//Load record from DB
function load($id)
{
parent::load($id);
$this->num_mount_points = $this->getDBValue('num_mount_points');
}
//Save record to DB
function save()
{
$this->setDBValue('num_mount_points', $this->num_mount_points );
parent::save();
}
}
?>
num_mount_points (line 6). ArmedHippo::load()
calls Hippo::load() (line 11), then extracts the value for
the field it manages (line 12). ArmedHippo::save()
stores the value for num_mount_points (line 16), then calls
Hippo::save() (line 17).ArmedHippo can be added without disturbing
Hippo. A weight property can be added to Hippo,
without requiring changes to ArmedHippo. StealthHippo
can inherit from ArmedHippo, with no changes to existing code. Further,
no calls to functions that perform application utility tasks appear in Hippo,
ArmedHippo, or StealthHippo. They are
centralized in BusinessBase.
<?php
class BusinessBase
{
var $table_name = null;
var $record = null;
var $_id_field = null;
}
?>
$table_name is, of course, the name of the RDB table for the class.
$record is an associative array describing each record in the table.
The array's index is the name of a field in the table (e. g., id). The value in each array element
is a DataField object:
<?php
class DataField
{
function TYPE_STRING()
{
return 1;
}
function TYPE_NUMERIC()
{
return 2;
}
var $db_type = null;
var $is_primary_key = null;
var $value = null;
function DataField($col_type, $is_pk)
{
$this->db_type = $col_type;
$this->is_primary_key = $is_pk;
}
}
?>
BusinessBase::addField() (called by Hippo and ArmedHippo) adds a field to
$records:
<?php
function addField( $db_col_name, $field )
{
$this->record[$db_col_name] = $field;
}
?>
BusinessBase::load() looks like this:
<?php
function load($id)
{
$this->find_id_field();
$conn = mysql_connect('localhost', 'hippo_user', 'hippo_user');
mysql_select_db('hippos');
$query = 'select * from '.$this->table_name.' where '.$this->_id_field." = $id";
$fetched_record = mysql_fetch_assoc( mysql_query($query) );
foreach ($this->record as $col_name=>$field)
$this->record[$col_name]->value = $fetched_record[$col_name];
application_utility_tasks();
}
?>
BusinessBase::load() calls find_id_field()
in line 3 to locate the primary key field, and store its name in $this->_id_field for later use.
The method then opens the database (line 4), builds a SELECT query (line 7), and executes it
(line 9). Lines 11-13 move the data into $record, where it can be retrieved with a statement
like this from Hippo:ArmedHippo defining a field that Hippo
has already defined). Application utility tasks haven't been fully specified. Collection classes
(e. g., HippoHerd) haven't been discussed. Compound primary keys are not handled. A database
abstraction layer (e. g., ADODB) should be used. Parameters like database user name and password
should not be hard-coded. And so on.BusinessBase::save() could check for errors, log database changes,
use an RDB abstraction layer, and so on. Adding these things to BusinessBase
would yield a robust, capable, reusable class that simplifies business application development.