Skip to content

Commit

Permalink
Starting to use an object for representing an eagerloadable
Browse files Browse the repository at this point in the history
  • Loading branch information
lorenzo committed Jan 14, 2015
1 parent c3d8e23 commit 58903d8
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 53 deletions.
127 changes: 127 additions & 0 deletions src/ORM/EagerLoadable.php
@@ -0,0 +1,127 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\ORM;

/**
*/
class EagerLoadable
{

protected $_name;

protected $_associations = [];

protected $_instance;

protected $_config = [];

protected $_aliasPath;

protected $_propertyPath;

protected $_canBeJoined = false;

protected $_forMatching;

public function __construct($name, array $config = [])
{
$this->_name = $name;
$allowed = [
'associations', 'instance', 'config', 'canBeJoined',
'aliasPath', 'propertyPath', 'forMatching'
];
foreach ($allowed as $property) {
if (isset($config[$property])) {
$this->{'_' . $property} = $config[$property];
}
}
}

public function addAssociation($name, EagerLoadable $association)
{
$this->_associations[$name] = $association;
}

public function associations()
{
return $this->_associations;
}

public function instance($instance = null)
{
if ($instance === null) {
return $this->_instance;
}
$this->_instance = $instance;
}

public function aliasPath($path = null)
{
if ($path === null) {
return $this->_aliasPath;
}
$this->_aliasPath = $path;
}

public function propertyPath($path = null)
{
if ($path === null) {
return $this->_propertyPath;
}
$this->_propertyPath = $path;
}

public function canBeJoined($possible = null)
{
if ($possible === null) {
return $this->_canBeJoined;
}
$this->_canBeJoined = $possible;
}

public function config(array $config = null)
{
if ($config === null) {
return $this->_config;
}
$this->_config = $config;
}

public function forMatching($matching = null)
{
if ($matching === null) {
return $this->_forMatching;
}
$this->_forMatching = $matching;
}

public function asContainArray()
{
$associations = [];
foreach ($this->_associations as $assoc) {
$associations += $assoc->asContainArray();
}
$config = $this->_config;
if ($this->_forMatching !== null) {
$config = ['matching' => $this->_forMatching] + $config;
}
return [
$this->_name => [
'associations' => $associations,
'config' => $config
]
];
}
}
116 changes: 63 additions & 53 deletions src/ORM/EagerLoader.php
Expand Up @@ -195,7 +195,7 @@ public function normalized(Table $repository)
$contain = (array)$this->_containments;
break;
}
$contain[$alias] =& $this->_normalizeContain(
$contain[$alias] = $this->_normalizeContain(
$repository,
$alias,
$options,
Expand Down Expand Up @@ -279,13 +279,13 @@ public function attachAssociations(Query $query, Table $repository, $includeFiel
return;
}

foreach ($this->attachableAssociations($repository) as $options) {
$config = $options['config'] + [
'aliasPath' => $options['aliasPath'],
'propertyPath' => $options['propertyPath'],
foreach ($this->attachableAssociations($repository) as $loadable) {
$config = $loadable->config() + [
'aliasPath' => $loadable->aliasPath(),
'propertyPath' => $loadable->propertyPath(),
'includeFields' => $includeFields
];
$options['instance']->attachTo($query, $config);
$loadable->instance()->attachTo($query, $config);
}
}

Expand Down Expand Up @@ -344,7 +344,7 @@ public function externalAssociations(Table $repository)
* @return array normalized associations
* @throws \InvalidArgumentException When containments refer to associations that do not exist.
*/
protected function &_normalizeContain(Table $parent, $alias, $options, $paths)
protected function _normalizeContain(Table $parent, $alias, $options, $paths)
{
$defaults = $this->_containOptions;
$instance = $parent->association($alias);
Expand All @@ -369,18 +369,22 @@ protected function &_normalizeContain(Table $parent, $alias, $options, $paths)
'propertyPath' => trim($paths['propertyPath'], '.')
];
$config['canBeJoined'] = $instance->canBeJoined($config['config']);
$eagerLoadable = new EagerLoadable($alias, $config);

if ($config['canBeJoined']) {
$this->_aliasList[$paths['root']][$alias][] =& $config;
$this->_aliasList[$paths['root']][$alias][] = $eagerLoadable;
} else {
$paths['root'] = $config['aliasPath'];
}

foreach ($extra as $t => $assoc) {
$config['associations'][$t] =& $this->_normalizeContain($table, $t, $assoc, $paths);
$eagerLoadable->addAssociation(
$t,
$this->_normalizeContain($table, $t, $assoc, $paths)
);
}

return $config;
return $eagerLoadable;
}

/**
Expand All @@ -394,14 +398,14 @@ protected function &_normalizeContain(Table $parent, $alias, $options, $paths)
*/
protected function _fixStrategies()
{
foreach ($this->_aliasList as &$aliases) {
foreach ($aliases as $alias => &$configs) {
foreach ($this->_aliasList as $aliases) {
foreach ($aliases as $alias => $configs) {
if (count($configs) < 2) {
continue;
}
foreach ($configs as &$config) {
if (strpos($config['aliasPath'], '.')) {
$this->_correctStrategy($config, $alias);
foreach ($configs as $loadable) {
if (strpos($loadable->aliasPath(), '.')) {
$this->_correctStrategy($loadable, $alias);
}
}
}
Expand All @@ -412,26 +416,23 @@ protected function _fixStrategies()
* Changes the association fetching strategy if required because of duplicate
* under the same direct associations chain
*
* This function modifies the $config variable
*
* @param array &$config The association config
* @param \Cake\ORM\EagerLoader $loadable The association config
* @param string $alias the name of the association to evaluate
* @return void|array
* @throws \RuntimeException if a duplicate association in the same chain is detected
* but is not possible to change the strategy due to conflicting settings
* @return void
*/
protected function _correctStrategy(&$config, $alias)
protected function _correctStrategy($loadable, $alias)
{
$currentStrategy = isset($config['config']['strategy']) ?
$config['config']['strategy'] :
$config = $loadable->config();
$currentStrategy = isset($config['strategy']) ?
$config['strategy'] :
'join';

if (!$config['canBeJoined'] || $currentStrategy !== 'join') {
return $config;
if (!$loadable->canBeJoined() || $currentStrategy !== 'join') {
return;
}

$config['canBeJoined'] = false;
$config['config']['strategy'] = $config['instance']::STRATEGY_SELECT;
$config = $config['strategy'] = Association::STRATEGY_SELECT;
$loadable->canBeJoined(false);
}

/**
Expand All @@ -447,15 +448,18 @@ protected function _resolveJoins($associations, $matching = [])
$result = [];
foreach ($matching as $table => $options) {
$result[$table] = $options;
$result += $this->_resolveJoins($options['associations'], []);
$result += $this->_resolveJoins($options->associations(), []);
}
foreach ($associations as $table => $options) {
$inMatching = isset($matching[$table]);
if (!$inMatching && $options['canBeJoined']) {
if (!$inMatching && $options->canBeJoined()) {
$result[$table] = $options;
$result += $this->_resolveJoins($options['associations'], $inMatching ? $mathching[$table] : []);
$result += $this->_resolveJoins(
$options->associations(),
$inMatching ? $mathching[$table] : []
);
} else {
$options['canBeJoined'] = false;
$options->canBeJoined(false);
$this->_loadExternal[] = $options;
}
}
Expand All @@ -481,21 +485,23 @@ public function loadExternal($query, $statement)
$driver = $query->connection()->driver();
list($collected, $statement) = $this->_collectKeys($external, $query, $statement);
foreach ($external as $meta) {
$contain = $meta['associations'];
$alias = $meta['instance']->source()->alias();
$contain = $meta->associations();
$instance = $meta->instance();
$config = $meta->config();
$alias = $instance->source()->alias();

$requiresKeys = $meta['instance']->requiresKeys($meta['config']);
$requiresKeys = $instance->requiresKeys($config);
if ($requiresKeys && empty($collected[$alias])) {
continue;
}

$keys = isset($collected[$alias]) ? $collected[$alias] : null;
$f = $meta['instance']->eagerLoader(
$meta['config'] + [
$f = $instance->eagerLoader(
$config + [
'query' => $query,
'contain' => $contain,
'keys' => $keys,
'nestKey' => $meta['aliasPath']
'nestKey' => $meta->aliasPath()
]
);
$statement = new CallbackStatement($statement, $driver, $f);
Expand Down Expand Up @@ -528,16 +534,20 @@ public function associationsMap($table)

$visitor = function ($level, $matching = false) use (&$visitor, &$map) {
foreach ($level as $assoc => $meta) {
$canBeJoined = $meta->canBeJoined();
$instance = $meta->instance();
$associations = $meta->associations();
$forMatching = $meta->forMatching();
$map[] = [
'alias' => $assoc,
'instance' => $meta['instance'],
'canBeJoined' => $meta['canBeJoined'],
'entityClass' => $meta['instance']->target()->entityClass(),
'nestKey' => $meta['canBeJoined'] ? $assoc : $meta['aliasPath'],
'matching' => isset($meta['matching']) ? $meta['matching'] : $matching
'instance' => $instance,
'canBeJoined' => $canBeJoined,
'entityClass' => $instance->target()->entityClass(),
'nestKey' => $canBeJoined ? $assoc : $meta->aliasPath(),
'matching' => $forMatching !== null ? $forMatching : $matching
];
if ($meta['canBeJoined'] && !empty($meta['associations'])) {
$visitor($meta['associations'], $matching);
if ($canBeJoined && $associations) {
$visitor($associations, $matching);
}
}
};
Expand All @@ -561,13 +571,12 @@ public function associationsMap($table)
*/
public function addToJoinsMap($alias, Association $assoc, $asMatching = false)
{
$this->_joinsMap[$alias] = [
$this->_joinsMap[$alias] = new EagerLoadable($alias, [
'aliasPath' => $alias,
'instance' => $assoc,
'canBeJoined' => true,
'matching' => $asMatching,
'associations' => []
];
'forMatching' => $asMatching,
]);
}

/**
Expand All @@ -583,13 +592,14 @@ protected function _collectKeys($external, $query, $statement)
{
$collectKeys = [];
foreach ($external as $meta) {
if (!$meta['instance']->requiresKeys($meta['config'])) {
$instance = $meta->instance();
if (!$instance->requiresKeys($meta->config())) {
continue;
}

$source = $meta['instance']->source();
$keys = $meta['instance']->type() === $meta['instance']::MANY_TO_ONE ?
(array)$meta['instance']->foreignKey() :
$source = $instance->source();
$keys = $instance->type() === Association::MANY_TO_ONE ?
(array)$instance->foreignKey() :
(array)$source->primaryKey();

$alias = $source->alias();
Expand Down

0 comments on commit 58903d8

Please sign in to comment.