Skip to content

Commit fa7685f

Browse files
committed
Refactoring retrieveManyToOneRelationshipsStorage signature
This will allow us in the future to build a oneToMany handler for smart eager loading.
1 parent a25a8f6 commit fa7685f

7 files changed

+167
-49
lines changed

src/AbstractTDBMObject.php

+14-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use TheCodingMachine\TDBM\QueryFactory\SmartEagerLoad\StorageNode;
2727
use TheCodingMachine\TDBM\Schema\ForeignKeys;
2828
use TheCodingMachine\TDBM\Utils\ManyToManyRelationshipPathDescriptor;
29+
use function array_combine;
2930

3031
/**
3132
* Instances of this class represent a "bean". Usually, a bean is mapped to a row of one table.
@@ -531,19 +532,29 @@ private function removeManyToOneRelationship(string $tableName, string $foreignK
531532
*
532533
* @param string $tableName
533534
* @param string $foreignKeyName
534-
* @param mixed[] $searchFilter
535-
* @param string $orderString The ORDER BY part of the query. All columns must be prefixed by the table name (in the form: table.column). WARNING : This parameter is not kept when there is an additionnal or removal object !
535+
* @param array<int, string> $localColumns
536+
* @param array<int, string> $foreignColumns
537+
* @param string $foreignTableName
538+
* @param string $orderString The ORDER BY part of the query. All columns must be prefixed by the table name (in the form: table.column). WARNING : This parameter is not kept when there is an additional or removal object !
536539
*
537540
* @return AlterableResultIterator
541+
* @throws TDBMException
538542
*/
539-
protected function retrieveManyToOneRelationshipsStorage(string $tableName, string $foreignKeyName, array $searchFilter, string $orderString = null) : AlterableResultIterator
543+
protected function retrieveManyToOneRelationshipsStorage(string $tableName, string $foreignKeyName, array $localColumns, array $foreignColumns, string $foreignTableName, string $orderString = null) : AlterableResultIterator
540544
{
541545
$key = $tableName.'___'.$foreignKeyName;
542546
$alterableResultIterator = $this->getManyToOneAlterableResultIterator($tableName, $foreignKeyName);
543547
if ($this->status === TDBMObjectStateEnum::STATE_DETACHED || $this->status === TDBMObjectStateEnum::STATE_NEW || (isset($this->manyToOneRelationships[$key]) && $this->manyToOneRelationships[$key]->getUnderlyingResultIterator() !== null)) {
544548
return $alterableResultIterator;
545549
}
546550

551+
$ids = [];
552+
foreach ($foreignColumns as $foreignColumn) {
553+
$ids[] = $this->get($foreignColumn, $foreignTableName);
554+
}
555+
556+
$searchFilter = array_combine($localColumns, $ids);
557+
547558
$unalteredResultIterator = $this->tdbmService->findObjects($tableName, $searchFilter, [], $orderString);
548559

549560
$alterableResultIterator->setResultIterator($unalteredResultIterator->getIterator());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
4+
namespace TheCodingMachine\TDBM\QueryFactory\SmartEagerLoad;
5+
6+
use Doctrine\DBAL\Connection;
7+
use TheCodingMachine\TDBM\TDBMException;
8+
9+
class OneToManyDataLoader
10+
{
11+
/**
12+
* @var Connection
13+
*/
14+
private $connection;
15+
/**
16+
* @var string
17+
*/
18+
private $sql;
19+
/**
20+
* @var string
21+
*/
22+
private $fkColumn;
23+
/**
24+
* @var array<string, array<int, array<string, mixed>>> Array of rows, indexed by foreign key.
25+
*/
26+
private $data;
27+
28+
public function __construct(Connection $connection, string $sql, string $fkColumn)
29+
{
30+
$this->connection = $connection;
31+
$this->sql = $sql;
32+
$this->fkColumn = $fkColumn;
33+
}
34+
35+
/**
36+
* @return array<string, array<string, mixed>> Rows, indexed by ID.
37+
*/
38+
private function load(): array
39+
{
40+
$results = $this->connection->fetchAll($this->sql);
41+
42+
$data = [];
43+
foreach ($results as $row) {
44+
$data[$row[$this->fkColumn]][] = $row;
45+
}
46+
47+
return $data;
48+
}
49+
50+
/**
51+
* Returns the DB row with the given ID.
52+
* Loads all rows if necessary.
53+
* Throws an exception if nothing found.
54+
*
55+
* @param string $id
56+
* @return array<string, mixed>
57+
*/
58+
public function get(string $id): array
59+
{
60+
if ($this->data === null) {
61+
$this->data = $this->load();
62+
}
63+
64+
if (!isset($this->data[$id])) {
65+
return [];
66+
}
67+
return $this->data[$id];
68+
}
69+
}

src/QueryFactory/SmartEagerLoad/Query/ManyToOnePartialQuery.php

+1-2
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,9 @@ class ManyToOnePartialQuery implements PartialQuery
3838

3939
public function __construct(PartialQuery $partialQuery, string $originTableName, string $tableName, string $pk, string $columnName)
4040
{
41-
// TODO: move this in a separate function. The constructor is called for every bean.
4241
$this->partialQuery = $partialQuery;
4342
$this->mainTable = $tableName;
44-
$this->key = $partialQuery->getKey().'__'.$columnName;
43+
$this->key = $partialQuery->getKey().'__mto__'.$columnName;
4544
$this->pk = $pk;
4645
$this->originTableName = $originTableName;
4746
$this->columnName = $columnName;

src/Utils/BeanDescriptor.php

+1-21
Original file line numberDiff line numberDiff line change
@@ -1596,7 +1596,7 @@ private function generateGetForeignKeys(array $fks): MethodGenerator
15961596
}
15971597
return parent::getForeignKeys(\$tableName);
15981598
EOF;
1599-
$code = sprintf($code, var_export($this->getTable()->getName(), true), $this->psr2VarExport($fkArray, ' '));
1599+
$code = sprintf($code, var_export($this->getTable()->getName(), true), Psr2Utils::psr2VarExport($fkArray, ' '));
16001600

16011601
$method = new MethodGenerator('getForeignKeys');
16021602
$method->setVisibility(AbstractMemberGenerator::VISIBILITY_PROTECTED);
@@ -1613,24 +1613,4 @@ private function generateGetForeignKeys(array $fks): MethodGenerator
16131613

16141614
return $method;
16151615
}
1616-
1617-
/**
1618-
* @param mixed $var
1619-
* @param string $indent
1620-
* @return string
1621-
*/
1622-
private function psr2VarExport($var, string $indent=''): string
1623-
{
1624-
if (is_array($var)) {
1625-
$indexed = array_keys($var) === range(0, count($var) - 1);
1626-
$r = [];
1627-
foreach ($var as $key => $value) {
1628-
$r[] = "$indent "
1629-
. ($indexed ? '' : $this->psr2VarExport($key) . ' => ')
1630-
. $this->psr2VarExport($value, "$indent ");
1631-
}
1632-
return "[\n" . implode(",\n", $r) . "\n" . $indent . ']';
1633-
}
1634-
return var_export($var, true);
1635-
}
16361616
}

