diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 8bf62dc78af..ce94433e4b6 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -559,4 +559,7 @@ parameters: message: "#^Access to an undefined property Cake\\\\Mailer\\\\Renderer\\:\\:\\$response\\.$#" count: 1 path: src/Mailer/Renderer.php - + - + message: "#^Unsafe usage of new static\\(\\)\\.$#" + count: 1 + path: src/ORM\Query.php diff --git a/src/ORM/Query.php b/src/ORM/Query.php index 978611b7ce0..21415d19537 100644 --- a/src/ORM/Query.php +++ b/src/ORM/Query.php @@ -130,6 +130,13 @@ class Query extends DatabaseQuery implements JsonSerializable, QueryInterface */ protected $_hydrate = true; + /** + * Whether aliases are generated for fields. + * + * @var bool + */ + protected $aliasingEnabled = true; + /** * A callable function that can be used to calculate the total amount of * records this query will match when not using `limit` @@ -225,7 +232,11 @@ public function select($fields = [], bool $overwrite = false) } if ($fields instanceof Table) { - $fields = $this->aliasFields($fields->getSchema()->columns(), $fields->getAlias()); + if ($this->aliasingEnabled) { + $fields = $this->aliasFields($fields->getSchema()->columns(), $fields->getAlias()); + } else { + $fields = $fields->getSchema()->columns(); + } } return parent::select($fields, $overwrite); @@ -255,9 +266,11 @@ public function selectAllExcept($table, array $excludedFields, bool $overwrite = } $fields = array_diff($table->getSchema()->columns(), $excludedFields); - $aliasedFields = $this->aliasFields($fields); + if ($this->aliasingEnabled) { + $fields = $this->aliasFields($fields); + } - return $this->select($aliasedFields, $overwrite); + return $this->select($fields, $overwrite); } /** @@ -1163,8 +1176,10 @@ protected function _addDefaultFields(): void $select = $this->clause('select'); } - $aliased = $this->aliasFields($select, $repository->getAlias()); - $this->select($aliased, true); + if ($this->aliasingEnabled) { + $select = $this->aliasFields($select, $repository->getAlias()); + } + $this->select($select, true); } /** @@ -1285,6 +1300,20 @@ public function insert(array $columns, array $types = []) return parent::insert($columns, $types); } + /** + * 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) + { + $query = new static($table->getConnection(), $table); + $query->aliasingEnabled = false; + + return $query; + } + /** * {@inheritDoc} * diff --git a/tests/TestCase/ORM/QueryTest.php b/tests/TestCase/ORM/QueryTest.php index 8b900319beb..cc53b68cb06 100644 --- a/tests/TestCase/ORM/QueryTest.php +++ b/tests/TestCase/ORM/QueryTest.php @@ -4001,4 +4001,54 @@ public function testWith(): void $this->assertEquals($expected, $query->toArray()); } + + /** + * Tests subquery() copies connection by default. + * + * @return void + */ + public function testSubqueryConnection() + { + $subquery = Query::subquery($this->table); + $this->assertEquals($this->table->getConnection(), $subquery->getConnection()); + } + + /** + * Tests subquery() disables aliasing. + * + * @return void + */ + public function testSubqueryAliasing() + { + $articles = $this->getTableLocator()->get('Articles'); + $subquery = Query::subquery($articles); + + $subquery->select('Articles.field1'); + $this->assertRegExpSql( + 'SELECT . FROM ', + $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()); + } + + public function testSubquerySelect() + { + $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')); + } }