Browse Source

Add ORM\Query\QueryFactory.

ADmad 3 years ago
parent
commit
960dc61ed9

+ 0 - 5
phpstan-baseline.neon

@@ -211,11 +211,6 @@ parameters:
 			path: src/ORM/Query.php
 
 		-
-			message: "#^Unsafe usage of new static\\(\\)\\.$#"
-			count: 1
-			path: src/ORM/Query.php
-
-		-
 			message: "#^Parameter \\#1 \\$rules of method Cake\\\\ORM\\\\Table\\:\\:buildRules\\(\\) expects Cake\\\\ORM\\\\RulesChecker, Cake\\\\Datasource\\\\RulesChecker given\\.$#"
 			count: 1
 			path: src/ORM/Table.php

+ 12 - 14
src/ORM/Query.php

@@ -1683,6 +1683,18 @@ class Query extends DbSelectQuery implements JsonSerializable, QueryInterface
     }
 
     /**
+     * Disable auto adding table's alias to the fields of SELECT clause.
+     *
+     * @return $this
+     */
+    public function disableAutoAliasing()
+    {
+        $this->aliasingEnabled = false;
+
+        return $this;
+    }
+
+    /**
      * Marks a query as dirty, removing any preprocessed information
      * from in memory caching such as previous results
      *
@@ -1696,20 +1708,6 @@ class Query extends DbSelectQuery implements JsonSerializable, QueryInterface
     }
 
     /**
-     * Returns a new Query that has automatic field aliasing disabled.
-     *
-     * @param \Cake\ORM\Table $table The table this query is starting on
-     * @return static
-     */
-    public static function subquery(Table $table): static
-    {
-        $query = new static($table->getConnection(), $table);
-        $query->aliasingEnabled = false;
-
-        return $query;
-    }
-
-    /**
      * @inheritDoc
      */
     public function __debugInfo(): array

+ 79 - 0
src/ORM/Query/QueryFactory.php

@@ -0,0 +1,79 @@
+<?php
+declare(strict_types=1);
+
+/**
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://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. (https://cakefoundation.org)
+ * @link          https://cakephp.org CakePHP(tm) Project
+ * @since         5.0.0
+ * @license       https://opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\ORM\Query;
+
+use Cake\Database\Connection;
+use Cake\ORM\Query;
+use Cake\ORM\Table;
+
+/**
+ * Factory class for generating instances of Select, Insert, Update, Delete queries.
+ */
+class QueryFactory
+{
+    /**
+     * Constructor
+     *
+     * @param \Cake\Database\Connection $connection Connection instance.
+     * @param \Cake\ORM\Table $table The table the query instanced created will be starting on.
+     */
+    public function __construct(
+        protected Connection $connection,
+        protected Table $table
+    ) {
+    }
+
+    /**
+     * Create a new Query instance.
+     *
+     * @return \Cake\ORM\Query
+     */
+    public function select(): Query
+    {
+        return new Query($this->connection, $this->table);
+    }
+
+    /**
+     * Create a new InsertQuery instance.
+     *
+     * @return \Cake\ORM\Query\InsertQuery
+     */
+    public function insert(): InsertQuery
+    {
+        return new InsertQuery($this->connection, $this->table);
+    }
+
+    /**
+     * Create a new UpdateQuery instance.
+     *
+     * @return \Cake\ORM\Query\UpdateQuery
+     */
+    public function update(): UpdateQuery
+    {
+        return new UpdateQuery($this->connection, $this->table);
+    }
+
+    /**
+     * Create a new DeleteQuery instance.
+     *
+     * @return \Cake\ORM\Query\DeleteQuery
+     */
+    public function delete(): DeleteQuery
+    {
+        return new DeleteQuery($this->connection, $this->table);
+    }
+}

+ 16 - 8
src/ORM/Table.php

@@ -43,6 +43,7 @@ use Cake\ORM\Exception\PersistenceFailedException;
 use Cake\ORM\Exception\RolledbackTransactionException;
 use Cake\ORM\Query\DeleteQuery;
 use Cake\ORM\Query\InsertQuery;
+use Cake\ORM\Query\QueryFactory;
 use Cake\ORM\Query\UpdateQuery;
 use Cake\ORM\Rule\IsUnique;
 use Cake\Utility\Inflector;
@@ -259,6 +260,8 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
      */
     protected ?string $_registryAlias = null;
 
