During some spare time I developed an abstract ORM class that can be extended by entity classes that automatically implement the create, update and delete methods for the object’s table entry in a relational database. This is a work in progress, but I will go through the existing version to explain.
The create, update, and delete methods can be called on any object that extends the AxisEntityObject class to perform the related operation on that instance. It will immediately remove, create or update the object related entry in the database using created SQL and PHP PDO. But also, each class inherits a static method that is the factory for the object type which requires only the primary key associated with an object to generate it.
Lets take a look at the interface.
<?php
interface IAxisEntityObject{
public function Create();
public static function Factory($primary_key, $custom_class_name = '');
public static function FactoryList($primary_keys, $custom_class_name = '');
public static function FactoryAll();
public function Update();
public function Delete();
}
As you can see we require the defined methods and also require three types of factories that are common in ORM object generation. We can product all objects of a class, a single object by primary key, or a list based on an array of primary keys.
Here is the implementation of the abstract class.
abstract class AxisEntityObject implements IAxisEntityObject
{
//////////////////////////////////
// CUD METHODS
////////////////////////////////
public function Create()
{
// We need to grab the current objects class before we call its functions
// so that we can link to the related table and not to a table named after our abstract class
$class = get_class($this);
$primary_key_column = call_user_func(array($class, 'primaryKeyName'), $class);
$table = call_user_func(array($class, 'tableName'), $class);
$entry_fields = array();
// Loop through each of the fields of the object instance
// and look for any other properties that need to cascade the creation process
foreach($this as $field => $value)
{
if($field != $primary_key_column)
{
if($value instanceof AxisEntityObject)
{
$value->Create();
}
elseif(is_array($value))
{
foreach($value as $array_value)
{
if($array_value instanceof AxisEntityObject)
{
// Cascade call all properties Create method if they are of the same class type
// recursive in nature
$array_value->Create();
}
}
}
elseif($this->$field != null)
{
// Build our SQL
$k_sql .= $field.', ';
$v_sql .= ':'.$field.', ';
$entry_fields[$field] = $value;
}
}
}
// Use PHP PDO to persist the objects fields and complete the create method
$cmd = "INSERT INTO ".$table." (".substr($k_sql, 0, -2).") VALUES (".substr($v_sql, 0, -2).")";
$STH = $GLOBALS['DBH']->prepare($cmd);
$STH->execute((array)$entry_fields);
return $GLOBALS['DBH']->lastInsertId();
}
public function Update()
{
// Again for updates we need the class name that extends AxisEntityObjects
$class = get_class($this);
$primary_key_column = call_user_func(array($class, 'primaryKeyName'), $class);
$table = call_user_func(array($class, 'tableName'), $class);
// Cascade the update call to all properties that are also extend the abstract class
if($this->$primary_key_column != '')
{
foreach($this as $field => $value)
{
if($field != $primary_key_column)
{
if($value instanceof AxisEntityObject)
{
$value->Update();
}
elseif(is_array($value))
{
foreach($value as $array_value)
{
if($array_value instanceof AxisEntityObject)
{
$array_value->Update();
}
}
}
elseif($value != null)
{
$v_sql .= $field . ' = :'.$field.', ';
$entry_fields[$field] = $value;
}
}
}
// Persist the object changes
$cmd = "UPDATE ".$table." SET ".substr($v_sql, 0, -2)." WHERE ".$primary_key_column." = ".$this->$primary_key_column;
$STH = $GLOBALS['DBH']->prepare($cmd);
$STH->execute((array)$entry_fields);
}
}
public function Delete()
{
$class = get_class($this);
$primary_key_column = call_user_func(array($class, 'primaryKeyName'), $class);
$table = call_user_func(array($class, 'tableName'), $class);
if($this->$primary_key_column != '')
{
$cmd = "DELETE FROM ".$table." WHERE ".$primary_key_column." = ".$this->$primary_key_column;
$STH = $GLOBALS['DBH']->prepare($cmd);
$STH->execute((array)$this);
}
}
//////////////////////////////////
// RETRIEVE METHODS
////////////////////////////////
public static function Factory($primary_key, $custom_class_name = '')
{
// Get our proper class name and table names
if($custom_class_name != '')
$class = $custom_class_name;
else
$class = get_called_class();
$primary_key_column = call_user_func(array($class, 'primaryKeyName'), $class);
$table = call_user_func(array($class, 'tableName'), $class);
// get table entry for object
$cmd = "SELECT
*
FROM
`".$table."`
WHERE
`".$primary_key_column."` = '".$primary_key."'";
// Execute call
$STH = $GLOBALS['DBH']->prepare($cmd);
$STH->setFetchMode(PDO::FETCH_CLASS, $class);
$STH->execute();
$result = $STH->fetch(PDO::FETCH_CLASS);
if($result == false)
return null;
return $result;
}
public static function FactoryAll()
{
// get the proper class
$class = get_called_class();
$primary_key_column = call_user_func(array($class, 'primaryKeyName'), $class);
$table = call_user_func(array($class, 'tableName'), $class);
$cmd = "SELECT ".$primary_key_column." FROM ".$table;
$STH = $GLOBALS['DBH']->prepare($cmd);
$STH->execute();
// Call our Factory method on all returned objects mapped
while($row = $STH->fetch(PDO::FETCH_OBJ))
{
$list[] = call_user_func(array($class, 'Factory'), $row->$primary_key_column, $class);
}
return $list;
}
public static function FactoryList($primary_keys, $custom_class_name = '')
{
// get our proper class
if($custom_class_name != '')
$class = $custom_class_name;
else
$class = get_called_class();
$primary_key_column = call_user_func(array($class, 'primaryKeyName'), $class);
$table = call_user_func(array($class, 'tableName'), $class);
$in = "('".implode("', '", $primary_keys)."')";
// get table entries
$cmd = "SELECT
*
FROM
`".$table."`
WHERE
`".$primary_key_column."` IN ".$in."
ORDER BY
CASE
";
foreach($primary_keys as $i => $v)
{
$cmd .= " WHEN ".$primary_key_column." = ".$v." THEN ".$i." ";
}
$cmd .= " END";
// USE PDO to build the objects of the proper class
$STH = $GLOBALS['DBH']->prepare($cmd);
$STH->setFetchMode(PDO::FETCH_CLASS, $class);
$STH->execute();
while($row = $STH->fetch(PDO::FETCH_CLASS))
{
$list[] = $row;
}
return $list;
}
// These methods are used to map the table names and class names for the ORM logic
protected static function primaryKeyName($class)
{
return call_user_func(array($class, 'underscoreFromCamelCase'), $class) . '_id';
}
protected static function tableName($class)
{
return DB_T_PREPEND . call_user_func(array($class, 'underscoreFromCamelCase'), $class);
}
protected static function underscoreFromCamelCase($str)
{
$str[0] = strtolower($str[0]);
$func = create_function('$c', 'return "_" . strtolower($c[1]);');
return preg_replace_callback('/([A-Z])/', $func, $str);
}
}
This class will perform ORM operations on an instance and can be used very simply in the following ways.
Lets extend this class with a normal entity class like Fruit.
Entity Class Creation:
class Fruit extends AxisEntityObject
{
}
Simple!
Create:
$fruit = new Fruit(); $fruit->fruit_name = 'Pineapple'; $fruit_id = $fruit->Create();
Retrieve and Update:
$fruit = Fruit::Factory('21'); // 21 is our primary key id in the database.
Combine retrieval with updating for simple object updating.
$fruit = Fruit::Factory('21');
$fruit->name = 'Orange';
$fruit->Update();
Delete:
$fruit = Fruit::Factory('21'); // 21 is our primary key id in the database.
$fruit->Delete();


