Identifying what your models should return is also key. Dealing with databases will normally return true/false or the insert id but if you need to return data then what type you need to return can make or break your app design.
If you need to return a JSON for consumption by a controller ( which could be for an custom API for an iPhone app or ‘dumb site’) or send back an associative array of a database query that will be sent to a classic fat-site view for iteration. (fat site = central website with all the models pulling right off the database into however you use the data)
Do you even need a design pattern for that? Build it how it makes sense, and if it happens to be in an existing design pattern all the better for the person that takes over for you later down the road.
In my experience, the patterns are usually how the MVC framework … work. For instance, a singleton would explain the CodeIgniter instance. You only want it one time so when you build a library you get the CI instance through your controller or through a magic method ( __get() for php 5 in this case). That way you can call CI libraries from within your own library without spawning a new CI instance.
I deal with databases, and I am not sure I’d ever use a singleton for any of my models. At that point you’d really want to look into some sort of ORM like doctrine. That can be limiting and full of overhead, where a good Base Model ( a class that handles CRUD functions using simple active-record methods of a MVC library ) can be just as powerful, customizable and use less overhead. I build my Base Models for CodeIgniter in a very metadata way. My Model that extends my Base Model will feed in all the meta data values like join tables and a default select statement.
Note: these examples are specific to CodeIgniter. $this->db is a CI library for ActiveRecord pattern for database interaction.
class MY_Model extends Model {
/**
| model_schemabase model_schema initiated from child.
|
| @param associative array
|
*/
protected $model_schema;
public function __construct() {
parent::Model();
}
/**
| Active Record Delete method.
|
| @param string
| @return boolean
|
*/
public function delete($qry = NULL) {
return $this->db->delete($this->model_schema['table'], $qry);
}
/**
| Active Record Create method.
|
| @param associative array
| @return boolean or integer
|
*/
public function create_and_return_id($data = NULL) {
if( ! $this->db->insert($this->model_schema['table'], $data)) {
return FALSE;
}
if( ! $id = $this->db->insert_id()) {
return FALSE;
}
return $id;
}
/**
| Active Record Get that accepts a query, limit and offset
|
| @param string x 3, 1 bool
| @return resource object or array depending on bool arg
|
*/
public function get($qry = NULL, $limit = NULL, $offset = NULL, $return_array = FALSE) {
$this->db->select($this->model_schema['select']);
if($this->model_schema['join']) {
foreach($this->model_schema['join'] as $key => $value) {
$this->db->join($key, $value, 'left');
}
}
if($limit) {
if($offset) {
$this->db->limit($limit, $offset);
} else {
$this->db->limit($limit);
}
}
if( empty($this->model_schema['where'])) {
if($qry) {
$this->db->where($this->model_schema['table'] . '.id', $qry);
}
} else {
foreach($this->model_schema['where'] as $key => $val) {
$this->db->where($key, $val);
}
}
if( ! empty($this->model_schema['order_by'])) {
$this->db->order_by($this->model_schema['order_by'], "asc");
}
$query = $this->db->get($this->model_schema['table']);
if( ! $query->num_rows() > 0) {
return FALSE;
}
if( ! $return_array) {
return $query->result();
} else {
return $query->result_array();
}
}
}
<?php if( ! defined('BASEPATH')) exit ('No direct script access allowed');
class Products extends MY_Model {
protected $model_schema = array (
'table' => 'products',
'join' => array(
'vendors' => "products.vendor_id = vendors.id",
'product_types' => 'products.product_type_id = product_types.id',
),
'select' => 'products.id, products.vendor_id, products.item, products.is_active, products.picture, products.description, product_types.type, vendors.company'
);
function __construct() {
parent::__construct();
}
}
There are other methods in the base model that like the get() that would take the join associative array into consideration. You could even add non-Base methods to your child model that call parent methods for specific needs:
function get_dropdown() {
$this->model_schema['select'] = 'products.id, products.item';
return parent::get();
}
That would go into the Products class that extends the base. You then get the ability to change the select value, in this case for a dropdown populator, while not having to rewrite the get method.
Heck, if you plan on doing a application where other counties, countries or providence might want to buy/or purchase services it from your employers… you should look into using something like mongoDB ( for regional speed ) and build an API system. Then your MVC app will just be a central application to consume bits sent back from the API.
It really just depends on your vision. Is this an online version of a desktop oriented app, or is this going to be dare I even use the ridiculous term web 2.0. Then go from there.