/vendor/php-activerecord/php-activerecord/lib/Table.php
PHP | 555 lines | 402 code | 78 blank | 75 comment | 46 complexity | 4402c088615e3b4492cfd226d387cec9 MD5 | raw file
- <?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 ActiveRecord\CallBack
- */
- 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);
- $this->reestablish_connection(false);
- $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 reestablish_connection($close=true)
- {
- // if connection name property is null the connection manager will use the default connection
- $connection = $this->class->getStaticPropertyValue('connection',null);
- if ($close)
- {
- ConnectionManager::drop_connection($connection);
- static::clear_cache();
- }
- return ($this->conn = ConnectionManager::get_connection($connection));
- }
- 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) > 0 ? $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);
- $table_name = $this->get_fully_qualified_table_name($quote_name);
- $conn = $this->conn;
- $this->columns = Cache::get("get_meta_data-$table_name", function() use ($conn, $table_name) { return $conn->columns($table_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';
- $namespace = $this->class->getNamespaceName();
- foreach ($this->class->getStaticProperties() as $name => $definitions)
- {
- if (!$definitions)# || !is_array($definitions))
- continue;
- foreach (wrap_strings_in_arrays($definitions) as $definition)
- {
- $relationship = null;
- $definition += compact('namespace');
- 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);
- }
- }
- /**
- * @deprecated Model.php now checks for get|set_ methods via method_exists so there is no need for declaring static g|setters.
- */
- private function set_setters_and_getters()
- {
- $getters = $this->class->getStaticPropertyValue('getters', array());
- $setters = $this->class->getStaticPropertyValue('setters', array());
- if (!empty($getters) || !empty($setters))
- trigger_error('static::$getters and static::$setters are deprecated. Please define your setters and getters by declaring methods in your model prefixed with get_ or set_. See
- http://www.phpactiverecord.org/projects/main/wiki/Utilities#attribute-setters and http://www.phpactiverecord.org/projects/main/wiki/Utilities#attribute-getters on how to make use of this option.', E_USER_DEPRECATED);
- }
- };
- ?>