Файл: onlinepoisk.wm-scripts.ru/vendor/AR/lib/Table.php
Строк: 600
<?php
/**
 * @package ActiveRecord
 */
namespace ActiveRecord;
/**
 * Manages reading and writing to a database table.
 *
 * This class manages a database table and is used by the Model class for
 * reading and writing to its database table. There is one instance of Table
 * for every table you have a model for.
 *
 * @package ActiveRecord
 */
class Table
{
    private static $cache = array();
    public $class;
    public $conn;
    public $pk;
    public $last_sql;
    // Name/value pairs of columns in this table
    public $columns = array();
    /**
     * Name of the table.
     */
    public $table;
    /**
     * Name of the database (optional)
     */
    public $db_name;
    /**
     * Name of the sequence for this table (optional). Defaults to {$table}_seq
     */
    public $sequence;
    /**
     * A instance of CallBack for this model/table
     * @static
     * @var object ActiveRecordCallBack
     */
    public $callback;
    /**
     * List of relationships for this table.
     */
    private $relationships = array();
    public static function load($model_class_name)
    {
        if (!isset(self::$cache[$model_class_name]))
        {
            /* do not place set_assoc in constructor..it will lead to infinite loop due to
               relationships requesting the model's table, but the cache hasn't been set yet */
            self::$cache[$model_class_name] = new Table($model_class_name);
            self::$cache[$model_class_name]->set_associations();
        }
        return self::$cache[$model_class_name];
    }
    public static function clear_cache($model_class_name=null)
    {
        if ($model_class_name && array_key_exists($model_class_name,self::$cache))
            unset(self::$cache[$model_class_name]);
        else
            self::$cache = array();
    }
    public function __construct($class_name)
    {
        $this->class = Reflections::instance()->add($class_name)->get($class_name);
        // if connection name property is null the connection manager will use the default connection
        $connection = $this->class->getStaticPropertyValue('connection',null);
        $this->conn = ConnectionManager::get_connection($connection);
        $this->set_table_name();
        $this->get_meta_data();
        $this->set_primary_key();
        $this->set_sequence_name();
        $this->set_delegates();
        $this->set_setters_and_getters();
        $this->callback = new CallBack($class_name);
        $this->callback->register('before_save', function(Model $model) { $model->set_timestamps(); }, array('prepend' => true));
        $this->callback->register('after_save', function(Model $model) { $model->reset_dirty(); }, array('prepend' => true));
    }
    public function create_joins($joins)
    {
        if (!is_array($joins))
            return $joins;
        $self = $this->table;
        $ret = $space = '';
        $existing_tables = array();
        foreach ($joins as $value)
        {
            $ret .= $space;
            if (stripos($value,'JOIN ') === false)
            {
                if (array_key_exists($value, $this->relationships))
                {
                    $rel = $this->get_relationship($value);
                    // if there is more than 1 join for a given table we need to alias the table names
                    if (array_key_exists($rel->class_name, $existing_tables))
                    {
                        $alias = $value;
                        $existing_tables[$rel->class_name]++;
                    }
                    else
                    {
                        $existing_tables[$rel->class_name] = true;
                        $alias = null;
                    }
                    $ret .= $rel->construct_inner_join_sql($this, false, $alias);
                }
                else
                    throw new RelationshipException("Relationship named $value has not been declared for class: {$this->class->getName()}");
            }
            else
                $ret .= $value;
            $space = ' ';
        }
        return $ret;
    }
    public function options_to_sql($options)
    {
        $table = array_key_exists('from', $options) ? $options['from'] : $this->get_fully_qualified_table_name();
        $sql = new SQLBuilder($this->conn, $table);
        if (array_key_exists('joins',$options))
        {
            $sql->joins($this->create_joins($options['joins']));
            // by default, an inner join will not fetch the fields from the joined table
            if (!array_key_exists('select', $options))
                $options['select'] = $this->get_fully_qualified_table_name() . '.*';
        }
        if (array_key_exists('select',$options))
            $sql->select($options['select']);
        if (array_key_exists('conditions',$options))
        {
            if (!is_hash($options['conditions']))
            {
                if (is_string($options['conditions']))
                    $options['conditions'] = array($options['conditions']);
                call_user_func_array(array($sql,'where'),$options['conditions']);
            }
            else
            {
                if (!empty($options['mapped_names']))
                    $options['conditions'] = $this->map_names($options['conditions'],$options['mapped_names']);
                $sql->where($options['conditions']);
            }
        }
        if (array_key_exists('order',$options))
            $sql->order($options['order']);
        if (array_key_exists('limit',$options))
            $sql->limit($options['limit']);
        if (array_key_exists('offset',$options))
            $sql->offset($options['offset']);
        if (array_key_exists('group',$options))
            $sql->group($options['group']);
        if (array_key_exists('having',$options))
            $sql->having($options['having']);
        return $sql;
    }
    public function find($options)
    {
        $sql = $this->options_to_sql($options);
        $readonly = (array_key_exists('readonly',$options) && $options['readonly']) ? true : false;
        $eager_load = array_key_exists('include',$options) ? $options['include'] : null;
        return $this->find_by_sql($sql->to_s(),$sql->get_where_values(), $readonly, $eager_load);
    }
    public function find_by_sql($sql, $values=null, $readonly=false, $includes=null)
    {
        $this->last_sql = $sql;
        $collect_attrs_for_includes = is_null($includes) ? false : true;
        $list = $attrs = array();
        $sth = $this->conn->query($sql,$this->process_data($values));
        while (($row = $sth->fetch()))
        {
            $model = new $this->class->name($row,false,true,false);
            if ($readonly)
                $model->readonly();
            if ($collect_attrs_for_includes)
                $attrs[] = $model->attributes();
            $list[] = $model;
        }
        if ($collect_attrs_for_includes && !empty($list))
            $this->execute_eager_load($list, $attrs, $includes);
        return $list;
    }
    /**
     * Executes an eager load of a given named relationship for this table.
     *
     * @param $models array found modesl for this table
     * @param $attrs array of attrs from $models
     * @param $includes array eager load directives
     * @return void
     */
    private function execute_eager_load($models=array(), $attrs=array(), $includes=array())
    {
        if (!is_array($includes))
            $includes = array($includes);
        foreach ($includes as $index => $name)
        {
            // nested include
            if (is_array($name))
            {
                $nested_includes = count($name) > 1 ? $name : $name[0];
                $name = $index;
            }
            else
                $nested_includes = array();
            $rel = $this->get_relationship($name, true);
            $rel->load_eagerly($models, $attrs, $nested_includes, $this);
        }
    }
    public function get_column_by_inflected_name($inflected_name)
    {
        foreach ($this->columns as $raw_name => $column)
        {
            if ($column->inflected_name == $inflected_name)
                return $column;
        }
        return null;
    }
    public function get_fully_qualified_table_name($quote_name=true)
    {
        $table = $quote_name ? $this->conn->quote_name($this->table) : $this->table;
        if ($this->db_name)
            $table = $this->conn->quote_name($this->db_name) . ".$table";
        return $table;
    }
    /**
     * Retrieve a relationship object for this table. Strict as true will throw an error
     * if the relationship name does not exist.
     *
     * @param $name string name of Relationship
     * @param $strict bool
     * @throws RelationshipException
     * @return Relationship or null
     */
    public function get_relationship($name, $strict=false)
    {
        if ($this->has_relationship($name))
            return $this->relationships[$name];
        if ($strict)
            throw new RelationshipException("Relationship named $name has not been declared for class: {$this->class->getName()}");
        return null;
    }
    /**
     * Does a given relationship exist?
     *
     * @param $name string name of Relationship
     * @return bool
     */
    public function has_relationship($name)
    {
        return array_key_exists($name, $this->relationships);
    }
    public function insert(&$data, $pk=null, $sequence_name=null)
    {
        $data = $this->process_data($data);
        $sql = new SQLBuilder($this->conn,$this->get_fully_qualified_table_name());
        $sql->insert($data,$pk,$sequence_name);
        $values = array_values($data);
        return $this->conn->query(($this->last_sql = $sql->to_s()),$values);
    }
    public function update(&$data, $where)
    {
        $data = $this->process_data($data);
        $sql = new SQLBuilder($this->conn,$this->get_fully_qualified_table_name());
        $sql->update($data)->where($where);
        $values = $sql->bind_values();
        return $this->conn->query(($this->last_sql = $sql->to_s()),$values);
    }
    public function delete($data)
    {
        $data = $this->process_data($data);
        $sql = new SQLBuilder($this->conn,$this->get_fully_qualified_table_name());
        $sql->delete($data);
        $values = $sql->bind_values();
        return $this->conn->query(($this->last_sql = $sql->to_s()),$values);
    }
    /**
     * Add a relationship.
     *
     * @param Relationship $relationship a Relationship object
     */
    private function add_relationship($relationship)
    {
        $this->relationships[$relationship->attribute_name] = $relationship;
    }
    private function get_meta_data()
    {
        // as more adapters are added probably want to do this a better way
        // than using instanceof but gud enuff for now
        $quote_name = !($this->conn instanceof PgsqlAdapter);
        $this->columns = $this->conn->columns($this->get_fully_qualified_table_name($quote_name));
    }
    /**
     * Replaces any aliases used in a hash based condition.
     *
     * @param $hash array A hash
     * @param $map array Hash of used_name => real_name
     * @return array Array with any aliases replaced with their read field name
     */
    private function map_names(&$hash, &$map)
    {
        $ret = array();
        foreach ($hash as $name => &$value)
        {
            if (array_key_exists($name,$map))
                $name = $map[$name];
            $ret[$name] = $value;
        }
        return $ret;
    }
    private function &process_data($hash)
    {
        if (!$hash)
            return $hash;
        foreach ($hash as $name => &$value)
        {
            if ($value instanceof DateTime)
            {
                if (isset($this->columns[$name]) && $this->columns[$name]->type == Column::DATE)
                    $hash[$name] = $this->conn->date_to_string($value);
                else
                    $hash[$name] = $this->conn->datetime_to_string($value);
            }
            else
                $hash[$name] = $value;
        }
        return $hash;
    }
    private function set_primary_key()
    {
        if (($pk = $this->class->getStaticPropertyValue('pk',null)) || ($pk = $this->class->getStaticPropertyValue('primary_key',null)))
            $this->pk = is_array($pk) ? $pk : array($pk);
        else
        {
            $this->pk = array();
            foreach ($this->columns as $c)
            {
                if ($c->pk)
                    $this->pk[] = $c->inflected_name;
            }
        }
    }
    private function set_table_name()
    {
        if (($table = $this->class->getStaticPropertyValue('table',null)) || ($table = $this->class->getStaticPropertyValue('table_name',null)))
            $this->table = $table;
        else
        {
            // infer table name from the class name
            $this->table = Inflector::instance()->tableize($this->class->getName());
            // strip namespaces from the table name if any
            $parts = explode('\',$this->table);
            $this->table = $parts[count($parts)-1];
        }
        if(($db = $this->class->getStaticPropertyValue('db',null)) || ($db = $this->class->getStaticPropertyValue('db_name',null)))
            $this->db_name = $db;
    }
    private function set_sequence_name()
    {
        if (!$this->conn->supports_sequences())
            return;
        if (!($this->sequence = $this->class->getStaticPropertyValue('sequence')))
            $this->sequence = $this->conn->get_sequence_name($this->table,$this->pk[0]);
    }
    private function set_associations()
    {
        require_once 'Relationship.php';
        foreach ($this->class->getStaticProperties() as $name => $definitions)
        {
            if (!$definitions || !is_array($definitions))
                continue;
            foreach ($definitions as $definition)
            {
                $relationship = null;
                switch ($name)
                {
                    case 'has_many':
                        $relationship = new HasMany($definition);
                        break;
                    case 'has_one':
                        $relationship = new HasOne($definition);
                        break;
                    case 'belongs_to':
                        $relationship = new BelongsTo($definition);
                        break;
                    case 'has_and_belongs_to_many':
                        $relationship = new HasAndBelongsToMany($definition);
                        break;
                }
                if ($relationship)
                    $this->add_relationship($relationship);
            }
        }
    }
    /**
     * Rebuild the delegates array into format that we can more easily work with in Model.
     * Will end up consisting of array of:
     *
     * array('delegate' => array('field1','field2',...),
     *       'to'       => 'delegate_to_relationship',
     *       'prefix'    => 'prefix')
     */
    private function set_delegates()
    {
        $delegates = $this->class->getStaticPropertyValue('delegate',array());
        $new = array();
        if (!array_key_exists('processed', $delegates))
            $delegates['processed'] = false;
        if (!empty($delegates) && !$delegates['processed'])
        {
            foreach ($delegates as &$delegate)
            {
                if (!is_array($delegate) || !isset($delegate['to']))
                    continue;
                if (!isset($delegate['prefix']))
                    $delegate['prefix'] = null;
                $new_delegate = array(
                    'to'        => $delegate['to'],
                    'prefix'    => $delegate['prefix'],
                    'delegate'    => array());
                foreach ($delegate as $name => $value)
                {
                    if (is_numeric($name))
                        $new_delegate['delegate'][] = $value;
                }
                $new[] = $new_delegate;
            }
            $new['processed'] = true;
            $this->class->setStaticPropertyValue('delegate',$new);
        }
    }
    /**
     * Builds the getters/setters array by prepending get_/set_ to the method names.
     */
    private function set_setters_and_getters()
    {
        $build = array('setters', 'getters');
        foreach ($build as $type)
        {
            $methods = array();
            $prefix = substr($type,0,3) . "_";
            foreach ($this->class->getStaticPropertyValue($type,array()) as $method)
                $methods[] = (substr($method,0,4) != $prefix ? "{$prefix}$method" : $method);
            $this->class->setStaticPropertyValue($type,$methods);
        }
    }
};
?>