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 {}
// Database fields/tables do not match codebase
class InconsistentModelException extends OrmException {
function __construct() {
// Drop the model cache (just incase)
ModelMeta::flushModelCache();
call_user_func_array(array('parent', '__construct'), func_get_args());
}
}
/**
* 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(),
'joins' => array(),
'foreign_keys' => array(),
function __construct($model) {
// Merge ModelMeta from parent model (if inherited)
$parent = get_parent_class($this->model);
if (is_subclass_of($parent, 'VerySimpleModel')) {
$meta = $parent::getMeta()->extend($model::$meta);
}
else {
$meta = $model::$meta + self::$base;
}
if (!$meta['view']) {
if (!$meta['table'])
throw new OrmConfigurationException(
sprintf(__('%s: Model does not define meta.table'), $this->model));
elseif (!$meta['pk'])
throw new OrmConfigurationException(
sprintf(__('%s: Model does not define meta.pk'), $this->model));
}
// Ensure other supported fields are set and are arrays
foreach (array('pk', 'ordering', 'defer', 'select_related') as $f) {
if (!isset($meta[$f]))
$meta[$f] = array();
elseif (!is_array($meta[$f]))
$meta[$f] = array($meta[$f]);
}
// Break down foreign-key metadata
foreach ($meta['joins'] as $field => &$j) {
if ($j['local'])
$meta['foreign_keys'][$j['local']] = $field;
$this->base = $meta;
}
function extend($meta) {
if ($meta instanceof self)
$meta = $meta->base;
return $meta + $this->base + self::$base;
}
/**
* Adds some more information to a declared relationship. If the
* relationship is a reverse relation, then the information from the
* reverse relation is loaded into the local definition
*
* Compiled-Join-Structure:
* 'constraint' => array(local => array(foreign_field, foreign_class)),
* Constraint used to construct a JOIN in an SQL query
* 'list' => boolean
* TRUE if an InstrumentedList should be employed to fetch a list
* of related items
* 'broker' => Handler for the 'list' property. Usually a subclass of
* 'InstrumentedList'
* 'null' => boolean
* TRUE if relation is nullable
* 'fkey' => array(class, pk)
* Classname and field of the first item in the constraint that
* points to a PK field of a foreign model
* 'local' => string
* The local field corresponding to the 'fkey' property
*/
function processJoin(&$j) {
if (isset($j['reverse'])) {
list($fmodel, $key) = explode('.', $j['reverse']);
// NOTE: It's ok if the forein meta data is not yet inspected.
$info = $fmodel::$meta['joins'][$key];
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($L,$field) = is_array($local) ? $local : explode('.', $local);
$constraint[$field ?: $L] = array($fmodel, $foreign);
}
if (!isset($j['list']))
$j['list'] = true;
if (!isset($j['null']))
// By default, reverse releationships can be empty lists
$j['null'] = true;
else {
foreach ($j['constraint'] as $local => $foreign) {
list($class, $field) = $constraint[$local]
= is_array($foreign) ? $foreign : explode('.', $foreign);
if ($j['list'] && !isset($j['broker'])) {
$j['broker'] = 'InstrumentedList';
}
if ($j['broker'] && !class_exists($j['broker'])) {
throw new OrmException($j['broker'] . ': List broker does not exist');
}
foreach ($constraint as $local => $foreign) {
list($class, $field) = $foreign;
if ($local[0] == "'" || $field[0] == "'" || !class_exists($class))
continue;
function addJoin($name, array $join) {
$this->base['joins'][$name] = $join;
$this->processJoin($this->base['joins'][$name]);
}
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() {
Loading
Loading full blame...