|
|
@@ -0,0 +1,350 @@
|
|
|
+<?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\Test\TestCase\ORM;
|
|
|
+
|
|
|
+use Cake\Database\ConnectionManager;
|
|
|
+use Cake\Database\Expression\IdentifierExpression;
|
|
|
+use Cake\Database\Expression\QueryExpression;
|
|
|
+use Cake\ORM\EagerLoader;
|
|
|
+use Cake\ORM\Query;
|
|
|
+use Cake\ORM\Table;
|
|
|
+use Cake\ORM\TableRegistry;
|
|
|
+use Cake\TestSuite\TestCase;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Tests EagerLoader
|
|
|
+ *
|
|
|
+ */
|
|
|
+class EagerLoaderTest extends TestCase {
|
|
|
+
|
|
|
+/**
|
|
|
+ * setUp method
|
|
|
+ *
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ public function setUp() {
|
|
|
+ parent::setUp();
|
|
|
+ $this->connection = ConnectionManager::get('test');
|
|
|
+ $schema = [
|
|
|
+ 'id' => ['type' => 'integer'],
|
|
|
+ '_constraints' => [
|
|
|
+ 'primary' => ['type' => 'primary', 'columns' => ['id']]
|
|
|
+ ]
|
|
|
+ ];
|
|
|
+ $schema1 = [
|
|
|
+ 'id' => ['type' => 'integer'],
|
|
|
+ 'name' => ['type' => 'string'],
|
|
|
+ 'phone' => ['type' => 'string'],
|
|
|
+ '_constraints' => [
|
|
|
+ 'primary' => ['type' => 'primary', 'columns' => ['id']]
|
|
|
+ ]
|
|
|
+ ];
|
|
|
+ $schema2 = [
|
|
|
+ 'id' => ['type' => 'integer'],
|
|
|
+ 'total' => ['type' => 'string'],
|
|
|
+ 'placed' => ['type' => 'datetime'],
|
|
|
+ '_constraints' => [
|
|
|
+ 'primary' => ['type' => 'primary', 'columns' => ['id']]
|
|
|
+ ]
|
|
|
+ ];
|
|
|
+
|
|
|
+ $this->table = $table = TableRegistry::get('foo', ['schema' => $schema]);
|
|
|
+ $clients = TableRegistry::get('clients', ['schema' => $schema1]);
|
|
|
+ $orders = TableRegistry::get('orders', ['schema' => $schema2]);
|
|
|
+ $companies = TableRegistry::get('companies', ['schema' => $schema, 'table' => 'organizations']);
|
|
|
+ $orderTypes = TableRegistry::get('orderTypes', ['schema' => $schema]);
|
|
|
+ $stuff = TableRegistry::get('stuff', ['schema' => $schema, 'table' => 'things']);
|
|
|
+ $stuffTypes = TableRegistry::get('stuffTypes', ['schema' => $schema]);
|
|
|
+ $categories = TableRegistry::get('categories', ['schema' => $schema]);
|
|
|
+
|
|
|
+ $table->belongsTo('clients');
|
|
|
+ $clients->hasOne('orders');
|
|
|
+ $clients->belongsTo('companies');
|
|
|
+ $orders->belongsTo('orderTypes');
|
|
|
+ $orders->hasOne('stuff');
|
|
|
+ $stuff->belongsTo('stuffTypes');
|
|
|
+ $companies->belongsTo('categories');
|
|
|
+ }
|
|
|
+
|
|
|
+/**
|
|
|
+ * tearDown method
|
|
|
+ *
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ public function tearDown() {
|
|
|
+ parent::tearDown();
|
|
|
+ TableRegistry::clear();
|
|
|
+ }
|
|
|
+
|
|
|
+/**
|
|
|
+ * Tests that fully defined belongsTo and hasOne relationships are joined correctly
|
|
|
+ *
|
|
|
+ * @return void
|
|
|
+ **/
|
|
|
+ public function testContainToJoinsOneLevel() {
|
|
|
+ $contains = [
|
|
|
+ 'clients' => [
|
|
|
+ 'orders' => [
|
|
|
+ 'orderTypes',
|
|
|
+ 'stuff' => ['stuffTypes']
|
|
|
+ ],
|
|
|
+ 'companies' => [
|
|
|
+ 'foreignKey' => 'organization_id',
|
|
|
+ 'categories'
|
|
|
+ ]
|
|
|
+ ]
|
|
|
+ ];
|
|
|
+
|
|
|
+ $query = $this->getMock('\Cake\ORM\Query', ['join'], [$this->connection, $this->table]);
|
|
|
+
|
|
|
+ $query->expects($this->at(0))->method('join')
|
|
|
+ ->with(['clients' => [
|
|
|
+ 'table' => 'clients',
|
|
|
+ 'type' => 'LEFT',
|
|
|
+ 'conditions' => new QueryExpression([
|
|
|
+ ['clients.id' => new IdentifierExpression('foo.client_id')]
|
|
|
+ ])
|
|
|
+ ]])
|
|
|
+ ->will($this->returnValue($query));
|
|
|
+
|
|
|
+ $query->expects($this->at(1))->method('join')
|
|
|
+ ->with(['orders' => [
|
|
|
+ 'table' => 'orders',
|
|
|
+ 'type' => 'INNER',
|
|
|
+ 'conditions' => new QueryExpression([
|
|
|
+ ['clients.id' => new IdentifierExpression('orders.client_id')]
|
|
|
+ ])
|
|
|
+ ]])
|
|
|
+ ->will($this->returnValue($query));
|
|
|
+
|
|
|
+ $query->expects($this->at(2))->method('join')
|
|
|
+ ->with(['orderTypes' => [
|
|
|
+ 'table' => 'order_types',
|
|
|
+ 'type' => 'LEFT',
|
|
|
+ 'conditions' => new QueryExpression([
|
|
|
+ ['orderTypes.id' => new IdentifierExpression('orders.order_type_id')]
|
|
|
+ ])
|
|
|
+ ]])
|
|
|
+ ->will($this->returnValue($query));
|
|
|
+
|
|
|
+ $query->expects($this->at(3))->method('join')
|
|
|
+ ->with(['stuff' => [
|
|
|
+ 'table' => 'things',
|
|
|
+ 'type' => 'INNER',
|
|
|
+ 'conditions' => new QueryExpression([
|
|
|
+ ['orders.id' => new IdentifierExpression('stuff.order_id')]
|
|
|
+ ])
|
|
|
+ ]])
|
|
|
+ ->will($this->returnValue($query));
|
|
|
+
|
|
|
+ $query->expects($this->at(4))->method('join')
|
|
|
+ ->with(['stuffTypes' => [
|
|
|
+ 'table' => 'stuff_types',
|
|
|
+ 'type' => 'LEFT',
|
|
|
+ 'conditions' => new QueryExpression([
|
|
|
+ ['stuffTypes.id' => new IdentifierExpression('stuff.stuff_type_id')]
|
|
|
+ ])
|
|
|
+ ]])
|
|
|
+ ->will($this->returnValue($query));
|
|
|
+
|
|
|
+ $query->expects($this->at(5))->method('join')
|
|
|
+ ->with(['companies' => [
|
|
|
+ 'table' => 'organizations',
|
|
|
+ 'type' => 'LEFT',
|
|
|
+ 'conditions' => new QueryExpression([
|
|
|
+ ['companies.id' => new IdentifierExpression('clients.organization_id')]
|
|
|
+ ])
|
|
|
+ ]])
|
|
|
+ ->will($this->returnValue($query));
|
|
|
+
|
|
|
+ $query->expects($this->at(6))->method('join')
|
|
|
+ ->with(['categories' => [
|
|
|
+ 'table' => 'categories',
|
|
|
+ 'type' => 'LEFT',
|
|
|
+ 'conditions' => new QueryExpression([
|
|
|
+ ['categories.id' => new IdentifierExpression('companies.category_id')]
|
|
|
+ ])
|
|
|
+ ]])
|
|
|
+ ->will($this->returnValue($query));
|
|
|
+
|
|
|
+ $loader = new EagerLoader;
|
|
|
+ $loader->contain($contains);
|
|
|
+ $query->select('foo.id')->eagerLoader($loader)->sql();
|
|
|
+ }
|
|
|
+
|
|
|
+/**
|
|
|
+ * Tests setting containments using dot notation, additionally proves that options
|
|
|
+ * are not overwritten when combining dot notation and array notation
|
|
|
+ *
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ public function testContainDotNotation() {
|
|
|
+ $loader = new EagerLoader;
|
|
|
+ $loader->contain([
|
|
|
+ 'clients.orders.stuff',
|
|
|
+ 'clients.companies.categories' => ['conditions' => ['a >' => 1]]
|
|
|
+ ]);
|
|
|
+ $expected = [
|
|
|
+ 'clients' => [
|
|
|
+ 'orders' => [
|
|
|
+ 'stuff' => []
|
|
|
+ ],
|
|
|
+ 'companies' => [
|
|
|
+ 'categories' => [
|
|
|
+ 'conditions' => ['a >' => 1]
|
|
|
+ ]
|
|
|
+ ]
|
|
|
+ ]
|
|
|
+ ];
|
|
|
+ $this->assertEquals($expected, $loader->contain());
|
|
|
+ $loader->contain([
|
|
|
+ 'clients.orders' => ['fields' => ['a', 'b']],
|
|
|
+ 'clients' => ['sort' => ['a' => 'desc']],
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $expected['clients']['orders'] += ['fields' => ['a', 'b']];
|
|
|
+ $expected['clients'] += ['sort' => ['a' => 'desc']];
|
|
|
+ $this->assertEquals($expected, $loader->contain());
|
|
|
+ }
|
|
|
+
|
|
|
+/**
|
|
|
+ * Tests that it is possible to pass a function as the array value for contain
|
|
|
+ *
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ public function testContainClosure() {
|
|
|
+ $builder = function($query) {
|
|
|
+ };
|
|
|
+ $loader = new EagerLoader;
|
|
|
+ $loader->contain([
|
|
|
+ 'clients.orders.stuff' => ['fields' => ['a']],
|
|
|
+ 'clients' => $builder
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $expected = [
|
|
|
+ 'clients' => [
|
|
|
+ 'orders' => [
|
|
|
+ 'stuff' => ['fields' => ['a']]
|
|
|
+ ],
|
|
|
+ 'queryBuilder' => $builder
|
|
|
+ ]
|
|
|
+ ];
|
|
|
+ $this->assertEquals($expected, $loader->contain());
|
|
|
+
|
|
|
+ $loader = new EagerLoader;
|
|
|
+ $loader->contain([
|
|
|
+ 'clients.orders.stuff' => ['fields' => ['a']],
|
|
|
+ 'clients' => ['queryBuilder' => $builder]
|
|
|
+ ]);
|
|
|
+ $this->assertEquals($expected, $loader->contain());
|
|
|
+ }
|
|
|
+
|
|
|
+/**
|
|
|
+ * Test that fields for contained models are aliased and added to the select clause
|
|
|
+ *
|
|
|
+ * @return void
|
|
|
+ **/
|
|
|
+ public function testContainToFieldsPredefined() {
|
|
|
+ $contains = [
|
|
|
+ 'clients' => [
|
|
|
+ 'fields' => ['name', 'company_id', 'clients.telephone'],
|
|
|
+ 'orders' => [
|
|
|
+ 'fields' => ['total', 'placed']
|
|
|
+ ]
|
|
|
+ ]
|
|
|
+ ];
|
|
|
+
|
|
|
+ $table = TableRegistry::get('foo');
|
|
|
+ $query = new Query($this->connection, $table);
|
|
|
+ $loader = new EagerLoader;
|
|
|
+ $loader->contain($contains);
|
|
|
+ $query->select('foo.id')->sql();
|
|
|
+ $loader->attachAssociations($query, true);
|
|
|
+
|
|
|
+ $select = $query->clause('select');
|
|
|
+ $expected = [
|
|
|
+ 'foo__id' => 'foo.id', 'clients__name' => 'clients.name',
|
|
|
+ 'clients__company_id' => 'clients.company_id',
|
|
|
+ 'clients__telephone' => 'clients.telephone',
|
|
|
+ 'orders__total' => 'orders.total', 'orders__placed' => 'orders.placed'
|
|
|
+ ];
|
|
|
+ $expected = $this->_quoteArray($expected);
|
|
|
+ $this->assertEquals($expected, $select);
|
|
|
+ }
|
|
|
+
|
|
|
+/**
|
|
|
+ * Tests that default fields for associations are added to the select clause when
|
|
|
+ * none is specified
|
|
|
+ *
|
|
|
+ * @return void
|
|
|
+ **/
|
|
|
+ public function testContainToFieldsDefault() {
|
|
|
+ $contains = ['clients' => ['orders']];
|
|
|
+
|
|
|
+ $query = new Query($this->connection, $this->table);
|
|
|
+ $query->select()->contain($contains)->sql();
|
|
|
+ $select = $query->clause('select');
|
|
|
+ $expected = [
|
|
|
+ 'foo__id' => 'foo.id', 'clients__name' => 'clients.name',
|
|
|
+ 'clients__id' => 'clients.id', 'clients__phone' => 'clients.phone',
|
|
|
+ 'orders__id' => 'orders.id', 'orders__total' => 'orders.total',
|
|
|
+ 'orders__placed' => 'orders.placed'
|
|
|
+ ];
|
|
|
+ $expected = $this->_quoteArray($expected);
|
|
|
+ $this->assertEquals($expected, $select);
|
|
|
+
|
|
|
+ $contains['clients']['fields'] = ['name'];
|
|
|
+ $query = new Query($this->connection, $this->table);
|
|
|
+ $query->select('foo.id')->contain($contains)->sql();
|
|
|
+ $select = $query->clause('select');
|
|
|
+ $expected = ['foo__id' => 'foo.id', 'clients__name' => 'clients.name'];
|
|
|
+ $expected = $this->_quoteArray($expected);
|
|
|
+ $this->assertEquals($expected, $select);
|
|
|
+
|
|
|
+ $contains['clients']['fields'] = [];
|
|
|
+ $contains['clients']['orders']['fields'] = false;
|
|
|
+ $query = new Query($this->connection, $this->table);
|
|
|
+ $query->select()->contain($contains)->sql();
|
|
|
+ $select = $query->clause('select');
|
|
|
+ $expected = [
|
|
|
+ 'foo__id' => 'foo.id',
|
|
|
+ 'clients__id' => 'clients.id',
|
|
|
+ 'clients__name' => 'clients.name',
|
|
|
+ 'clients__phone' => 'clients.phone',
|
|
|
+ ];
|
|
|
+ $expected = $this->_quoteArray($expected);
|
|
|
+ $this->assertEquals($expected, $select);
|
|
|
+ }
|
|
|
+
|
|
|
+/**
|
|
|
+ * Helper function sued to quoted both keys and values in an array in case
|
|
|
+ * the test suite is running with auto quoting enabled
|
|
|
+ *
|
|
|
+ * @param array $elements
|
|
|
+ * @return array
|
|
|
+ */
|
|
|
+ protected function _quoteArray($elements) {
|
|
|
+ if ($this->connection->driver()->autoQuoting()) {
|
|
|
+ $quoter = function($e) {
|
|
|
+ return $this->connection->driver()->quoteIdentifier($e);
|
|
|
+ };
|
|
|
+ return array_combine(
|
|
|
+ array_map($quoter, array_keys($elements)),
|
|
|
+ array_map($quoter, array_values($elements))
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return $elements;
|
|
|
+ }
|
|
|
+}
|