Permalink
Browse files

First pass at refactoring SQL relationship support.

  • Loading branch information...
1 parent 9b7ce70 commit ab41ff6719c661870d2141cf09eb81beb9f7cacb @nateabele nateabele committed Feb 26, 2011
Showing with 88 additions and 107 deletions.
  1. +5 −17 data/Entity.php
  2. +1 −1 data/Model.php
  3. +7 −9 data/Source.php
  4. +2 −0 data/collection/RecordSet.php
  5. +53 −8 data/model/Query.php
  6. +4 −72 data/source/Database.php
  7. +16 −0 tests/cases/data/source/DatabaseTest.php
View
22 data/Entity.php
@@ -10,7 +10,8 @@
use BadMethodCallException;
use UnexpectedValueException;
-use lithium\util\Collection as Col;
+use lithium\data\Source;
+use lithium\data\Collection;
/**
* `Entity` is a smart data object which represents data such as a row or document in a
@@ -244,21 +245,8 @@ public function data($name = null) {
if ($name) {
return $this->__get($name);
}
- $return = $this->_updated + $this->_data;
- foreach($return as $key => $val){
- if(is_callable(array($val, 'data'))) {
- $return[$key] = $val->data();
- }else if(is_array($val) && is_numeric(key($val))){
- $r = array();
- foreach($val as $k => $v){
- if(is_callable(array($v, 'data'))) {
- $r[$k] = $v->data();
- }
- }
- $return[$key] = $r;
- }
- }
- return $return;
+ $related = array_map(function($rel) { return $rel->data(); }, $this->_relationships);
+ return $related + $this->_updated + $this->_data;
}
/**
@@ -429,7 +417,7 @@ public function assignTo($parent, array $config = array()) {
public function to($format, array $options = array()) {
switch ($format) {
case 'array':
- $result = Col::toArray($this->data());
+ $result = Collection::toArray($this->data());
break;
default:
$result = $this;
View
2 data/Model.php
@@ -439,7 +439,7 @@ public static function find($type, array $options = array()) {
$type = 'first';
}
- $options = (array) $options + (array) $self->_query + (array) $defaults;
+ $options = (array) $options + (array) $self->_query;
$meta = array('meta' => $self->_meta, 'name' => get_called_class());
$params = compact('type', 'options');
View
16 data/Source.php
@@ -274,24 +274,22 @@ public function item($model, array $data = array(), array $options = array()) {
$defaults = array('class' => 'entity');
$options += $defaults;
- $type = $options['class'];
- $class = isset($this->_classes[$type]) ? $this->_classes[$type] : $type;
+ $class = $options['class'];
unset($options['class']);
- foreach($data as $key => $val) {
- switch(true) {
+ foreach ($data as $key => $val) {
+ switch (true) {
case is_array($val) && !is_numeric(key($val)):
$data[$key] = $this->item($model, $val, $options);
- break;
+ break;
case is_array($val) && is_numeric(key($val)):
foreach($val as $k => $v) {
- $data[$key][$k] = $this->item($model, $v, $options);
+ $data[$key][$k] = is_object($v) ? $v : $this->item($model, $v, $options);
}
- break;
+ break;
}
}
-
- return new $class(compact('model', 'data') + $options);
+ return $this->_instance($class, compact('model', 'data') + $options);
}
}
View
2 data/collection/RecordSet.php
@@ -268,6 +268,7 @@ protected function _populate($data = null, $key = null) {
if (!($data = $data ?: $this->_result->next())) {
return $this->close();
}
+
$key = null;
$index = false;
$offset = 0;
@@ -282,6 +283,7 @@ protected function _populate($data = null, $key = null) {
if ($model == $this->_model) {
$key = $model::key($record);
+
if (($index = array_search(reset($key), $this->_index)) !== false) {
$recordMap[$this->_model] = $this->_data[$index];
if (is_object($recordMap[$this->_model])) {
View
61 data/model/Query.php
@@ -117,7 +117,9 @@ protected function _init() {
$this->_config['whitelist'] = array_combine($list, $list);
}
if ($this->_config['with']) {
- $this->_associate($this->_config['with']);
+ foreach ((array) $this->_config['with'] as $related) {
+ $this->_associate($related);
+ }
}
$joins = $this->_config['joins'];
$this->_config['joins'] = array();
@@ -253,7 +255,8 @@ public function limit($limit = null) {
if ($limit) {
$this->_config['limit'] = intval($limit);
return $this;
- }else if($limit === false){
+ }
+ if ($limit === false) {
$this->_config['limit'] = null;
return $this;
}
@@ -314,7 +317,7 @@ public function group($group = null) {
$this->_config['group'] = $group;
return $this;
}
- if($group === false){
+ if ($group === false) {
$this->_config['group'] = null;
return $this;
}
@@ -493,7 +496,7 @@ public function alias($alias = null) {
}
/**
- * Gets a custom query field which does not have an accessor method.
+ * Gets or sets a custom query field which does not have an accessor method.
*
* @param string $method Query part.
* @param string $params Query parameters.
@@ -536,18 +539,60 @@ protected function _associate($related) {
if (!$model = $this->model()) {
return;
}
- $queryClass = get_class($this);
+ $class = get_class($this);
+ $hasMany = false;
+ $addHostSubquery = false;
foreach ((array) $related as $name => $config) {
if (is_int($name)) {
$name = $config;
$config = array();
}
- if (!$relation = $model::relations($name)) {
- throw new QueryException("Related model not found.");
+ if (!$relationship = $model::relations($name)) {
+ throw new QueryException("Model relationship `{$name}` not found.");
}
- $config += $relation->data();
+ list($name, $query) = $this->_fromRelationship($relationship);
+ $this->join($name, $query);
+ $hasMany = $hasMany || $relationship->type() == 'hasMany';
}
+
+ if ($hasMany && $query->limit()) {
+ $model = $query->model();
+ $name = $model::meta('name');
+ $key = $model::key();
+
+ $idOptions = array(
+ 'relations' => false,
+ 'group' => 'GROUP BY ' . $name . '.' . $key,
+ 'fields' => array($name . '.' . $key),
+ 'joins' => $query->joins()
+ ) + $args;
+
+ $query->fields($idOptions['fields'], true)->group($idOptions['group']);
+
+ $ids = $self->read($query, $idOptions);
+ $ids = array_map(function($index) use ($key) { return $index[$key]; }, $ids->data());
+
+ $fields = $args['fields'] ?: false;
+ $group = $args['group'] ?: false;
+
+ $query->fields($fields, true)->group($group)->limit(false)->conditions(array(
+ "{$name}.{$key}" => $ids
+ ));
+ }
+ }
+
+ protected function _fromRelationship($rel) {
+ $name = $rel->name();
+ $this->_config['relationships'][$rel->fieldName()] = $name;
+
+ $constraint = $rel->constraints();
+ $class = get_class($this);
+
+ return array($name, $this->_instance($class, compact('constraint') + array(
+ 'type' => 'LEFT',
+ 'model' => $rel->to()
+ )));
}
}
View
76 data/source/Database.php
@@ -250,7 +250,7 @@ public function create($query, array $options = array()) {
public function read($query, array $options = array()) {
$defaults = array('
return' => is_string($query) ? 'array' : 'item', 'schema' => array(),
- 'relations' => true, 'return' => 'item'
+ 'return' => 'item'
);
$options += $defaults;
@@ -263,70 +263,6 @@ public function read($query, array $options = array()) {
$model = is_object($query) ? $query->model() : null;
$schema = !is_null($model) ? $model::schema() : array();
- $hasMany = array();
-
- $addHostSubquery = false;
- $relations = is_array($args['relations']) ? $args['relations'] : array();
- foreach($relations as $relation) {
- $conditions = array();
- $constraint = is_array($relation->constraint) ? $relation->constraint : array();
- foreach($constraint as $key => $val){
- $conditions[] = $key . ' = ' . $val;
- }
- $constraint = implode(' AND ', $conditions);
- switch($relation->type) {
- case 'hasOne':
- case 'belongsTo':
- $query->join($relation->to, new Query(array(
- 'model' => $relation->to,
- 'constraint' => $constraint,
- 'type' => 'LEFT'
- )));
- break;
- case 'hasMany':
- $query->join($relation->to, new Query(array(
- 'model' => $relation->to,
- 'constraint' => $constraint,
- 'type' => 'LEFT'
- )));
-
- $toModel = $relation->to;
- $hasMany[] = $toModel::meta('name');
-
- if($query->limit()) {
- $addHostSubquery = true;
- }
- break;
- }
- }
-
- if($addHostSubquery){
- $model = $query->model();
- $name = $model::meta('name');
- $key = $model::key();
-
- $idOptions = array(
- 'relations' => false,
- 'group' => 'GROUP BY ' . $name . '.' . $key,
- 'fields' => array($name . '.' . $key),
- 'joins' => $query->joins()
- ) + $args;
-
- $query->fields($idOptions['fields'], true)
- ->group($idOptions['group']);
-
- $ids = $self->read($query, $idOptions);
- $ids = $ids->data();
- $ids = array_map(function($index) use ($key){
- return $index[$key];
- }, $ids);
-
- $query->fields($args['fields'] ?: false, true)
- ->group($args['group'] ?: false)
- ->limit(false)
- ->conditions(array($name . '.' . $key => $ids));
- }
-
if (is_string($query)) {
$sql = String::insert($query, $self->value($args));
} else {
@@ -350,18 +286,14 @@ public function read($query, array $options = array()) {
}
return $records;
case 'item':
- $return = $self->item($query->model(), array(), compact('query', 'result') +
- array(
- 'class' => 'set',
- ));
+ return $self->item($query->model(), array(), compact('query', 'result') + array(
+ 'class' => 'set',
+ ));
//@todo - this is a hack, should have a more formal way to setup the data
if (count($hasMany)) {
count($return);
}
-
-
- return $return;
}
});
}
View
16 tests/cases/data/source/DatabaseTest.php
@@ -461,6 +461,22 @@ public function testRawConditions() {
$this->assertEqual("WHERE CUSTOM", $this->db->conditions("CUSTOM", $query));
}
+ public function testRelationshipGeneration() {
+ $comment = 'lithium\tests\mocks\data\model\MockDatabaseComment';
+
+ $hasMany = $this->db->relationship($this->_model, 'hasMany', 'Comments', array(
+ 'to' => $comment
+ ));
+ $this->assertEqual(array('id' => 'mock_database_post_id'), $hasMany->keys());
+ $this->assertEqual('comments', $hasMany->fieldName());
+
+ $belongsTo = $this->db->relationship($comment, 'belongsTo', 'Posts', array(
+ 'to' => $this->_model
+ ));
+ $this->assertEqual(array('post_id' => 'id'), $belongsTo->keys());
+ $this->assertEqual('post', $belongsTo->fieldName());
+ }
+
/**
* Tests that various syntaxes for the `'order'` key of the query object produce the correct
* SQL.

0 comments on commit ab41ff6

Please sign in to comment.