diff --git a/src/ORM/NativeQueryResultMapper.php b/src/ORM/NativeQueryResultMapper.php index 7b5f89d..0b6bc11 100644 --- a/src/ORM/NativeQueryResultMapper.php +++ b/src/ORM/NativeQueryResultMapper.php @@ -5,6 +5,7 @@ namespace Bancer\NativeQueryMapper\ORM; use Cake\Database\StatementInterface; +use Cake\Datasource\EntityInterface; use Cake\ORM\Table; /** @@ -104,6 +105,23 @@ public function all(): array return $hydrator->hydrateMany($rows); } + /** + * Returns the first hydrated entity from the native query result. + * + * This executes the native SQL, hydrates entities using the mapping strategy, + * and returns only the first entity (or null if no rows were returned). + * + * @return \Cake\Datasource\EntityInterface|null + */ + public function first(): ?EntityInterface + { + $entities = $this->all(); + if ($entities === []) { + return null; + } + return $entities[0]; + } + /** * Extract column aliases used in the SQL result set. * diff --git a/tests/TestCase/ORM/NativeQueryMapperTest.php b/tests/TestCase/ORM/NativeQueryMapperTest.php index 6b72725..64523e2 100644 --- a/tests/TestCase/ORM/NativeQueryMapperTest.php +++ b/tests/TestCase/ORM/NativeQueryMapperTest.php @@ -307,8 +307,57 @@ public function testHasManyMinimalSQL(): void ], ]) ->toArray(); - $this->assertEqualsEntities($cakeEntities, $actual); - //static::assertEquals($cakeEntities, $actual); + static::assertEquals($cakeEntities, $actual); + } + + public function testHasManyFirst(): void + { + /** @var \Bancer\NativeQueryMapperTest\TestApp\Model\Table\ArticlesTable $ArticlesTable */ + $ArticlesTable = $this->fetchTable(ArticlesTable::class); + $stmt = $ArticlesTable->prepareNativeStatement(" + SELECT + a.id AS Articles__id, + title AS Articles__title, + c.id AS Comments__id, + article_id AS Comments__article_id, + content AS Comments__content + FROM articles AS a + LEFT JOIN comments AS c ON a.id=c.article_id + WHERE a.id=1 + "); + $actual = $ArticlesTable->mapNativeStatement($stmt)->first(); + static::assertInstanceOf(Article::class, $actual); + $actualComments = $actual->get('comments'); + static::assertIsArray($actualComments); + static::assertCount(2, $actualComments); + static::assertInstanceOf(Comment::class, $actualComments[0]); + $expected = [ + 'id' => 1, + 'title' => 'Article 1', + 'comments' => [ + [ + 'id' => 1, + 'article_id' => 1, + 'content' => 'Comment 1', + ], + [ + 'id' => 2, + 'article_id' => 1, + 'content' => 'Comment 2', + ], + ], + ]; + static::assertEquals($expected, $actual->toArray()); + $cakeEntity = $ArticlesTable->find() + ->select(['Articles.id', 'Articles.title']) + ->contain([ + 'Comments' => [ + 'fields' => ['Comments.id', 'Comments.article_id', 'Comments.content'], + ], + ]) + ->where(['Articles.id' => 1]) + ->first(); + static::assertEquals($cakeEntity, $actual); } public function testBelongsTo(): void