/** * Returns an array having as keys a dotted path of associations that participate * in this eager loader. The values of the array will contain the following keys * * - alias: The association alias * - instance: The association instance * - canBeJoined: Whether or not the association will be loaded using a JOIN * - entityClass: The entity that should be used for hydrating the results * - nestKey: A dotted path that can be used to correctly insert the data into the results. * - matching: Whether or not it is an association loaded through `matching()`. * * @param \Cake\ORM\Table $table The table containing the association that * will be normalized * @return array */ public function associationsMap($table) { $map = []; if (!$this->matching() && !$this->contain() && empty($this->_joinsMap)) { return $map; } $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' => $instance, 'canBeJoined' => $canBeJoined, 'entityClass' => $instance->target()->entityClass(), 'nestKey' => $canBeJoined ? $assoc : $meta->aliasPath(), 'matching' => $forMatching !== null ? $forMatching : $matching]; if ($canBeJoined && $associations) { $visitor($associations, $matching); } } }; $visitor($this->_matching->normalized($table), true); $visitor($this->normalized($table)); $visitor($this->_joinsMap); return $map; }
/** * Test clearing containments but not matching joins. * * @return void */ public function testClearContain() { $contains = ['clients' => ['orders' => ['orderTypes', 'stuff' => ['stuffTypes']], 'companies' => ['categories']]]; $loader = new EagerLoader(); $loader->contain($contains); $loader->matching('clients.addresses'); $this->assertNull($loader->clearContain()); $result = $loader->normalized($this->table); $this->assertEquals([], $result); $this->assertArrayHasKey('clients', $loader->matching()); }
/** * Tests that the path for gettings to a deep assocition is materialized in an * array key * * @return void */ public function testNormalizedPath() { $contains = ['clients' => ['orders' => ['orderTypes', 'stuff' => ['stuffTypes']], 'companies' => ['categories']]]; $query = $this->getMock('\\Cake\\ORM\\Query', ['join'], [$this->connection, $this->table]); $loader = new EagerLoader(); $loader->contain($contains); $normalized = $loader->normalized($this->table); $this->assertEquals('clients', $normalized['clients']->aliasPath()); $this->assertEquals('client', $normalized['clients']->propertyPath()); $assocs = $normalized['clients']->associations(); $this->assertEquals('clients.orders', $assocs['orders']->aliasPath()); $this->assertEquals('client.order', $assocs['orders']->propertyPath()); $assocs = $assocs['orders']->associations(); $this->assertEquals('clients.orders.orderTypes', $assocs['orderTypes']->aliasPath()); $this->assertEquals('client.order.order_type', $assocs['orderTypes']->propertyPath()); $this->assertEquals('clients.orders.stuff', $assocs['stuff']->aliasPath()); $this->assertEquals('client.order.stuff', $assocs['stuff']->propertyPath()); $assocs = $assocs['stuff']->associations(); $this->assertEquals('clients.orders.stuff.stuffTypes', $assocs['stuffTypes']->aliasPath()); $this->assertEquals('client.order.stuff.stuff_type', $assocs['stuffTypes']->propertyPath()); }
/** * Sets the list of associations that should be eagerly loaded along with this * query. The list of associated tables passed must have been previously set as * associations using the Table API. * * ### Example: * * ``` * // Bring articles' author information * $query->contain('Author'); * * // Also bring the category and tags associated to each article * $query->contain(['Category', 'Tag']); * ``` * * Associations can be arbitrarily nested using dot notation or nested arrays, * this allows this object to calculate joins or any additional queries that * must be executed to bring the required associated data. * * ### Example: * * ``` * // Eager load the product info, and for each product load other 2 associations * $query->contain(['Product' => ['Manufacturer', 'Distributor']); * * // Which is equivalent to calling * $query->contain(['Products.Manufactures', 'Products.Distributors']); * * // For an author query, load his region, state and country * $query->contain('Regions.States.Countries'); * ``` * * It is possible to control the conditions and fields selected for each of the * contained associations: * * ### Example: * * ``` * $query->contain(['Tags' => function ($q) { * return $q->where(['Tags.is_popular' => true]); * }]); * * $query->contain(['Products.Manufactures' => function ($q) { * return $q->select(['name'])->where(['Manufactures.active' => true]); * }]); * ``` * * Each association might define special options when eager loaded, the allowed * options that can be set per association are: * * - foreignKey: Used to set a different field to match both tables, if set to false * no join conditions will be generated automatically. `false` can only be used on * joinable associations and cannot be used with hasMany or belongsToMany associations. * - fields: An array with the fields that should be fetched from the association * - queryBuilder: Equivalent to passing a callable instead of an options array * * ### Example: * * ``` * // Set options for the hasMany articles that will be eagerly loaded for an author * $query->contain([ * 'Articles' => [ * 'fields' => ['title', 'author_id'] * ] * ]); * ``` * * When containing associations, it is important to include foreign key columns. * Failing to do so will trigger exceptions. * * ``` * // Use special join conditions for getting an Articles's belongsTo 'authors' * $query->contain([ * 'Authors' => [ * 'foreignKey' => false, * 'queryBuilder' => function ($q) { * return $q->where(...); // Add full filtering conditions * } * ] * ]); * ``` * * If called with no arguments, this function will return an array with * with the list of previously configured associations to be contained in the * result. * * If called with an empty first argument and $override is set to true, the * previous list will be emptied. * * @param array|string $associations list of table aliases to be queried * @param bool $override whether override previous list with the one passed * defaults to merging previous list with the new one. * @return array|$this */ public function contain($associations = null, $override = false) { if ($override) { $this->_eagerLoader->clearContain(); } $result = $this->eagerLoader()->contain($associations); if ($associations !== null || $override) { $this->_dirty(); } if ($associations === null) { return $result; } return $this; }