Skip to content

Commit

Permalink
Slowly chaging eager loading logic to be able to trigger before find on
Browse files Browse the repository at this point in the history
associations correctly
  • Loading branch information
lorenzo committed Feb 9, 2014
1 parent 8db3d69 commit 4840c96
Show file tree
Hide file tree
Showing 8 changed files with 49 additions and 38 deletions.
39 changes: 18 additions & 21 deletions src/ORM/Association.php
Expand Up @@ -390,7 +390,6 @@ protected function _options(array $options) {
*/
public function attachTo(Query $query, array $options = []) {
$target = $this->target();
$source = $this->source();
$options += [
'includeFields' => true,
'foreignKey' => $this->foreignKey(),
Expand All @@ -409,28 +408,14 @@ public function attachTo(Query $query, array $options = []) {
}

$options['conditions'] = $query->newExpr()->add($options['conditions']);
$extraOptions = [];
$dummy = $target->query();

if (!empty($options['queryBuilder'])) {
$dummy = $options['queryBuilder']($dummy);
}

$this->_dispatchBeforeFind($dummy);
$options = $this->_copyAttributes($dummy, $options);
$joinOptions = ['table' => 1, 'conditions' => 1, 'type' => 1];
$query->join([$target->alias() => array_intersect_key($options, $joinOptions)]);

if (empty($options['fields'])) {
$f = isset($options['fields']) ? $options['fields'] : null;
if ($options['includeFields'] && ($f === null || $f !== false)) {
$options['fields'] = $target->schema()->columns();
}
}

if (!empty($options['fields'])) {
$query->select($query->aliasFields($options['fields'], $target->alias()));
}
$this->_copyAttributes($query, $dummy, $options);
}

/**
Expand Down Expand Up @@ -489,11 +474,23 @@ protected function _dispatchBeforeFind($query) {
$table->getEventManager()->dispatch($event);
}

protected function _copyAttributes($query, $options) {
debug($query->contain());
$options['fields'] = $query->clause('select') ?: $options['fields'];
$options['conditions']->add($query->clause('where') ?: []);
return $options;
protected function _copyAttributes($query, $surrogate, $options) {
$joinOptions = ['table' => 1, 'conditions' => 1, 'type' => 1];
$options['conditions']->add($surrogate->clause('where') ?: []);
$target = $this->_targetTable;
$query->join([$target->alias() => array_intersect_key($options, $joinOptions)]);

$options['fields'] = $surrogate->clause('select') ?: $options['fields'];
if (empty($options['fields'])) {
$f = isset($options['fields']) ? $options['fields'] : null;
if ($options['includeFields'] && ($f === null || $f !== false)) {
$options['fields'] = $target->schema()->columns();
}
}

if (!empty($options['fields'])) {
$query->select($query->aliasFields($options['fields'], $target->alias()));
}
}

/**
Expand Down
22 changes: 15 additions & 7 deletions src/ORM/EagerLoader.php
Expand Up @@ -153,12 +153,17 @@ public function normalized(Table $repository) {
}

$contain = [];
foreach ($this->_containments as $table => $options) {
foreach ($this->_containments as $alias => $options) {
if (!empty($options['instance'])) {
$contain = (array)$this->_containments;
break;
}
$contain[$table] = $this->_normalizeContain($repository, $table, $options);
$contain[$alias] = $this->_normalizeContain(
$repository,
$alias,
$options,
$alias
);
}

return $this->_normalized = $contain;
Expand Down Expand Up @@ -234,17 +239,17 @@ protected function _reformatContain($associations, $original) {
* those that cannot be loaded without executing a separate query.
*
* @param \Cake\ORM\Query $query The query to be modified
* @param \Cake\ORM\Table $repository The repository containing the associations
* @param boolean $includeFields whether to append all fields from the associations
* to the passed query. This can be overridden according to the settings defined
* per association in the containments array
* @return void
*/
public function attachAssociations(Query $query, $includeFields) {
public function attachAssociations(Query $query, Table $repository, $includeFields) {
if (empty($this->_containments)) {
return;
}

$repository = $query->repository();
foreach ($this->attachableAssociations($repository) as $options) {
$config = $options['config'] + ['includeFields' => $includeFields];
$options['instance']->attachTo($query, $config);
Expand Down Expand Up @@ -275,10 +280,11 @@ public function attachableAssociations(Table $repository) {
* @param Table $parent owning side of the association
* @param string $alias name of the association to be loaded
* @param array $options list of extra options to use for this association
* @param string $path A dot separated string of associations that lead to this `$alias`
* @return array normalized associations
* @throws \InvalidArgumentException When containments refer to associations that do not exist.
*/
protected function _normalizeContain(Table $parent, $alias, $options) {
protected function _normalizeContain(Table $parent, $alias, $options, $path) {
$defaults = $this->_containOptions;
$instance = $parent->association($alias);
if (!$instance) {
Expand All @@ -293,12 +299,14 @@ protected function _normalizeContain(Table $parent, $alias, $options) {
$config = [
'associations' => [],
'instance' => $instance,
'config' => array_diff_key($options, $extra)
'config' => array_diff_key($options, $extra),
'path' => $path
];
$config['canBeJoined'] = $instance->canBeJoined($config['config']);

foreach ($extra as $t => $assoc) {
$config['associations'][$t] = $this->_normalizeContain($table, $t, $assoc);
$step = $path . '.' . $t;
$config['associations'][$t] = $this->_normalizeContain($table, $t, $assoc, $step);
}

return $config;
Expand Down
2 changes: 1 addition & 1 deletion src/ORM/Query.php
Expand Up @@ -918,7 +918,7 @@ protected function _transformQuery() {
$this->from([$this->_table->alias() => $this->_table->table()]);
}
$this->_addDefaultFields();
$this->eagerLoader()->attachAssociations($this, !$this->_hasFields);
$this->eagerLoader()->attachAssociations($this, $this->_table, !$this->_hasFields);
}
return parent::_transformQuery();
}
Expand Down
4 changes: 2 additions & 2 deletions tests/TestCase/ORM/Association/BelongsToManyTest.php
Expand Up @@ -1508,7 +1508,7 @@ public function testAttachToBeforeFindExtraOptions() {
$association = new BelongsToMany('Tags', $config);
$listener = $this->getMock('stdClass', ['__invoke']);
$this->tag->getEventManager()->attach($listener, 'Model.beforeFind');
$opts = ['somthing' => 'more'];
$opts = ['something' => 'more'];
$newQuery = $this->tag->query()->applyOptions($opts);
$listener->expects($this->once())->method('__invoke')
->with($this->isInstanceOf('\Cake\Event\Event'), $newQuery, $opts, false);
Expand All @@ -1520,7 +1520,7 @@ public function testAttachToBeforeFindExtraOptions() {
->with($this->isInstanceOf('\Cake\Event\Event'), $newQuery2, [], false);

$association->attachTo($query, ['queryBuilder' => function($q) {
return $q->applyOptions(['somthing' => 'more']);
return $q->applyOptions(['something' => 'more']);
}]);
}

Expand Down
6 changes: 4 additions & 2 deletions tests/TestCase/ORM/Association/BelongsToTest.php
Expand Up @@ -344,8 +344,9 @@ public function testAttachToBeforeFind() {
$listener = $this->getMock('stdClass', ['__invoke']);
$this->company->getEventManager()->attach($listener, 'Model.beforeFind');
$association = new BelongsTo('Companies', $config);
$dummy = $this->company->query();
$listener->expects($this->once())->method('__invoke')
->with($this->isInstanceOf('\Cake\Event\Event'), $query, [], false);
->with($this->isInstanceOf('\Cake\Event\Event'), $dummy, [], false);
$association->attachTo($query);
}

Expand All @@ -366,8 +367,9 @@ public function testAttachToBeforeFindExtraOptions() {
$this->company->getEventManager()->attach($listener, 'Model.beforeFind');
$association = new BelongsTo('Companies', $config);
$options = ['something' => 'more'];
$dummy = $this->company->query()->applyOptions($options);
$listener->expects($this->once())->method('__invoke')
->with($this->isInstanceOf('\Cake\Event\Event'), $query, $options, false);
->with($this->isInstanceOf('\Cake\Event\Event'), $dummy, $options, false);
$association->attachTo($query, ['queryBuilder' => function($q) {
return $q->applyOptions(['something' => 'more']);
}]);
Expand Down
6 changes: 4 additions & 2 deletions tests/TestCase/ORM/Association/HasManyTest.php
Expand Up @@ -788,8 +788,9 @@ public function testAttachToBeforeFind() {
$listener = $this->getMock('stdClass', ['__invoke']);
$association = new HasMany('Articles', $config);
$this->article->getEventManager()->attach($listener, 'Model.beforeFind');
$dummy = $this->article->query();
$listener->expects($this->once())->method('__invoke')
->with($this->isInstanceOf('\Cake\Event\Event'), $query, [], false);
->with($this->isInstanceOf('\Cake\Event\Event'), $dummy, [], false);
$association->attachTo($query);
}

Expand All @@ -810,8 +811,9 @@ public function testAttachToBeforeFindExtraOptions() {
$association = new HasMany('Articles', $config);
$this->article->getEventManager()->attach($listener, 'Model.beforeFind');
$opts = ['something' => 'more'];
$dummy = $this->article->query()->applyOptions($opts);
$listener->expects($this->once())->method('__invoke')
->with($this->isInstanceOf('\Cake\Event\Event'), $query, $opts, false);
->with($this->isInstanceOf('\Cake\Event\Event'), $dummy, $opts, false);
$association->attachTo($query, ['queryBuilder' => function($q) {
return $q->applyOptions(['something' => 'more']);
}]);
Expand Down
6 changes: 4 additions & 2 deletions tests/TestCase/ORM/Association/HasOneTest.php
Expand Up @@ -320,9 +320,10 @@ public function testAttachToBeforeFind() {
];
$listener = $this->getMock('stdClass', ['__invoke']);
$this->profile->getEventManager()->attach($listener, 'Model.beforeFind');
$dummy = $this->profile->query();
$association = new HasOne('Profiles', $config);
$listener->expects($this->once())->method('__invoke')
->with($this->isInstanceOf('\Cake\Event\Event'), $query, [], false);
->with($this->isInstanceOf('\Cake\Event\Event'), $dummy, [], false);
$association->attachTo($query);
}

Expand All @@ -343,8 +344,9 @@ public function testAttachToBeforeFindExtraOptions() {
$this->profile->getEventManager()->attach($listener, 'Model.beforeFind');
$association = new HasOne('Profiles', $config);
$opts = ['something' => 'more'];
$dummy = $this->profile->query()->applyOptions($opts);
$listener->expects($this->once())->method('__invoke')
->with($this->isInstanceOf('\Cake\Event\Event'), $query, $opts, false);
->with($this->isInstanceOf('\Cake\Event\Event'), $dummy, $opts, false);
$association->attachTo($query, ['queryBuilder' => function($q) {
return $q->applyOptions(['something' => 'more']);
}]);
Expand Down
2 changes: 1 addition & 1 deletion tests/TestCase/ORM/EagerLoaderTest.php
Expand Up @@ -271,7 +271,7 @@ public function testContainToFieldsPredefined() {
$loader = new EagerLoader;
$loader->contain($contains);
$query->select('foo.id');
$loader->attachAssociations($query, true);
$loader->attachAssociations($query, $table, true);

$select = $query->clause('select');
$expected = [
Expand Down

0 comments on commit 4840c96

Please sign in to comment.