src/Utils/DirectForeignKeyMethodDescriptor.php

+9-21
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Zend\Code\Generator\AbstractMemberGenerator;
1515
use Zend\Code\Generator\DocBlock\Tag\ReturnTag;
1616
use Zend\Code\Generator\MethodGenerator;
17+
use function var_export;
1718

1819
/**
1920
* Represents a method to get a list of beans from a direct foreign key pointing to our bean.
@@ -133,10 +134,12 @@ public function getCode() : array
133134
$getter->setReturnType('?' . $classType);
134135

135136
$code = sprintf(
136-
'return $this->retrieveManyToOneRelationshipsStorage(%s, %s, %s)->first();',
137+
'return $this->retrieveManyToOneRelationshipsStorage(%s, %s, %s, %s, %s)->first();',
137138
var_export($this->foreignKey->getLocalTableName(), true),
138139
var_export($tdbmFk->getCacheKey(), true),
139-
$this->getFilters($this->foreignKey)
140+
Psr2Utils::psr2InlineVarExport($this->foreignKey->getUnquotedLocalColumns()),
141+
Psr2Utils::psr2InlineVarExport($this->foreignKey->getUnquotedForeignColumns()),
142+
var_export($this->foreignKey->getForeignTableName(), true)
140143
);
141144
} else {
142145
$getter->setDocBlock(sprintf('Returns the list of %s pointing to this bean via the %s column.', $beanClass, implode(', ', $this->foreignKey->getUnquotedLocalColumns())));
@@ -147,10 +150,12 @@ public function getCode() : array
147150
$getter->setReturnType(AlterableResultIterator::class);
148151

149152
$code = sprintf(
150-
'return $this->retrieveManyToOneRelationshipsStorage(%s, %s, %s);',
153+
'return $this->retrieveManyToOneRelationshipsStorage(%s, %s, %s, %s, %s);',
151154
var_export($this->foreignKey->getLocalTableName(), true),
152155
var_export($tdbmFk->getCacheKey(), true),
153-
$this->getFilters($this->foreignKey)
156+
Psr2Utils::psr2InlineVarExport($this->foreignKey->getUnquotedLocalColumns()),
157+
Psr2Utils::psr2InlineVarExport($this->foreignKey->getUnquotedForeignColumns()),
158+
var_export($this->foreignKey->getForeignTableName(), true)
154159
);
155160
}
156161

@@ -163,23 +168,6 @@ public function getCode() : array
163168
return [ $getter ];
164169
}
165170

166-
private function getFilters(ForeignKeyConstraint $fk) : string
167-
{
168-
$counter = 0;
169-
$parameters = [];
170-
171-
$fkForeignColumns = $fk->getUnquotedForeignColumns();
172-
173-
foreach ($fk->getUnquotedLocalColumns() as $columnName) {
174-
$fkColumn = $fkForeignColumns[$counter];
175-
$parameters[] = sprintf('%s => $this->get(%s, %s)', var_export($fk->getLocalTableName().'.'.$columnName, true), var_export($fkColumn, true), var_export($this->foreignKey->getForeignTableName(), true));
176-
++$counter;
177-
}
178-
$parametersCode = '['.implode(', ', $parameters).']';
179-
180-
return $parametersCode;
181-
}
182-
183171
private $hasLocalUniqueIndex;
184172
/**
185173
* Check if the ForeignKey have an unique index

src/Utils/Psr2Utils.php

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
4+
namespace TheCodingMachine\TDBM\Utils;
5+
6+
7+
use function array_keys;
8+
use function count;
9+
use function implode;
10+
use function is_array;
11+
use function range;
12+
use function var_export;
13+
14+
class Psr2Utils
15+
{
16+
/**
17+
* @param mixed $var
18+
* @param string $indent
19+
* @return string
20+
*/
21+
public static function psr2VarExport($var, string $indent=''): string
22+
{
23+
if (is_array($var)) {
24+
$indexed = array_keys($var) === range(0, count($var) - 1);
25+
$r = [];
26+
foreach ($var as $key => $value) {
27+
$r[] = "$indent "
28+
. ($indexed ? '' : self::psr2VarExport($key) . ' => ')
29+
. self::psr2VarExport($value, "$indent ");
30+
}
31+
return "[\n" . implode(",\n", $r) . "\n" . $indent . ']';
32+
}
33+
return var_export($var, true);
34+
}
35+
36+
/**
37+
* @param mixed $var
38+
* @return string
39+
*/
40+
public static function psr2InlineVarExport($var): string
41+
{
42+
if (is_array($var)) {
43+
$indexed = array_keys($var) === range(0, count($var) - 1);
44+
$r = [];
45+
foreach ($var as $key => $value) {
46+
$r[] = ($indexed ? '' : self::psr2InlineVarExport($key) . ' => ')
47+
. self::psr2InlineVarExport($value);
48+
}
49+
return '[' . implode(',', $r) . ']';
50+
}
51+
return var_export($var, true);
52+
}
53+
}

