As part of my Book List project I am making use of the Template design pattern for my CodeIgniter database table models. The idea is that an abstract class contains the common processing needed by each class that will extend it, and it will have some number of abstract methods which essentially force each child class to set variables that differentiate each child class — in this case the database table and column names for the specific model class.
The abstract Table class:
<?php
/**
* @author Charles Reace (www.charles-reace.com)
*/
/**
* Common processing for DB table model classes
*/
abstract class Table extends Model
{
/**
* @var string DB table name
*/
protected $table;
/**
* @var array Table row data
*/
protected $data;
/**
* @var array Table column names
*/
protected $columns;
/**
* Constructor
* @return void
*/
public function __construct($id = null)
{
parent::Model();
$this->setTable();
$this->setColumns();
if(empty($id) or $this->populate($id) == false)
{
foreach($this->columns as $col)
{
$this->data[$col] = '';
}
}
}
/**
* Define the values of $this->columns
*
* Array of column names, 'id' must be first
*/
abstract protected function setColumns();
/**
* Set the table name in $this->table
*/
abstract protected function setTable();
/**
* Populate data from DB for specified ID
* @param int $id
* @return false
*/
public function populate($id)
{
$query = $this->db->get_where($this->table, array('id' => (int)$id));
if($query)
{
if($query->num_rows())
{
$results = $query->result_array();
$this->data = array_shift($results);
return true;
}
user_error("No rows found for id '$id'");
return false;
}
user_error("Invalid query");
return false;
}
/**
* Update this row in DB
* @return int Affected rows (or false on query failure)
*/
public function update()
{
if(empty($this->data['id']))
{
user_error("No ID");
return false;
}
$data = $this->data;
unset($data['id']);
$this->db->where('id', $this->data['id']);
$result = $this->db->update($this->table, $data);
return ($result == false) ? $result : $this->db->affected_rows();
}
/**
* Insert this row into DB
* @return bool
*/
public function insert()
{
$data = $this->data;
unset($data['id']);
$result = $this->db->insert($this->table, $data);
if($result and $this->db->affected_rows())
{
$this->data['id'] = $this->db->insert_id();
return true;
}
return false;
}
/**
* Delete this record from DB
* @return mixed Number rows deleted, else false on failure
*/
public function delete()
{
if(empty($this->data['id']))
{
user_error("No ID");
return false;
}
$this->db->where('id', $this->data['id']);
$result = $this->db->delete($this->table);
return ($result == false) ? $result : $this->db->affected_rows();
}
}
Here is an example of a child class that extends Table, in this case for the “book” database table:
<?php
/**
* @author Charles Reace (www.charles-reace.com)
*/
/**
* @uses Table
*/
require_once dirname(__FILE__).DIRECTORY_SEPARATOR."table.php";
/**
* Model for the book table
*/
class Book extends Table
{
public function __construct($id = null)
{
parent::__construct($id);
}
protected function setTable()
{
$this->table = 'book';
}
protected function setColumns()
{
$this->columns = array(
'id',
'title',
'sub_title',
'series',
'copyright'
);
}
}
This has proven to be a time-saver both in amount of code that needs to be written and in terms of debugging: once I get the methods of the abstract class correct, I'm confident of their solidity in each child class.
