Ok ... I spent a couple hours and hacked a little more on the Data Mapper I started. It ended up being mostly SQL generation code and that usually points to a big waste of time to me

. It still only does loading of objects. It supports basic joins but is certainly is not very flexible nor does it handle any error conditions really.
I also threw in the ability to read in the mappings from an XML file as that was mentioned a couple times. That is pretty trivial with Simple XML. I just wanted to see how adding wrappers around the manual configuration core would work. It seems pretty straightforward to add automation.
It is PHP4 code (except SimpleXML) should run PHP4 or PHP5. You should be able to cut and paste into a file to run. The core code (Db_Datamapper, Db_Datamapper_Mapping and Db_Datamapper_Join) is still pretty small -- which is the only promising thing.
Code: Select all
<?php
/*
* Mock_Db and Mock_Db_Result class are just to support this example
*/
class Mock_Db {
var $_data = array();
var $error = 0;
function Mock_Db() {
// put some test data into the mapper for this example
$this->_data = array(
'Steve' => array(
'id' => 1,
'userid' => 'Steve',
'password' => 'lollypop',
'inactive' => 'N',
),
'Sally' => array(
'id' => 2,
'userid' => 'Sally',
'password' => 'sallybop',
'inactive' => 'N',
),
);
}
function & query($sql) {
// get value from "WHERE key='value' at end of SQL
$a = split('WHERE', $sql);
$a = split('=', $a[1]);
$key = trim($a[1], " ='");
if (isset($this->_data[$key])) {
$row = $this->_data[$key];
} else {
$row = array();
$this->error = 1;
}
$result = new Mock_Db_Result($row);
return $result;
}
function isError() {
return $this->error;
}
}
class Mock_Db_Result {
var $_row = array(); // debug data for this example
var $error = 0;
function Mock_Db_Result($row) {
// put some test data into the mapper for this example
if ($row) {
$this->_row = $row;
} else {
$this->error = 1;
}
}
function fetchRow() {
return $this->_row;
}
function isError() {
return $this->error;
}
}
/*
* Data Mapper classes start here
*/
class Db_Datamapper {
var $db = null;
var $class_name = '';
var $table_name = '';
var $objects = array(); // track created objects
var $mappings = array();
var $joins = array();
function Db_Datamapper($db, $class_name, $table_name='') {
$this->setDb($db);
$this->setClass($class_name);
$this->setTable($table_name);
}
function setDb(&$db) {
$this->db =& $db;
}
function setClass($class_name) {
$this->class_name = $class_name;
}
function setTable($table_name) {
$this->table_name = $table_name;
}
function addMapping(&$mapping) {
if ($mapping->property_name) {
// use default table name if none provided
$mapping->table_name = $mapping->table_name ? $mapping->table_name : $this->table_name;
$this->mappings[$mapping->property_name] =& $mapping;
return true;
}
return false;
}
function addJoin(&$join) {
$this->joins[] =& $join;
return true;
}
function getTableNames() {
$tables = array();
if ($this->table_name) {
$tables[$this->table_name] = 1;
}
foreach (array_keys($this->mappings) as $property_name) {
if ($this->mappings[$property_name]->table_name) {
$tables[$this->mappings[$property_name]->table_name] = 1;
}
}
return array_keys($tables);
}
function getTableNamesSQL() {
$tables = $this->getTableNames();
// more than one table then join
if (count($tables) > 1) {
if ($this->joins) {
$sql = '';
foreach (array_keys($this->joins) as $id) {
// are both tables in join in the table array?
if (in_array($this->joins[$id]->table_name1, $tables) && in_array($this->joins[$id]->table_name2, $tables)) {
if (! $sql) {
$sql = $this->joins[$id]->table_name1;
}
$sql .= $this->joins[$id]->getSQL();
}
return $sql;
}
} else {
return implode(',', $tables);
}
} else {
return $tables[0];
}
}
function getFieldNames() {
$fields = array();
foreach (array_keys($this->mappings) as $property_name) {
$fields[$property_name] = $this->mappings[$property_name]->getFieldName();
}
return $fields;
}
function getKeyField() {
foreach (array_keys($this->mappings) as $property_name) {
if ($this->mappings[$property_name]->isKey()) {
return $this->mappings[$property_name]->getFieldName();
}
}
}
function getOperationSQL($field_name, $value, $operator='=') {
if ($field_name && $value && $operator) {
return "$field_name$operator'$value'";
}
return '';
}
function & load($key) {
if (! isset($this->objects[$key])) {
$fields = $this->getFieldNames();
$tables = $this->getTableNamesSQL();
$where = $this->getOperationSQL($this->getKeyField(), $key);
$row = array();
if ($fields && $tables && $where) {
$sql = 'SELECT ' . implode(',', $fields) . ' FROM ' . $tables . ' WHERE ' . $where;
echo "Db_Datamapper::load $key SQL=$sql<br/>";
$result = $this->db->query($sql); // replace this with SQL generation and fetch data
if (! $this->db->isError()) {
$row = $result->fetchRow();
}
}
if ($row) {
$class_name = $this->class_name;
$this->objects[$key] =& new $class_name ();
foreach (array_keys($this->mappings) as $property_name) {
$this->mappings[$property_name]->setObject($this->objects[$key], $row);
}
}
} else {
echo "Db_Datamapper::load $key already loaded.<br/>";
}
return $this->objects[$key];
}
function insert() {
}
function update() {
}
function delete() {
}
}
class Db_Datamapper_Mapping {
var $property_name = '';
var $field_name = '';
var $type = '';
var $size = 0;
var $is_key = false;
var $table_name = '';
var $filters=array();
function Db_Datamapper_Mapping($property_name, $field_name, $type, $size, $is_key=false, $table_name='', $filters=array()) {
$this->property_name = $property_name;
$this->field_name = $field_name;
$this->type = $type;
$this->size = $size;
$this->is_key = $is_key;
$this->table_name = $table_name;
$this->filters = $filters;
}
function setObject($object, $row) {
if ($this->property_name && $this->field_name) {
$property = $this->property_name;
if (isset($row[$this->field_name])) {
$object->$property = $row[$this->field_name];
}
}
}
function setRow($row, $object) {
if ($this->property_name && $this->field_name) {
$property = $this->property_name;
if (isset($object->$property)) {
$row[$this->field_name] = $object->$property;
}
}
}
function getFieldName() {
if ($this->table_name) {
return $this->table_name . '.' . $this->field_name;
} else {
return $this->field_name;
}
}
function isKey() {
return $this->is_key;
}
}
class Db_Datamapper_Join {
var $table_name1;
var $field_name1;
var $table_name2;
var $field_name2;
var $join_type;
function Db_Datamapper_Join($table_name1, $field_name1, $table_name2, $field_name2, $join_type='') {
$this->table_name1 = $table_name1;
$this->field_name1 = $field_name1;
$this->table_name2 = $table_name2;
$this->field_name2 = $field_name2;
$this->join_type = $join_type;
}
function getSQL() {
return " {$this->join_type} JOIN {$this->table_name2} ON {$this->table_name1}.{$this->field_name1}={$this->table_name2}.{$this->field_name2}";
}
}
class Db_Datamapper_Xml extends Db_Datamapper {
function Db_Datamapper_Xml (&$db, $filename) {
$xml =& simplexml_load_file($filename);
if ($xml) {
$this->setDb($db);
$this->setClass(strval($xml->class));
$this->setTable(strval($xml->table));
foreach ($xml->mapping as $map) {
$property = strval($map->property);
$field = strval($map->field);
$type = strval($map->type);
$size = strval($map->size);
$is_key = strval($map->is_key);
$table = strval($map->table);
$filters = strval($map->filters);
$this->addMapping(new Db_Datamapper_Mapping($property, $field, $type, $size, $is_key, $table, $filters));
}
foreach ($xml->join as $join) {
$table1 = strval($join->table1);
$field1 = strval($join->field1);
$table2 = strval($join->table2);
$field2 = strval($join->field2);
$join_type = strval($join->join_type);
$this->addJoin(new Db_Datamapper_Join($table1, $field1, $table2, $field2, $join_type));
}
}
}
}
/*
* Example code start here
*/
class User_Mapper extends Db_Datamapper {
function User_Mapper (&$db) {
$this->setDb($db);
$this->setClass('User');
$this->setTable('users');
$this->addMapping(new Db_Datamapper_Mapping('username', 'userid', 'string', 20, true, '', array()));
$this->addMapping(new Db_Datamapper_Mapping('password', 'password', 'string', 24, false, '', array()));
$this->addMapping(new Db_Datamapper_Mapping('active', 'inactive', 'string', 1, false, '', array()));
// uncomment these two lines and comment previous line to show join generation
# $this->addMapping(new Db_Datamapper_Mapping('active', 'inactive', 'string', 1, false, 'company', array()));
# $this->addJoin(new Db_Datamapper_Join('users', 'userid', 'company', 'userid', 'LEFT'));
}
}
class User {
var $username = '';
var $password = '';
var $active = false;
}
$mapper =& new User_Mapper(new Mock_Db());
#$mapper =& new Db_Datamapper_Xml(new Mock_Db(), 'mapping01.xml');
$User1 = $mapper->load('Steve');
$User2 = $mapper->load('Sally');
echo 'User1:<pre>' . print_r($User1, 1) . '</pre>';
echo 'User2:<pre>' . print_r($User2, 1) . '</pre>';
// loading a record again should return the same object loaded above without loading
$User3 = $mapper->load('Steve');
And here is the XML file if anyone cares...
Code: Select all
<?xml version="1.0" encoding="utf-8" ?>
<map>
<class>User</class>
<table>users</table>
<mapping>
<property>username</property>
<field>userid</field>
<type>string</type>
<size>20</size>
<is_key>1</is_key>
<table></table>
<filters></filters>
</mapping>
<mapping>
<property>password</property>
<field>password</field>
<type>string</type>
<size>24</size>
<is_key></is_key>
<table></table>
<filters></filters>
</mapping>
<mapping>
<property>active</property>
<field>inactive</field>
<type>string</type>
<size>20</size>
<is_key></is_key>
<table>company</table>
<filters></filters>
</mapping>
<join>
<table1>users</table1>
<field1>userid</field1>
<table2>company</table2>
<field2>userid</field2>
<join_type>LEFT</join_type>
</join>
</map>
edited to run in PHP4, XML only works in PHP5