+    protected QueryFactory $queryFactory;
+
     /**
      * Initializes a new instance
      *
@@ -278,7 +281,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
      *   validation set, or an associative array, where key is the name of the
      *   validation set and value the Validator instance.
      *
-     * @param array<string, mixed> $config List of options for this table
+     * @param array<string, mixed> $config List of options for this table.
      */
     public function __construct(array $config = [])
     {
@@ -294,6 +297,9 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
         if (!empty($config['connection'])) {
             $this->setConnection($config['connection']);
         }
+        if (!empty($config['queryFactory'])) {
+            $this->queryFactory = $config['queryFactory'];
+        }
         if (!empty($config['schema'])) {
             $this->setSchema($config['schema']);
         }
@@ -323,6 +329,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
         $this->_behaviors = $behaviors ?: new BehaviorRegistry();
         $this->_behaviors->setTable($this);
         $this->_associations = $associations ?: new AssociationCollection();
+        $this->queryFactory ??= new QueryFactory($this->getConnection(), $this);
 
         $this->initialize($config);
         $this->_eventManager->on($this);
@@ -1689,7 +1696,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
      */
     public function query(): Query
     {
-        return new Query($this->getConnection(), $this);
+        return $this->queryFactory->select();
     }
 
     /**
@@ -1699,7 +1706,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
      */
     public function insertQuery(): InsertQuery
     {
-        return new InsertQuery($this->getConnection(), $this);
+        return $this->queryFactory->insert();
     }
 
     /**
@@ -1709,7 +1716,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
      */
     public function updateQuery(): UpdateQuery
     {
-        return new UpdateQuery($this->getConnection(), $this);
+        return $this->queryFactory->update();
     }
 
     /**
@@ -1719,18 +1726,19 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
      */
     public function deleteQuery(): DeleteQuery
     {
-        return new DeleteQuery($this->getConnection(), $this);
+        return $this->queryFactory->delete();
     }
 
     /**
-     * Creates a new Query::subquery() instance for a table.
+     * Creates a new Query instance with field auto aliasing disabled.
+     *
+     * This is useful for subqueries.
      *
      * @return \Cake\ORM\Query
-     * @see \Cake\ORM\Query::subquery()
      */
     public function subquery(): Query
     {
-        return Query::subquery($this);
+        return $this->queryFactory->select()->disableAutoAliasing();
     }
 
     /**

+ 0 - 69
tests/TestCase/ORM/QueryTest.php

@@ -3829,75 +3829,6 @@ class QueryTest extends TestCase
     }
 
     /**
-     * Tests subquery() copies connection by default.
-     */
-    public function testSubqueryConnection(): void
-    {
-        $subquery = Query::subquery($this->table);
-        $this->assertEquals($this->table->getConnection(), $subquery->getConnection());
-    }
-
-    /**
-     * Tests subquery() disables aliasing.
-     */
-    public function testSubqueryAliasing(): void
-    {
-        $articles = $this->getTableLocator()->get('Articles');
-        $subquery = Query::subquery($articles);
-
-        $subquery->select('Articles.field1');
-        $this->assertRegExpSql(
-            'SELECT <Articles>.<field1> FROM <articles> <Articles>',
-            $subquery->sql(),
-            !$this->connection->getDriver()->isAutoQuotingEnabled()
-        );
-
-        $subquery->select($articles, true);
-        $this->assertEqualsSql('SELECT id, author_id, title, body, published FROM articles Articles', $subquery->sql());
-
-        $subquery->selectAllExcept($articles, ['author_id'], true);
-        $this->assertEqualsSql('SELECT id, title, body, published FROM articles Articles', $subquery->sql());
-    }
-
-    /**
-     * Tests subquery() in where clause.
-     */
-    public function testSubqueryWhereClause(): void
-    {
-        $subquery = Query::subquery($this->getTableLocator()->get('Authors'))
-            ->select(['Authors.id'])
-            ->where(['Authors.name' => 'mariano']);
-
-        $query = $this->getTableLocator()->get('Articles')->find()
-            ->where(['Articles.author_id IN' => $subquery])
-            ->order(['Articles.id' => 'ASC']);
-
-        $results = $query->all()->toList();
-        $this->assertCount(2, $results);
-        $this->assertEquals([1, 3], array_column($results, 'id'));
-    }
-
-    /**
-     * Tests subquery() in join clause.
-     */
-    public function testSubqueryJoinClause(): void
-    {
-        $subquery = Query::subquery($this->getTableLocator()->get('Articles'))
-            ->select(['author_id']);
-
-        $query = $this->getTableLocator()->get('Authors')->find();
-        $query
-            ->select(['Authors.id', 'total_articles' => $query->func()->count('articles.author_id')])
-            ->leftJoin(['articles' => $subquery], ['articles.author_id' => new IdentifierExpression('Authors.id')])
-            ->group(['Authors.id'])
-            ->order(['Authors.id' => 'ASC']);
-
-        $results = $query->all()->toList();
-        $this->assertEquals(1, $results[0]->id);
-        $this->assertEquals(2, $results[0]->total_articles);
-    }
-
-    /**
      * Tests that queries that fetch associated data in separate queries do properly
      * inherit the hydration and results casting mode of the parent query.
      */

+ 62 - 7
tests/TestCase/ORM/TableTest.php

@@ -22,6 +22,7 @@ use BadMethodCallException;
 use Cake\Collection\Collection;
 use Cake\Database\Driver\Sqlserver;
 use Cake\Database\Exception\DatabaseException;
+use Cake\Database\Expression\IdentifierExpression;
 use Cake\Database\Expression\QueryExpression;
 use Cake\Database\Schema\TableSchema;
 use Cake\Database\StatementInterface;
@@ -221,6 +222,66 @@ class TableTest extends TestCase
     }
 
     /**
+     * Tests subquery() disables aliasing.
+     */
+    public function testSubqueryAliasing(): void
+    {
+        $articles = $this->getTableLocator()->get('Articles');
+        $subquery = $articles->subquery();
+
+        $subquery->select('Articles.field1');
+        $this->assertRegExpSql(
+            'SELECT <Articles>.<field1> FROM <articles> <Articles>',
+            $subquery->sql(),
+            !$this->connection->getDriver()->isAutoQuotingEnabled()
+        );
+
+        $subquery->select($articles, true);
+        $this->assertEqualsSql('SELECT id, author_id, title, body, published FROM articles Articles', $subquery->sql());
+
+        $subquery->selectAllExcept($articles, ['author_id'], true);
+        $this->assertEqualsSql('SELECT id, title, body, published FROM articles Articles', $subquery->sql());
+    }
+
+    /**
+     * Tests subquery() in where clause.
+     */
+    public function testSubqueryWhereClause(): void
+    {
+        $subquery = $this->getTableLocator()->get('Authors')->subquery()
+            ->select(['Authors.id'])
+            ->where(['Authors.name' => 'mariano']);
+
+        $query = $this->getTableLocator()->get('Articles')->find()
+            ->where(['Articles.author_id IN' => $subquery])
+            ->order(['Articles.id' => 'ASC']);
+
+        $results = $query->all()->toList();
+        $this->assertCount(2, $results);
+        $this->assertEquals([1, 3], array_column($results, 'id'));
+    }
+
+    /**
+     * Tests subquery() in join clause.
+     */
+    public function testSubqueryJoinClause(): void
+    {
+        $subquery = $this->getTableLocator()->get('Articles')->subquery()
+            ->select(['author_id']);
+
+        $query = $this->getTableLocator()->get('Authors')->find();
+        $query
+            ->select(['Authors.id', 'total_articles' => $query->func()->count('articles.author_id')])
+            ->leftJoin(['articles' => $subquery], ['articles.author_id' => new IdentifierExpression('Authors.id')])
+            ->group(['Authors.id'])
+            ->order(['Authors.id' => 'ASC']);
+
+        $results = $query->all()->toList();
+        $this->assertEquals(1, $results[0]->id);
+        $this->assertEquals(2, $results[0]->total_articles);
+    }
+
+    /**
      * Tests the table method
      */
     public function testTableMethod(): void
@@ -2352,13 +2413,7 @@ class TableTest extends TestCase
             ->setConstructorArgs([['driver' => $this->connection->getDriver()] + $config])
             ->getMock();
 
-        /** @var \Cake\ORM\Table|\PHPUnit\Framework\MockObject\MockObject $table */
-        $table = $this->getMockBuilder(Table::class)
-            ->onlyMethods(['getConnection'])
-            ->setConstructorArgs([['table' => 'users']])
-            ->getMock();
-        $table->expects($this->any())->method('getConnection')
-            ->will($this->returnValue($connection));
+        $table = new Table(['table' => 'users', 'connection' => $connection]);
 
         $connection->expects($this->once())->method('begin');
         $connection->expects($this->once())->method('commit');