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 {
static $base = array(
'pk' => false,
'table' => false,
'defer' => array(),
'select_related' => array(),
);
function __construct($model) {
// TODO: Merge ModelMeta from parent model (if inherited)
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', 'defer') 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();
foreach ($meta['joins'] as $field => &$j) {
if (isset($j['reverse'])) {
list($fmodel, $key) = explode('.', $j['reverse']);
$info = $fmodel::$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] = "$fmodel.$foreign";
}
$j['constraint'] = $constraint;
if (!isset($j['list']))
$j['list'] = true;
if (!isset($j['null']))
$j['null'] = $info['null'] ?: false;
}
// 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;
case 'newInstance':
$class_repr = sprintf(
'O:%d:"%s":0:{}',
strlen($this->model), $this->model
);
$this->base['newInstance'] = function() use ($class_repr) {
return unserialize($class_repr);
};
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[$field] = 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)) {
try {
$v = $this->ht[$field] = $class::lookup(
array($j['fkey'][1] => $this->ht[$j['local']]));
}
catch (DoesNotExist $e) {
$v = null;
}
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);
Loading
Loading full blame...