Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion src/ORM/AutoHydratorRecursive.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ class AutoHydratorRecursive
*/
protected array $entitiesMap = [];

/**
* The map of aliases and corresponding Table objects.
*
* @var array<string,\Cake\ORM\Table|null>
*/
protected array $aliasMap = [];

/**
* @var \Cake\Datasource\EntityInterface[]
*/
Expand All @@ -56,11 +63,13 @@ class AutoHydratorRecursive
/**
* @param \Cake\ORM\Table $rootTable
* @param mixed[] $mappingStrategy Mapping strategy.
* @param array<string,\Cake\ORM\Table> $aliasMap Aliases and corresponding Table objects.
*/
public function __construct(Table $rootTable, array $mappingStrategy)
public function __construct(Table $rootTable, array $mappingStrategy, array $aliasMap)
{
$this->rootTable = $rootTable;
$this->mappingStrategy = $mappingStrategy;
$this->aliasMap = $aliasMap;
}

/**
Expand Down Expand Up @@ -215,6 +224,17 @@ protected function constructEntity(
}
}
}
if (isset($this->aliasMap[$alias])) {
/** @var \Cake\ORM\Table $Table */
$Table = $this->aliasMap[$alias];
$options = [
'validate' => false,
];
$entity = $Table->marshaller()->one($fields, $options);
$entity->clean();
$entity->setNew(false);
return $entity;
}
$options = [
'markClean' => true,
'markNew' => false,
Expand Down
28 changes: 28 additions & 0 deletions src/ORM/MappingStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Cake\ORM\Locator\LocatorAwareTrait;
use Cake\Utility\Hash;
use Cake\Utility\Inflector;
use RuntimeException;

class MappingStrategy
{
Expand All @@ -35,6 +36,13 @@ class MappingStrategy
*/
protected array $aliasList;

/**
* The map of aliases and corresponding Table objects.
*
* @var array<string,\Cake\ORM\Table|null>
*/
protected array $aliasMap = [];

/**
* A list of aliases to be mapped.
*
Expand Down Expand Up @@ -63,12 +71,14 @@ public function __construct(Table $rootTable, array $aliases)
}
$this->aliasList = $aliases;
$this->unknownAliases = array_combine($aliases, $aliases);
$this->aliasMap = array_fill_keys($aliases, null);
$rootAlias = $rootTable->getAlias();
if (!isset($this->unknownAliases[$rootAlias])) {
$message = "The query must select at least one column from the root table.";
$message .= " The column alias must use {$rootAlias}__{column_name} format";
throw new UnknownAliasException($message);
}
$this->aliasMap[$rootAlias] = $rootTable;
unset($this->unknownAliases[$rootAlias]);
}

Expand Down Expand Up @@ -120,6 +130,7 @@ private function scanRootLevel(Table $table): array
if (!isset($this->unknownAliases[$alias])) {
continue;
}
$this->aliasMap[$alias] = $target;
unset($this->unknownAliases[$alias]);
$firstLevelAssoc = [
'className' => $target->getEntityClass(),
Expand All @@ -137,6 +148,7 @@ private function scanRootLevel(Table $table): array
'primaryKey' => $assoc->junction()->getPrimaryKey(),
'propertyName' => Inflector::underscore(Inflector::singularize($through)),
];
$this->aliasMap[$through] = $assoc->junction();
unset($this->unknownAliases[$through]);
}
}
Expand Down Expand Up @@ -179,6 +191,7 @@ private function scanTableRecursive(string $alias): array
if (!isset($this->unknownAliases[$childAlias])) {
continue;
}
$this->aliasMap[$childAlias] = $target;
unset($this->unknownAliases[$childAlias]);
$result[$type][$childAlias]['className'] = $target->getEntityClass();
$result[$type][$childAlias]['primaryKey'] = $target->getPrimaryKey();
Expand All @@ -194,6 +207,7 @@ private function scanTableRecursive(string $alias): array
'propertyName' => Inflector::underscore(Inflector::singularize($through)),
];
if (isset($this->unknownAliases[$through])) {
$this->aliasMap[$through] = $assoc->junction();
unset($this->unknownAliases[$through]);
}
} else {
Expand Down Expand Up @@ -241,4 +255,18 @@ private function unknownAliasesToString(): string
{
return implode("', '", array_keys($this->unknownAliases));
}

/**
* Gets aliases map.
*
* @return array<string,\Cake\ORM\Table> Keys are alias names.
*/
public function getAliasMap(): array
{
$aliasWithoutTable = array_search(null, $this->aliasMap, true);
if (in_array(null, $this->aliasMap, true) || $aliasWithoutTable !== false) {
throw new RuntimeException("Failed to locate Table object for alias '$aliasWithoutTable'");
}
return $this->aliasMap;
}
}
4 changes: 3 additions & 1 deletion src/ORM/StatementQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@ public function all(): array
if (!$rows) {
return [];
}
$aliasMap = [];
if ($this->mapStrategy === null) {
$aliases = $this->extractAliases($rows);
$strategy = new MappingStrategy($this->rootTable, $aliases);
$this->mapStrategy = $strategy->build()->toArray();
$aliasMap = $strategy->getAliasMap();
}
$hydrator = new AutoHydratorRecursive($this->rootTable, $this->mapStrategy);
$hydrator = new AutoHydratorRecursive($this->rootTable, $this->mapStrategy, $aliasMap);
return $hydrator->hydrateMany($rows);
}

