Файл: onlinepoisk.wm-scripts.ru/vendor/AR/lib/Relationship.php
Строк: 765
<?php
/**
* @package ActiveRecord
*/
namespace ActiveRecord;
/**
* Interface for a table relationship.
*
* @package ActiveRecord
*/
interface InterfaceRelationship
{
public function __construct($options=array());
public function build_association(Model $model, $attributes=array());
public function create_association(Model $model, $attributes=array());
}
/**
* Abstract class that all relationships must extend from.
*
* @package ActiveRecord
* @see http://www.phpactiverecord.org/guides/associations
*/
abstract class AbstractRelationship implements InterfaceRelationship
{
/**
* Name to be used that will trigger call to the relationship.
*
* @var string
*/
public $attribute_name;
/**
* Class name of the associated model.
*
* @var string
*/
public $class_name;
/**
* Name of the foreign key.
*
* @var string
*/
public $foreign_key = array();
/**
* Options of the relationship.
*
* @var array
*/
protected $options = array();
/**
* Is the relationship single or multi.
*
* @var boolean
*/
protected $poly_relationship = false;
/**
* List of valid options for relationships.
*
* @var array
*/
static protected $valid_association_options = array('class_name', 'class', 'foreign_key', 'conditions', 'select', 'readonly');
/**
* Constructs a relationship.
*
* @param array $options Options for the relationship (see {@link valid_association_options})
* @return mixed
*/
public function __construct($options=array())
{
$this->attribute_name = $options[0];
$this->options = $this->merge_association_options($options);
$relationship = strtolower(denamespace(get_called_class()));
if ($relationship === 'hasmany' || $relationship === 'hasandbelongstomany')
$this->poly_relationship = true;
if (isset($this->options['conditions']) && !is_array($this->options['conditions']))
$this->options['conditions'] = array($this->options['conditions']);
if (isset($this->options['class']))
$this->set_class_name($this->options['class']);
elseif (isset($this->options['class_name']))
$this->set_class_name($this->options['class_name']);
$this->attribute_name = strtolower(Inflector::instance()->variablize($this->attribute_name));
if (!$this->foreign_key && isset($this->options['foreign_key']))
$this->foreign_key = is_array($this->options['foreign_key']) ? $this->options['foreign_key'] : array($this->options['foreign_key']);
}
protected function get_table()
{
return Table::load($this->class_name);
}
/**
* What is this relationship's cardinality?
*
* @return bool
*/
public function is_poly()
{
return $this->poly_relationship;
}
/**
* Eagerly loads relationships for $models.
*
* This method takes an array of models, collects PK or FK (whichever is needed for relationship), then queries
* the related table by PK/FK and attaches the array of returned relationships to the appropriately named relationship on
* $models.
*
* @param Table $table
* @param $models array of model objects
* @param $attributes array of attributes from $models
* @param $includes array of eager load directives
* @param $query_keys -> key(s) to be queried for on included/related table
* @param $model_values_keys -> key(s)/value(s) to be used in query from model which is including
* @return void
*/
protected function query_and_attach_related_models_eagerly(Table $table, $models, $attributes, $includes=array(), $query_keys=array(), $model_values_keys=array())
{
$values = array();
$options = array();
$inflector = Inflector::instance();
$query_key = $query_keys[0];
$model_values_key = $model_values_keys[0];
foreach ($attributes as $column => $value)
$values[] = $value[$inflector->variablize($model_values_key)];
$values = array($values);
$options['conditions'] = SQLBuilder::create_conditions_from_underscored_string($table->conn,$query_key,$values);
if (!empty($includes))
$options['include'] = $includes;
$class = $this->class_name;
$related_models = $class::find('all', $options);
$used_models = array();
$model_values_key = $inflector->variablize($model_values_key);
$query_key = $inflector->variablize($query_key);
foreach ($models as $model)
{
$matches = 0;
$key_to_match = $model->$model_values_key;
foreach ($related_models as $related)
{
if ($related->$query_key == $key_to_match)
{
$hash = spl_object_hash($related);
if (in_array($hash, $used_models))
$model->set_relationship_from_eager_load(clone($related), $this->attribute_name);
else
$model->set_relationship_from_eager_load($related, $this->attribute_name);
$used_models[] = $hash;
$matches++;
}
}
if (0 === $matches)
$model->set_relationship_from_eager_load(null, $this->attribute_name);
}
}
/**
* Creates a new instance of specified {@link Model} with the attributes pre-loaded.
*
* @param Model $model The model which holds this association
* @param array $attributes Hash containing attributes to initialize the model with
* @return Model
*/
public function build_association(Model $model, $attributes=array())
{
$class_name = $this->class_name;
return new $class_name($attributes);
}
/**
* Creates a new instance of {@link Model} and invokes save.
*
* @param Model $model The model which holds this association
* @param array $attributes Hash containing attributes to initialize the model with
* @return Model
*/
public function create_association(Model $model, $attributes=array())
{
$class_name = $this->class_name;
$new_record = $class_name::create($attributes);
return $this->append_record_to_associate($model, $new_record);
}
protected function append_record_to_associate(Model $associate, Model $record)
{
$association =& $associate->{$this->attribute_name};
if ($this->poly_relationship)
$association[] = $record;
else
$association = $record;
return $record;
}
protected function merge_association_options($options)
{
$available_options = array_merge(self::$valid_association_options,static::$valid_association_options);
$valid_options = array_intersect_key(array_flip($available_options),$options);
foreach ($valid_options as $option => $v)
$valid_options[$option] = $options[$option];
return $valid_options;
}
protected function unset_non_finder_options($options)
{
foreach (array_keys($options) as $option)
{
if (!in_array($option, Model::$VALID_OPTIONS))
unset($options[$option]);
}
return $options;
}
protected function keyify($class_name)
{
return strtolower(classify(denamespace($class_name))). '_id';
}
/**
* Infers the $this->class_name based on $this->attribute_name.
*
* Will try to guess the appropriate class by singularizing and uppercasing $this->attribute_name.
*
* @return void
* @see attribute_name
*/
protected function set_inferred_class_name()
{
$this->set_class_name(classify($this->attribute_name, true));
}
protected function set_class_name($class_name)
{
$reflection = Reflections::instance()->add($class_name)->get($class_name);
if (!$reflection->isSubClassOf('ActiveRecord\Model'))
throw new RelationshipException("'$class_name' must extend from ActiveRecord\Model");
$this->class_name = $class_name;
}
protected function create_conditions_from_keys(Model $model, $condition_keys=array(), $value_keys=array())
{
$condition_string = implode('_and_', $condition_keys);
$condition_values = array_values($model->get_values_for($value_keys));
// return null if all the foreign key values are null so that we don't try to do a query like "id is null"
if (all(null,$condition_values))
return null;
$conditions = SQLBuilder::create_conditions_from_underscored_string(Table::load(get_class($model))->conn,$condition_string,$condition_values);
# DO NOT CHANGE THE NEXT TWO LINES. add_condition operates on a reference and will screw options array up
if (isset($this->options['conditions']))
$options_conditions = $this->options['conditions'];
else
$options_conditions = array();
return Utils::add_condition($options_conditions, $conditions);
}
/**
* Creates INNER JOIN SQL for associations.
*
* @param Table $from_table the table used for the FROM SQL statement
* @param bool $using_through is this a THROUGH relationship?
* @param string $alias a table alias for when a table is being joined twice
* @return string SQL INNER JOIN fragment
*/
public function construct_inner_join_sql(Table $from_table, $using_through=false, $alias=null)
{
if ($using_through)
{
$join_table = $from_table;
$join_table_name = $from_table->get_fully_qualified_table_name();
$from_table_name = Table::load($this->class_name)->get_fully_qualified_table_name();
}
else
{
$join_table = Table::load($this->class_name);
$join_table_name = $join_table->get_fully_qualified_table_name();
$from_table_name = $from_table->get_fully_qualified_table_name();
}
// need to flip the logic when the key is on the other table
if ($this instanceof HasMany || $this instanceof HasOne)
{
$this->set_keys($from_table->class->getName());
if ($using_through)
{
$foreign_key = $this->primary_key[0];
$join_primary_key = $this->foreign_key[0];
}
else
{
$join_primary_key = $this->foreign_key[0];
$foreign_key = $this->primary_key[0];
}
}
else
{
$foreign_key = $this->foreign_key[0];
$join_primary_key = $this->primary_key[0];
}
if (!is_null($alias))
{
$aliased_join_table_name = $alias = $this->get_table()->conn->quote_name($alias);
$alias .= ' ';
}
else
$aliased_join_table_name = $join_table_name;
return "INNER JOIN $join_table_name {$alias}ON($from_table_name.$foreign_key = $aliased_join_table_name.$join_primary_key)";
}
/**
* This will load the related model data.
*
* @param Model $model The model this relationship belongs to
*/
abstract function load(Model $model);
};
/**
* One-to-many relationship.
*
* <code>
* # Table: people
* # Primary key: id
* # Foreign key: school_id
* class Person extends ActiveRecordModel {}
*
* # Table: schools
* # Primary key: id
* class School extends ActiveRecordModel {
* static $has_many = array(
* array('people')
* );
* });
* </code>
*
* Example using options:
*
* <code>
* class Payment extends ActiveRecordModel {
* static $belongs_to = array(
* array('person'),
* array('order')
* );
* }
*
* class Order extends ActiveRecordModel {
* static $has_many = array(
* array('people',
* 'through' => 'payments',
* 'select' => 'people.*, payments.amount',
* 'conditions' => 'payments.amount < 200')
* );
* }
* </code>
*
* @package ActiveRecord
* @see http://www.phpactiverecord.org/guides/associations
* @see valid_association_options
*/
class HasMany extends AbstractRelationship
{
/**
* Valid options to use for a {@link HasMany} relationship.
*
* <ul>
* <li><b>limit/offset:</b> limit the number of records</li>
* <li><b>primary_key:</b> name of the primary_key of the association (defaults to "id")</li>
* <li><b>group:</b> GROUP BY clause</li>
* <li><b>order:</b> ORDER BY clause</li>
* <li><b>through:</b> name of a model</li>
* </ul>
*
* @var array
*/
static protected $valid_association_options = array('primary_key', 'order', 'group', 'having', 'limit', 'offset', 'through', 'source');
protected $primary_key;
private $has_one = false;
private $through;
/**
* Constructs a {@link HasMany} relationship.
*
* @param array $options Options for the association
* @return HasMany
*/
public function __construct($options=array())
{
parent::__construct($options);
if (isset($this->options['through']))
{
$this->through = $this->options['through'];
if (isset($this->options['source']))
$this->set_class_name($this->options['source']);
}
if (!$this->primary_key && isset($this->options['primary_key']))
$this->primary_key = is_array($this->options['primary_key']) ? $this->options['primary_key'] : array($this->options['primary_key']);
if (!$this->class_name)
$this->set_inferred_class_name();
}
protected function set_keys($model_class_name, $override=false)
{
//infer from class_name
if (!$this->foreign_key || $override)
$this->foreign_key = array($this->keyify($model_class_name));
if (!$this->primary_key || $override)
$this->primary_key = Table::load($model_class_name)->pk;
}
public function load(Model $model)
{
$class_name = $this->class_name;
$this->set_keys(get_class($model));
// since through relationships depend on other relationships we can't do
// this initiailization in the constructor since the other relationship
// may not have been created yet and we only want this to run once
if (!isset($this->initialized))
{
if ($this->through)
{
// verify through is a belongs_to or has_many for access of keys
if (!($through_relationship = $this->get_table()->get_relationship($this->through)))
throw new HasManyThroughAssociationException("Could not find the association $this->through in model " . get_class($model));
if (!($through_relationship instanceof HasMany) && !($through_relationship instanceof BelongsTo))
throw new HasManyThroughAssociationException('has_many through can only use a belongs_to or has_many association');
// save old keys as we will be reseting them below for inner join convenience
$pk = $this->primary_key;
$fk = $this->foreign_key;
$this->set_keys($this->get_table()->class->getName(), true);
$through_table = Table::load(classify($this->through, true));
$this->options['joins'] = $this->construct_inner_join_sql($through_table, true);
// reset keys
$this->primary_key = $pk;
$this->foreign_key = $fk;
}
$this->initialized = true;
}
if (!($conditions = $this->create_conditions_from_keys($model, $this->foreign_key, $this->primary_key)))
return null;
$options = $this->unset_non_finder_options($this->options);
$options['conditions'] = $conditions;
return $class_name::find($this->poly_relationship ? 'all' : 'first',$options);
}
private function inject_foreign_key_for_new_association(Model $model, &$attributes)
{
$this->set_keys($model);
$primary_key = Inflector::instance()->variablize($this->foreign_key[0]);
if (!isset($attributes[$primary_key]))
$attributes[$primary_key] = $model->id;
return $attributes;
}
public function build_association(Model $model, $attributes=array())
{
$attributes = $this->inject_foreign_key_for_new_association($model, $attributes);
return parent::build_association($model, $attributes);
}
public function create_association(Model $model, $attributes=array())
{
$attributes = $this->inject_foreign_key_for_new_association($model, $attributes);
return parent::create_association($model, $attributes);
}
public function load_eagerly($models=array(), $attributes=array(), $includes, Table $table)
{
$this->set_keys($table->class->name);
$this->query_and_attach_related_models_eagerly($table,$models,$attributes,$includes,$this->foreign_key, $table->pk);
}
};
/**
* One-to-one relationship.
*
* <code>
* # Table name: states
* # Primary key: id
* class State extends ActiveRecordModel {}
*
* # Table name: people
* # Foreign key: state_id
* class Person extends ActiveRecordModel {
* static $has_one = array(array('state'));
* }
* </code>
*
* @package ActiveRecord
* @see http://www.phpactiverecord.org/guides/associations
*/
class HasOne extends HasMany
{
};
/**
* @todo implement me
* @package ActiveRecord
* @see http://www.phpactiverecord.org/guides/associations
*/
class HasAndBelongsToMany extends AbstractRelationship
{
public function __construct($options=array())
{
/* options =>
* join_table - name of the join table if not in lexical order
* foreign_key -
* association_foreign_key - default is {assoc_class}_id
* uniq - if true duplicate assoc objects will be ignored
* validate
*/
}
public function load(Model $model)
{
}
};
/**
* Belongs to relationship.
*
* <code>
* class School extends ActiveRecordModel {}
*
* class Person extends ActiveRecordModel {
* static $belongs_to = array(
* array('school')
* );
* }
* </code>
*
* Example using options:
*
* <code>
* class School extends ActiveRecordModel {}
*
* class Person extends ActiveRecordModel {
* static $belongs_to = array(
* array('school', 'primary_key' => 'school_id')
* );
* }
* </code>
*
* @package ActiveRecord
* @see valid_association_options
* @see http://www.phpactiverecord.org/guides/associations
*/
class BelongsTo extends AbstractRelationship
{
public function __construct($options=array())
{
parent::__construct($options);
if (!$this->class_name)
$this->set_inferred_class_name();
//infer from class_name
if (!$this->foreign_key)
$this->foreign_key = array($this->keyify($this->class_name));
$this->primary_key = array(Table::load($this->class_name)->pk[0]);
}
public function load(Model $model)
{
$keys = array();
$inflector = Inflector::instance();
foreach ($this->foreign_key as $key)
$keys[] = $inflector->variablize($key);
if (!($conditions = $this->create_conditions_from_keys($model, $this->primary_key, $keys)))
return null;
$options = $this->unset_non_finder_options($this->options);
$options['conditions'] = $conditions;
$class = $this->class_name;
return $class::first($options);
}
public function load_eagerly($models=array(), $attributes, $includes, Table $table)
{
$this->query_and_attach_related_models_eagerly($table,$models,$attributes,$includes, $this->primary_key,$this->foreign_key);
}
};
?>