tests/TDBMDaoGeneratorTest.php

+20-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
namespace TheCodingMachine\TDBM;
2323

24+
use Author;
2425
use Doctrine\Common\Cache\ArrayCache;
2526
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
2627
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
@@ -40,6 +41,7 @@
4041
use TheCodingMachine\TDBM\Test\Dao\AlbumDao;
4142
use TheCodingMachine\TDBM\Test\Dao\AllNullableDao;
4243
use TheCodingMachine\TDBM\Test\Dao\AnimalDao;
44+
use TheCodingMachine\TDBM\Test\Dao\ArticleDao;
4345
use TheCodingMachine\TDBM\Test\Dao\ArtistDao;
4446
use TheCodingMachine\TDBM\Test\Dao\BaseObjectDao;
4547
use TheCodingMachine\TDBM\Test\Dao\Bean\AccountBean;
@@ -2229,18 +2231,34 @@ public function testManyToOneEagerLoading(): void
22292231
$countryIds[] = $user->getCountry()->getId();
22302232
}
22312233

2232-
$this->assertFalse($users->getIterator()->hasManyToOneDataLoader('__country_id'));
2234+
$this->assertFalse($users->getIterator()->hasManyToOneDataLoader('__mto__country_id'));
22332235
$this->assertSame([2, 1, 3, 2, 2, 4], $countryIds);
22342236

22352237
$countryNames = [];
22362238
foreach ($users as $user) {
22372239
$countryNames[] = $user->getCountry()->getLabel();
22382240
}
22392241

2240-
$this->assertTrue($users->getIterator()->hasManyToOneDataLoader('__country_id'));
2242+
$this->assertTrue($users->getIterator()->hasManyToOneDataLoader('__mto__country_id'));
22412243
$this->assertSame(['UK', 'France', 'Jamaica', 'UK', 'UK', 'Mexico'], $countryNames);
22422244
}
22432245

2246+
/**
2247+
* @depends testDaoGeneration
2248+
*/
2249+
public function testManyToOneEagerLoadingOnTableWithInheritance(): void
2250+
{
2251+
$articleDao = new ArticleDao($this->tdbmService);
2252+
/** @var ArticleBean[] $articles */
2253+
$articles = $articleDao->findAll()->withOrder('id asc');
2254+
$names = [];
2255+
foreach ($articles as $article) {
2256+
$names[] = $article->getAuthor()->getName();
2257+
}
2258+
$this->assertCount(1, $names);
2259+
$this->assertSame('Bill Shakespeare', $names[0]);
2260+
}
2261+
22442262
/**
22452263
* @depends testDaoGeneration
22462264
*/

0 commit comments

Comments
 (0)