Expand Down
1 change: 1 addition & 0 deletions tests/TestApp/Model/Table/CommentsTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ public function initialize(array $config): void
parent::initialize($config);
$this->belongsTo('Articles', ['className' => ArticlesTable::class]);
$this->belongsTo('Users', ['className' => UsersTable::class]);
$this->addBehavior('Timestamp');
}
}
29 changes: 28 additions & 1 deletion tests/TestCase/ORM/NativeQueryMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Bancer\NativeQueryMapperTest\TestCase;

use PHPUnit\Framework\TestCase;
use Bancer\NativeQueryMapper\ORM\MissingColumnException;
use Bancer\NativeQueryMapper\ORM\UnknownAliasException;
use Bancer\NativeQueryMapperTest\TestApp\Model\Entity\Article;
Expand All @@ -18,6 +17,8 @@
use Bancer\NativeQueryMapperTest\TestApp\Model\Table\CountriesTable;
use Bancer\NativeQueryMapperTest\TestApp\Model\Table\UsersTable;
use Cake\ORM\Locator\LocatorAwareTrait;
use DateTimeImmutable;
use PHPUnit\Framework\TestCase;

class NativeQueryMapperTest extends TestCase
{
Expand Down Expand Up @@ -1224,4 +1225,30 @@ public function testDeepAssociationsWithBelongsToManyMinimalSQL(): void
$this->assertEqualsEntities($cakeEntities, $actual);
static::assertEquals($cakeEntities, $actual);*/
}

public function testDatetimeFields(): void
{
/** @var \Bancer\NativeQueryMapperTest\TestApp\Model\Table\CommentsTable $CommentsTable */
$CommentsTable = $this->fetchTable(CommentsTable::class);
$stmt = $CommentsTable->prepareSQL("
SELECT
id AS Comments__id,
content AS Comments__content,
created AS Comments__created
FROM comments
");
$actual = $CommentsTable->fromNativeQuery($stmt)->all();
static::assertCount(5, $actual);
static::assertInstanceOf(Comment::class, $actual[0]);
$expected = [
'id' => 1,
'content' => 'Comment 1',
'created' => new DateTimeImmutable('2025-10-23 14:00:00'),
];
$cakeEntities = $CommentsTable->find()
->select(['Comments.id', 'Comments.content', 'Comments.created'])
->toArray();
static::assertEquals($expected, $actual[0]->toArray());
static::assertEquals($cakeEntities, $actual);
}
}
13 changes: 7 additions & 6 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
article_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
content TEXT,
created DATETIME NULL,
FOREIGN KEY (article_id) REFERENCES articles(id),
FOREIGN KEY (user_id) REFERENCES users(id)
);
Expand Down Expand Up @@ -123,13 +124,13 @@
");

$connection->execute("
INSERT INTO comments (id, article_id, user_id, content)
INSERT INTO comments (id, article_id, user_id, content, created)
VALUES
(1,1,2,'Comment 1'),
(2,1,3,'Comment 2'),
(3,2,1,'Comment 3'),
(4,3,4,'Comment 4'),
(5,5,5,'Comment 5');
(1,1,2,'Comment 1','2025-10-23 14:00:00'),
(2,1,3,'Comment 2','2025-10-24 15:00:00'),
(3,2,1,'Comment 3','2025-10-25 16:00:00'),
(4,3,4,'Comment 4','2025-10-26 17:00:00'),
(5,5,5,'Comment 5','2025-10-27 18:00:00');
");

$connection->execute("
Expand Down