Permalink
Browse files

Starting to refactor all the contain logic into a separate object, it is

big enough to be its own class and was strating to get hard to follow
  • Loading branch information...
1 parent a693097 commit 621fe36b52768656e3435ce4e0e8577ba864f671 @lorenzo lorenzo committed Jan 29, 2014
Showing with 319 additions and 278 deletions.
  1. +311 −0 src/ORM/EagerLoader.php
  2. +8 −278 src/ORM/Query.php
View
311 src/ORM/EagerLoader.php
@@ -0,0 +1,311 @@
+<?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 CakePHP(tm) v 3.0.0
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+namespace Cake\ORM;
+
+use Closure;
+
+/**
+ *
+ */
+class EagerLoader {
+
+/**
+ * Nested array describing the association to be fetched
+ * and the options to apply for each of them, if any
+ *
+ * @var \ArrayObject
+ */
+ protected $_containments;
+
+/**
+ * Contains a nested array with the compiled containments tree
+ * This is a normalized version of the user provided containments array.
+ *
+ * @var array
+ */
+ protected $_normalizedContainments;
+
+/**
+ * List of options accepted by associations in contain()
+ * index by key for faster access
+ *
+ * @var array
+ */
+ protected $_containOptions = [
+ 'associations' => 1,
+ 'foreignKey' => 1,
+ 'conditions' => 1,
+ 'fields' => 1,
+ 'sort' => 1,
+ 'matching' => 1,
+ 'queryBuilder' => 1
+ ];
+
+/**
+ * A list of associations that should be eagerly loaded
+ *
+ * @var array
+ */
+ protected $_loadEagerly = [];
+
+ public function contain($associations = null, $override = false) {
+ if ($this->_containments === null || $override) {
+ $this->_containments = new \ArrayObject;
+ }
+
+ if ($associations === null) {
+ return $this->_containments;
+ }
+
+ $associations = (array)$associations;
+ $current = current($associations);
+ if (is_array($current) && isset($current['instance'])) {
+ $this->_containments = $this->_normalizedContainments = $associations;
+ return;
+ }
+
+ $old = $this->_containments->getArrayCopy();
+ $associations = $this->_reformatContain($associations, $old);
+ $this->_containments->exchangeArray($associations);
+ $this->_normalizedContainments = null;
+ }
+
+ public function matching($assoc, callable $builder = null) {
+ $assocs = explode('.', $assoc);
+ $last = array_pop($assocs);
+ $containments = [];
+ $pointer =& $containments;
+
+ foreach ($assocs as $name) {
+ $pointer[$name] = ['matching' => true];
+ $pointer =& $pointer[$name];
+ }
+
+ $pointer[$last] = ['queryBuilder' => $builder, 'matching' => true];
+ return $this->contain($containments);
+ }
+
+/**
+ * Formats the containments array so that associations are always set as keys
+ * in the array. This function merges the original associations array with
+ * the new associations provided
+ *
+ * @param array $associations user provided containments array
+ * @param array $original The original containments array to merge
+ * with the new one
+ * @return array
+ */
+ protected function _reformatContain($associations, $original) {
+ $result = $original;
+
+ foreach ((array)$associations as $table => $options) {
+ $pointer =& $result;
+ if (is_int($table)) {
+ $table = $options;
+ $options = [];
+ }
+
+ if (isset($this->_containOptions[$table])) {
+ $pointer[$table] = $options;
+ continue;
+ }
+
+ if (strpos($table, '.')) {
+ $path = explode('.', $table);
+ $table = array_pop($path);
+ foreach ($path as $t) {
+ $pointer += [$t => []];
+ $pointer =& $pointer[$t];
+ }
+ }
+
+ if (is_array($options)) {
+ $options = $this->_reformatContain($options, []);
+ }
+
+ if ($options instanceof Closure) {
+ $options = ['queryBuilder' => $options];
+ }
+
+ $pointer += [$table => []];
+ $pointer[$table] = $options + $pointer[$table];
+ }
+
+ return $result;
+ }
+
+
+/**
+ * Returns the fully normalized array of associations that should be eagerly
+ * loaded. The normalized array will restructure the original one by sorting
+ * all associations under one key and special options under another.
+ *
+ * Additionally it will set an 'instance' key per association containing the
+ * association instance from the corresponding source table
+ *
+ * @return array
+ */
+ public function normalizedContainments() {
+ if ($this->_normalizedContainments !== null || empty($this->_containments)) {
+ return $this->_normalizedContainments;
+ }
+
+ $contain = [];
+ foreach ($this->_containments as $table => $options) {
+ if (!empty($options['instance'])) {
+ $contain = (array)$this->_containments;
+ break;
+ }
+ $contain[$table] = $this->_normalizeContain(
+ $this->_table,
+ $table,
+ $options
+ );
+ }
+
+ return $this->_normalizedContainments = $contain;
+ }
+
+/**
+ * Helper function used to add the required joins for associations defined using
+ * `contain()`
+ *
+ * @return void
+ */
+ protected function _addContainments() {
+ $this->_loadEagerly = [];
+ if (empty($this->_containments)) {
+ return;
+ }
+
+ $contain = $this->normalizedContainments();
+ foreach ($contain as $relation => $meta) {
+ if ($meta['instance'] && !$meta['canBeJoined']) {
+ $this->_loadEagerly[$relation] = $meta;
+ }
+ }
+
+ foreach ($this->_resolveJoins($this->_table, $contain) as $options) {
+ $table = $options['instance']->target();
+ $this->_addJoin($options['instance'], $options['config']);
+ foreach ($options['associations'] as $relation => $meta) {
+ if ($meta['instance'] && !$meta['canBeJoined']) {
+ $this->_loadEagerly[$relation] = $meta;
+ }
+ }
+ }
+ }
+
+/**
+ * Auxiliary function responsible for fully normalizing deep associations defined
+ * using `contain()`
+ *
+ * @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
+ * @return array normalized associations
+ * @throws \InvalidArgumentException When containments refer to associations that do not exist.
+ */
+ protected function _normalizeContain(Table $parent, $alias, $options) {
+ $defaults = $this->_containOptions;
+ $instance = $parent->association($alias);
+ if (!$instance) {
+ throw new \InvalidArgumentException(
+ sprintf('%s is not associated with %s', $parent->alias(), $alias)
+ );
+ }
+
+ $table = $instance->target();
+
+ $extra = array_diff_key($options, $defaults);
+ $config = [
+ 'associations' => [],
+ 'instance' => $instance,
+ 'config' => array_diff_key($options, $extra)
+ ];
+ $config['canBeJoined'] = $instance->canBeJoined($config['config']);
+
+ foreach ($extra as $t => $assoc) {
+ $config['associations'][$t] = $this->_normalizeContain($table, $t, $assoc);
+ }
+ return $config;
+ }
+
+/**
+ * Helper function used to compile a list of all associations that can be
+ * joined in this query.
+ *
+ * @param Table $source the owning side of the association
+ * @param array $associations list of associations for $source
+ * @return array
+ */
+ protected function _resolveJoins($source, $associations) {
+ $result = [];
+ foreach ($associations as $table => $options) {
+ $associated = $options['instance'];
+ if ($options['canBeJoined']) {
+ $result[$table] = $options;
+ $result += $this->_resolveJoins($associated->target(), $options['associations']);
+ }
+ }
+ return $result;
+ }
+
+/**
+ * Helper function used to return the keys from the query records that will be used
+ * to eagerly load associations.
+ *
+ *
+ * @param BufferedStatement $statement
+ * @return array
+ */
+ protected function _collectKeys($statement) {
+ $collectKeys = [];
+ foreach ($this->_loadEagerly as $meta) {
+ $source = $meta['instance']->source();
+ if ($meta['instance']->requiresKeys($meta['config'])) {
+ $alias = $source->alias();
+ $pkFields = [];
+ foreach ((array)$source->primaryKey() as $key) {
+ $pkFields[] = key($this->aliasField($key, $alias));
+ }
+ $collectKeys[$alias] = [$alias, $pkFields, count($pkFields) === 1];
+ }
+ }
+
+ $keys = [];
+ if (!empty($collectKeys)) {
+ while ($result = $statement->fetch('assoc')) {
+ foreach ($collectKeys as $parts) {
+ if ($parts[2]) {
+ $keys[$parts[0]][] = $result[$parts[1][0]];
+ continue;
+ }
+
+ $collected = [];
+ foreach ($parts[1] as $key) {
+ $collected[] = $result[$key];
+ }
+ $keys[$parts[0]][] = $collected;
+ }
+ }
+
+ $statement->rewind();
+ }
+
+ return $keys;
+ }
+
+}
View
286 src/ORM/Query.php
@@ -1,7 +1,5 @@
<?php
/**
- * PHP Version 5.4
- *
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
@@ -62,22 +60,6 @@ class Query extends DatabaseQuery {
protected $_table;
/**
- * Nested array describing the association to be fetched
- * and the options to apply for each of them, if any
- *
- * @var \ArrayObject
- */
- protected $_containments;
-
-/**
- * Contains a nested array with the compiled containments tree
- * This is a normalized version of the user provided containments array.
- *
- * @var array
- */
- protected $_normalizedContainments;
-
-/**
* Whether the user select any fields before being executed, this is used
* to determined if any fields should be automatically be selected.
*
@@ -86,29 +68,6 @@ class Query extends DatabaseQuery {
protected $_hasFields;
/**
- * A list of associations that should be eagerly loaded
- *
- * @var array
- */
- protected $_loadEagerly = [];
-
-/**
- * List of options accepted by associations in contain()
- * index by key for faster access
- *
- * @var array
- */
- protected $_containOptions = [
- 'associations' => 1,
- 'foreignKey' => 1,
- 'conditions' => 1,
- 'fields' => 1,
- 'sort' => 1,
- 'matching' => 1,
- 'queryBuilder' => 1
- ];
-
-/**
* A ResultSet.
*
* When set, query execution will be bypassed.
@@ -181,6 +140,7 @@ class Query extends DatabaseQuery {
public function __construct($connection, $table) {
$this->connection($connection);
$this->repository($table);
+ $this->_eagerLoader = new EagerLoader;
}
/**
@@ -322,27 +282,16 @@ public function addDefaultTypes(Table $table) {
* @return \ArrayObject|Query
*/
public function contain($associations = null, $override = false) {
- if ($this->_containments === null || $override) {
+ if ($associations !== null || $override) {
$this->_dirty();
- $this->_containments = new \ArrayObject;
+ return $this;
}
+ $result = $this->_eagerLoader->contain($associations, $override);
if ($associations === null) {
- return $this->_containments;
+ return $result;
}
- $associations = (array)$associations;
- $current = current($associations);
- if (is_array($current) && isset($current['instance'])) {
- $this->_containments = $this->_normalizedContainments = $associations;
- return $this;
- }
-
- $old = $this->_containments->getArrayCopy();
- $associations = $this->_reformatContain($associations, $old);
- $this->_containments->exchangeArray($associations);
- $this->_normalizedContainments = null;
- $this->_dirty();
return $this;
}
@@ -397,98 +346,9 @@ public function contain($associations = null, $override = false) {
* @return Query
*/
public function matching($assoc, callable $builder = null) {
- $assocs = explode('.', $assoc);
- $last = array_pop($assocs);
- $containments = [];
- $pointer =& $containments;
-
- foreach ($assocs as $name) {
- $pointer[$name] = ['matching' => true];
- $pointer =& $pointer[$name];
- }
-
- $pointer[$last] = ['queryBuilder' => $builder, 'matching' => true];
- return $this->contain($containments);
- }
-
-/**
- * Formats the containments array so that associations are always set as keys
- * in the array. This function merges the original associations array with
- * the new associations provided
- *
- * @param array $associations user provided containments array
- * @param array $original The original containments array to merge
- * with the new one
- * @return array
- */
- protected function _reformatContain($associations, $original) {
- $result = $original;
-
- foreach ((array)$associations as $table => $options) {
- $pointer =& $result;
- if (is_int($table)) {
- $table = $options;
- $options = [];
- }
-
- if (isset($this->_containOptions[$table])) {
- $pointer[$table] = $options;
- continue;
- }
-
- if (strpos($table, '.')) {
- $path = explode('.', $table);
- $table = array_pop($path);
- foreach ($path as $t) {
- $pointer += [$t => []];
- $pointer =& $pointer[$t];
- }
- }
-
- if (is_array($options)) {
- $options = $this->_reformatContain($options, []);
- }
-
- if ($options instanceof \Closure) {
- $options = ['queryBuilder' => $options];
- }
-
- $pointer += [$table => []];
- $pointer[$table] = $options + $pointer[$table];
- }
-
- return $result;
- }
-
-/**
- * Returns the fully normalized array of associations that should be eagerly
- * loaded. The normalized array will restructure the original one by sorting
- * all associations under one key and special options under another.
- *
- * Additionally it will set an 'instance' key per association containing the
- * association instance from the corresponding source table
- *
- * @return array
- */
- public function normalizedContainments() {
- if ($this->_normalizedContainments !== null || empty($this->_containments)) {
- return $this->_normalizedContainments;
- }
-
- $contain = [];
- foreach ($this->_containments as $table => $options) {
- if (!empty($options['instance'])) {
- $contain = (array)$this->_containments;
- break;
- }
- $contain[$table] = $this->_normalizeContain(
- $this->_table,
- $table,
- $options
- );
- }
-
- return $this->_normalizedContainments = $contain;
+ $this->_eagerLoader->matching($assoc, $builder);
+ $this->_dirty();
+ return $this;
}
/**
@@ -1053,91 +913,6 @@ protected function _transformQuery() {
}
/**
- * Helper function used to add the required joins for associations defined using
- * `contain()`
- *
- * @return void
- */
- protected function _addContainments() {
- $this->_loadEagerly = [];
- if (empty($this->_containments)) {
- return;
- }
-
- $contain = $this->normalizedContainments();
- foreach ($contain as $relation => $meta) {
- if ($meta['instance'] && !$meta['canBeJoined']) {
- $this->_loadEagerly[$relation] = $meta;
- }
- }
-
- foreach ($this->_resolveJoins($this->_table, $contain) as $options) {
- $table = $options['instance']->target();
- $this->_addJoin($options['instance'], $options['config']);
- foreach ($options['associations'] as $relation => $meta) {
- if ($meta['instance'] && !$meta['canBeJoined']) {
- $this->_loadEagerly[$relation] = $meta;
- }
- }
- }
- }
-
-/**
- * Auxiliary function responsible for fully normalizing deep associations defined
- * using `contain()`
- *
- * @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
- * @return array normalized associations
- * @throws \InvalidArgumentException When containments refer to associations that do not exist.
- */
- protected function _normalizeContain(Table $parent, $alias, $options) {
- $defaults = $this->_containOptions;
- $instance = $parent->association($alias);
- if (!$instance) {
- throw new \InvalidArgumentException(
- sprintf('%s is not associated with %s', $parent->alias(), $alias)
- );
- }
-
- $table = $instance->target();
-
- $extra = array_diff_key($options, $defaults);
- $config = [
- 'associations' => [],
- 'instance' => $instance,
- 'config' => array_diff_key($options, $extra)
- ];
- $config['canBeJoined'] = $instance->canBeJoined($config['config']);
-
- foreach ($extra as $t => $assoc) {
- $config['associations'][$t] = $this->_normalizeContain($table, $t, $assoc);
- }
- return $config;
- }
-
-/**
- * Helper function used to compile a list of all associations that can be
- * joined in this query.
- *
- * @param Table $source the owning side of the association
- * @param array $associations list of associations for $source
- * @return array
- */
- protected function _resolveJoins($source, $associations) {
- $result = [];
- foreach ($associations as $table => $options) {
- $associated = $options['instance'];
- if ($options['canBeJoined']) {
- $result[$table] = $options;
- $result += $this->_resolveJoins($associated->target(), $options['associations']);
- }
- }
- return $result;
- }
-
-/**
* Adds a join based on a particular association and some custom options
*
* @param Association $association
@@ -1172,51 +947,6 @@ protected function _eagerLoad($statement) {
}
/**
- * Helper function used to return the keys from the query records that will be used
- * to eagerly load associations.
- *
- *
- * @param BufferedStatement $statement
- * @return array
- */
- protected function _collectKeys($statement) {
- $collectKeys = [];
- foreach ($this->_loadEagerly as $meta) {
- $source = $meta['instance']->source();
- if ($meta['instance']->requiresKeys($meta['config'])) {
- $alias = $source->alias();
- $pkFields = [];
- foreach ((array)$source->primaryKey() as $key) {
- $pkFields[] = key($this->aliasField($key, $alias));
- }
- $collectKeys[$alias] = [$alias, $pkFields, count($pkFields) === 1];
- }
- }
-
- $keys = [];
- if (!empty($collectKeys)) {
- while ($result = $statement->fetch('assoc')) {
- foreach ($collectKeys as $parts) {
- if ($parts[2]) {
- $keys[$parts[0]][] = $result[$parts[1][0]];
- continue;
- }
-
- $collected = [];
- foreach ($parts[1] as $key) {
- $collected[] = $result[$key];
- }
- $keys[$parts[0]][] = $collected;
- }
- }
-
- $statement->rewind();
- }
-
- return $keys;
- }
-
-/**
* Inspects if there are any set fields for selecting, otherwise adds all
* the fields for the default table.
*

0 comments on commit 621fe36

Please sign in to comment.