Newer
Older
<?php
/*********************************************************************
class.orm.php
Simple ORM (Object Relational Mapper) for PHP5 based on Django's ORM,
except that complex filter operations are not supported. The ORM simply
supports ANDed filter operations without any GROUP BY support.
Jared Hancock <jared@osticket.com>
Copyright (c) 2006-2013 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
class OrmException extends Exception {}
class OrmConfigurationException extends Exception {}
/**
* Meta information about a model including edges (relationships), table
* name, default sorting information, database fields, etc.
*
* This class is constructed and built automatically from the model's
* ::_inspect method using a class's ::$meta array.
*/
class ModelMeta implements ArrayAccess {
var $base = array();
function __construct($model) {
$meta = $model::$meta;
if (!$meta['table'])
throw new OrmConfigurationException(
__('Model does not define meta.table'), $model);
elseif (!$meta['pk'])
throw new OrmConfigurationException(
__('Model does not define meta.pk'), $model);
// Ensure other supported fields are set and are arrays
foreach (array('pk', 'ordering', 'deferred') as $f) {
if (!isset($meta[$f]))
$meta[$f] = array();
elseif (!is_array($meta[$f]))
$meta[$f] = array($meta[$f]);
}
// Break down foreign-key metadata
if (!isset($meta['joins']))
$meta['joins'] = array();
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
foreach ($meta['joins'] as $field => &$j) {
if (isset($j['reverse'])) {
list($model, $key) = explode('.', $j['reverse']);
$info = $model::$meta['joins'][$key];
$constraint = array();
if (!is_array($info['constraint']))
throw new OrmConfigurationException(sprintf(__(
// `reverse` here is the reverse of an ORM relationship
'%s: Reverse does not specify any constraints'),
$j['reverse']));
foreach ($info['constraint'] as $foreign => $local) {
list(,$field) = explode('.', $local);
$constraint[$field] = "$model.$foreign";
}
$j['constraint'] = $constraint;
if (!isset($j['list']))
$j['list'] = true;
}
// XXX: Make this better (ie. composite keys)
$keys = array_keys($j['constraint']);
$foreign = $j['constraint'][$keys[0]];
$j['fkey'] = explode('.', $foreign);
$j['local'] = $keys[0];
}
$this->base = $meta;
}
function offsetGet($field) {
if (!isset($this->base[$field]))
$this->setupLazy($field);
return $this->base[$field];
}
function offsetSet($field, $what) {
$this->base[$field] = $what;
}
function offsetExists($field) {
return isset($this->base[$field]);
}
function offsetUnset($field) {
throw new Exception('Model MetaData is immutable');
}
function setupLazy($what) {
switch ($what) {
case 'fields':
$this->base['fields'] = self::inspectFields();
break;
default:
throw new Exception($what . ': No such meta-data');
}
}
function inspectFields() {
return DbEngine::getCompiler()->inspectTable($this['table']);
}
}
class VerySimpleModel {
static $meta = array(
'table' => false,
'ordering' => false,
'pk' => false
);
var $ht;
var $dirty = array();
var $__deferred__ = array();
function __construct($row) {
$this->ht = $row;
}
function get($field, $default=false) {
if (array_key_exists($field, $this->ht))
return $this->ht[$field];
elseif (isset(static::$meta['joins'][$field])) {
if (!static::$meta instanceof ModelMeta)
static::_inspect();
$j = static::$meta['joins'][$field];
// Support instrumented lists and such
if (isset($this->ht[$j['local']])
&& isset($j['list']) && $j['list']) {
$fkey = $j['fkey'];
$v = $this->ht[$name] = new InstrumentedList(
// Send Model, Foriegn-Field, Local-Id
array($fkey[0], $fkey[1], $this->get($j['local']))
);
return $v;
}
// Support relationships
elseif (isset($j['fkey'])
&& ($class = $j['fkey'][0])
&& class_exists($class)) {
$v = $this->ht[$field] = $class::lookup(
array($j['fkey'][1] => $this->ht[$j['local']]));
return $v;
}
elseif (isset($this->__deferred__[$field])) {
// Fetch deferred field
$row = static::objects()->filter($this->getPk())
->values_flat($field)
->one();
if ($row)
return $this->ht[$field] = $row[0];
}
elseif ($field == 'pk') {
return $this->getPk();
}
if (isset($default))
return $default;
// TODO: Inspect fields from database before throwing this error
throw new OrmException(sprintf(__('%s: %s: Field not defined'),
get_class($this), $field));
}
function __get($field) {
return $this->get($field, null);
}
function __isset($field) {
return array_key_exists($field, $this->ht)
|| isset(static::$meta['joins'][$field]);
function __unset($field) {
unset($this->ht[$field]);
}
// Update of foreign-key by assignment to model instance
if (isset(static::$meta['joins'][$field])) {
$j = static::$meta['joins'][$field];
if ($j['list'] && ($value instanceof InstrumentedList)) {
// Magic list property
$this->ht[$field] = $value;
return;
}
if ($value === null) {
// Pass. Set local field to NULL in logic below
}
elseif ($value instanceof $j['fkey'][0]) {
if ($value->__new__)
$value->save();
// Capture the object under the object's field name
$this->ht[$field] = $value;
Loading
Loading full blame...