Skip to content
Snippets Groups Projects
class.orm.php 71.7 KiB
Newer Older
Jared Hancock's avatar
Jared Hancock committed
<?php
/*********************************************************************
    class.orm.php

    Simple ORM (Object Relational Mapper) for PHP5 based on Django's ORM,
Jared Hancock's avatar
Jared Hancock committed
    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(),
        'view' => false,
    var $model;

    function __construct($model) {
        $this->model = $model;
        $meta = $model::$meta + self::$base;
        // 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) {
            $this->processJoin($j);
        unset($j);
    function processJoin(&$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];
    }

    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']);
    }
}

Jared Hancock's avatar
Jared Hancock committed
class VerySimpleModel {
    static $meta = array(
        'table' => false,
        'ordering' => false,
        'pk' => false
    );

    var $ht;
Jared Hancock's avatar
Jared Hancock committed
    var $__new__ = false;
    var $__deleted__ = false;
    var $__deferred__ = array();
Jared Hancock's avatar
Jared Hancock committed

    function __construct($row) {
        $this->ht = $row;
    }

    function get($field, $default=false) {
Jared Hancock's avatar
Jared Hancock committed
        if (array_key_exists($field, $this->ht))
            return $this->ht[$field];
        elseif (isset(static::$meta['joins'][$field])) {
            // Make sure joins were inspected
            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;
                }
                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
Loading
Loading full blame...