Thought I'd share my ORM.
It works in active record. Ignore the fact that I'm using a custom DB class, you can see how it works.
PHP Code:
<?php
class ActiveRecord {
public $table;
public $properties = array();
public $primaryKey = 'id';
public $key;
public $db;
public $fullyFetched = false;
public static $defaults;
public $relations = array();
public static function setDefaults($defaults) {
self::$defaults = $defaults;
}
public function isNewRecord() {
return $this->properties[$this->primaryKey] === null;
}
public function __set($value, $data) {
if (method_exists($this, '_set' . $value)) $this->{'_set' . $value}($data);
else {
if ($data == (string) (double) $data) $data = (double) $data;
else if (is_numeric($data)) $data = (integer) $data;
$this->properties[$value] = $data;
}
}
public function __toString() {
return (string) $this->properties[$this->primaryKey];
}
public function __get($value) {
if (array_key_exists($value, $this->properties)) return $this->properties[$value];
else if (isset($this->relations[$value])) return $this->relations[$value];
else if (method_exists($this, '_get' . $value)) return $this->relations[$value] = $this->{'_get' . $value}();
else return null;
}
public function __construct($id = null, $field = null) {
foreach (self::$defaults as $property => $value) $this->$property = $value;
if ($this->db == null) throw new Exception('Trying to create an ActiveRecord object with no database connection set.');
if ($id !== null) $this->load($id, $field);
//Make sure the PK property is always set. This ensures the __toString() method doesn't give a notice.
if (!array_key_exists($this->primaryKey, $this->properties)) $this->properties[$this->primaryKey] = null;
$this->init();
}
public function init() {
}
public function unlinkRecord($recursive = true) {
$this->properties[$this->primaryKey] = null;
if ($recursive) foreach ($this as &$value) $this->unlinkAll($value);
}
public function unlinkAll($obj) {
if ($obj !== $this && $obj !== $this->properties) {
if ($obj instanceof ActiveRecord) $obj->unlinkRecord(true);
else if (is_array($obj)) {
foreach ($obj as &$value) $this->unlinkAll($value);
}
}
}
public function save($recursive = true) {
$query = $this->_buildQuery();
if ($this->properties[$this->primaryKey] == null) $new = true;
else $new = false;
$this->db->query('INSERT INTO ' . $this->table . ' SET ' . $query . ' ON DUPLICATE KEY UPDATE ' . $query);
if ($new) $this->properties[$this->primaryKey] = $this->db->insertId;
if ($recursive) foreach ($this as &$value) $this->saveAll($value);
}
private function saveAll($obj) {
if ($obj !== $this && $obj !== $this->properties) {
if ($obj instanceof ActiveRecord) $obj->save(true);
else if (is_array($obj)) {
foreach ($obj as &$value) $this->saveAll($value);
}
}
}
public function delete() {
if ($this->properties[$this->primaryKey] != null) {
$this->db->query('DELETE FROM ' . $this->table . ' WHERE `' . $this->primaryKey . '` = %0%', $this->properties[$this->primaryKey]);
}
}
public function setForeignKey($field, ActiveRecord $obj) {
$this->properties[$field] = &$obj;
}
public function load($id, $field = null) {
if ($field === null) $field = $this->primaryKey;
$res = $this->db->query('SELECT * FROM ' . $this->table . ' WHERE ' . $field . ' = %0%', $id);
$this->fill($res);
}
protected function fill($result) {
if ($row = $this->db->fetchObject($result)) {
foreach ($row as $key => $value) $this->properties[$key] = $value;
$this->fullyFetched = true;
}
}
public function _buildQuery() {
$tmp = array();
foreach ($this->properties as $field => $value) {
if ($value === null) $tmp[] = '`' . $field . '` = NULL';
else {
if (is_object($value)) $value = $value->__toString(); //PHP 5.1 support
$tmp[] = '`' . $field . '` = ' . $this->db->escape($value);
}
}
return implode(', ', $tmp);
}
}
?>
Admittedly it uses magic methods to avoid some of the problems. It allows having references in the $properties array.
You could add a "Map" function if you wanted to explicitly define all the columns in the model.
Some example models:
Simple load/update/save:
PHP Code:
class User extends ActiveRecord {
public $table = 'user';
}
$user = new User(123);
$user->name = 'Tom';
$user->save();
What if the user has an address in a separate table?
It takes the idea of PHP's magic __get() and extends it. I now have magic functions for getting the related records.
PHP Code:
class User extends ActiveRecord {
public $table = 'user';
private function _getAddress() {
$address = new ActiveRecord();
$address->setTable('Address')->load($this->id, 'userId');
$address->setForeignKey('userId', $this);
return $address;
}
private function _setAddress(ActiveRecord $address) {
$address->setForeignKey('userId', $this);
$this->relations['address'] = $address;
}
}
$user = new User(123);
$user->address->city = 'London';
$user->save();
New records can be done in the same way.
PHP Code:
$user = new User();
$user->name = 'Tom';
$user->address = new Address(); //$user->address->setForeignKey() is called from _setAddress
$user->address->city = 'London';
$user->save();
In this example $user->address is mapped to $user->_getAddress(); but this will only happen the first time. This way the database is accessed on the fly only when it's needed.
One to many relationships can be handled in the same way:
PHP Code:
class User extends ActiveRecord {
public $table = 'user';
private function _getPets() {
$result = $this->db->query('SELECT * FROM PETS WHERE userId = ' . $this->db->escape($this->id));
$pets = array();
while ($pet = $result->fetch_object('DbRecord')) {
$pet->setTable('pet');
$pet->setForeignKey('userId', $this);
$pets[] = $pet;
}
return $pets;
}
}
$user = new User(123);
if (count($user->pets) > 0) {
echo $user->pets[0]->name;
}
The real magic in this system comes from the __toString() function. It allows objects to be placed in the $properties array, but when the query is built the object is automatically converted to it's primary key value meaning automatic foreign key resolution for contained objects
Bookmarks