Skip to content

Commit

Permalink
Refactoring retrieveManyToOneRelationshipsStorage signature
Browse files Browse the repository at this point in the history
This will allow us in the future to build a oneToMany handler for smart eager loading.
  • Loading branch information
moufmouf committed Sep 11, 2019
1 parent a25a8f6 commit fa7685f
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 49 deletions.
17 changes: 14 additions & 3 deletions src/AbstractTDBMObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use TheCodingMachine\TDBM\QueryFactory\SmartEagerLoad\StorageNode;
use TheCodingMachine\TDBM\Schema\ForeignKeys;
use TheCodingMachine\TDBM\Utils\ManyToManyRelationshipPathDescriptor;
use function array_combine;

/**
* Instances of this class represent a "bean". Usually, a bean is mapped to a row of one table.
Expand Down Expand Up @@ -531,19 +532,29 @@ private function removeManyToOneRelationship(string $tableName, string $foreignK
*
* @param string $tableName
* @param string $foreignKeyName
* @param mixed[] $searchFilter
* @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 !
* @param array<int, string> $localColumns
* @param array<int, string> $foreignColumns
* @param string $foreignTableName
* @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 !
*
* @return AlterableResultIterator
* @throws TDBMException
*/
protected function retrieveManyToOneRelationshipsStorage(string $tableName, string $foreignKeyName, array $searchFilter, string $orderString = null) : AlterableResultIterator
protected function retrieveManyToOneRelationshipsStorage(string $tableName, string $foreignKeyName, array $localColumns, array $foreignColumns, string $foreignTableName, string $orderString = null) : AlterableResultIterator
{
$key = $tableName.'___'.$foreignKeyName;
$alterableResultIterator = $this->getManyToOneAlterableResultIterator($tableName, $foreignKeyName);
if ($this->status === TDBMObjectStateEnum::STATE_DETACHED || $this->status === TDBMObjectStateEnum::STATE_NEW || (isset($this->manyToOneRelationships[$key]) && $this->manyToOneRelationships[$key]->getUnderlyingResultIterator() !== null)) {
return $alterableResultIterator;
}

$ids = [];
foreach ($foreignColumns as $foreignColumn) {
$ids[] = $this->get($foreignColumn, $foreignTableName);
}

$searchFilter = array_combine($localColumns, $ids);

$unalteredResultIterator = $this->tdbmService->findObjects($tableName, $searchFilter, [], $orderString);

$alterableResultIterator->setResultIterator($unalteredResultIterator->getIterator());
Expand Down
69 changes: 69 additions & 0 deletions src/QueryFactory/SmartEagerLoad/OneToManyDataLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php


namespace TheCodingMachine\TDBM\QueryFactory\SmartEagerLoad;

use Doctrine\DBAL\Connection;
use TheCodingMachine\TDBM\TDBMException;

class OneToManyDataLoader
{
/**
* @var Connection
*/
private $connection;
/**
* @var string
*/
private $sql;
/**
* @var string
*/
private $fkColumn;
/**
* @var array<string, array<int, array<string, mixed>>> Array of rows, indexed by foreign key.
*/
private $data;

public function __construct(Connection $connection, string $sql, string $fkColumn)
{
$this->connection = $connection;
$this->sql = $sql;
$this->fkColumn = $fkColumn;
}

/**
* @return array<string, array<string, mixed>> Rows, indexed by ID.
*/
private function load(): array
{
$results = $this->connection->fetchAll($this->sql);

$data = [];
foreach ($results as $row) {
$data[$row[$this->fkColumn]][] = $row;
}

return $data;
}

/**
* Returns the DB row with the given ID.
* Loads all rows if necessary.
* Throws an exception if nothing found.
*
* @param string $id
* @return array<string, mixed>
*/
public function get(string $id): array
{
if ($this->data === null) {
$this->data = $this->load();
}

if (!isset($this->data[$id])) {
return [];
}
return $this->data[$id];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,9 @@ class ManyToOnePartialQuery implements PartialQuery

public function __construct(PartialQuery $partialQuery, string $originTableName, string $tableName, string $pk, string $columnName)
{
// TODO: move this in a separate function. The constructor is called for every bean.
$this->partialQuery = $partialQuery;
$this->mainTable = $tableName;
$this->key = $partialQuery->getKey().'__'.$columnName;
$this->key = $partialQuery->getKey().'__mto__'.$columnName;
$this->pk = $pk;
$this->originTableName = $originTableName;
$this->columnName = $columnName;
Expand Down
22 changes: 1 addition & 21 deletions src/Utils/BeanDescriptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -1596,7 +1596,7 @@ private function generateGetForeignKeys(array $fks): MethodGenerator
}
return parent::getForeignKeys(\$tableName);
EOF;
$code = sprintf($code, var_export($this->getTable()->getName(), true), $this->psr2VarExport($fkArray, ' '));
$code = sprintf($code, var_export($this->getTable()->getName(), true), Psr2Utils::psr2VarExport($fkArray, ' '));

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

return $method;
}

/**
* @param mixed $var
* @param string $indent
* @return string
*/
private function psr2VarExport($var, string $indent=''): string
{
if (is_array($var)) {
$indexed = array_keys($var) === range(0, count($var) - 1);
$r = [];
foreach ($var as $key => $value) {
$r[] = "$indent "
. ($indexed ? '' : $this->psr2VarExport($key) . ' => ')
. $this->psr2VarExport($value, "$indent ");
}
return "[\n" . implode(",\n", $r) . "\n" . $indent . ']';
}
return var_export($var, true);
}
}
30 changes: 9 additions & 21 deletions src/Utils/DirectForeignKeyMethodDescriptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Zend\Code\Generator\AbstractMemberGenerator;
use Zend\Code\Generator\DocBlock\Tag\ReturnTag;
use Zend\Code\Generator\MethodGenerator;
use function var_export;

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

$code = sprintf(
'return $this->retrieveManyToOneRelationshipsStorage(%s, %s, %s)->first();',
'return $this->retrieveManyToOneRelationshipsStorage(%s, %s, %s, %s, %s)->first();',
var_export($this->foreignKey->getLocalTableName(), true),
var_export($tdbmFk->getCacheKey(), true),
$this->getFilters($this->foreignKey)
Psr2Utils::psr2InlineVarExport($this->foreignKey->getUnquotedLocalColumns()),
Psr2Utils::psr2InlineVarExport($this->foreignKey->getUnquotedForeignColumns()),
var_export($this->foreignKey->getForeignTableName(), true)
);
} else {
$getter->setDocBlock(sprintf('Returns the list of %s pointing to this bean via the %s column.', $beanClass, implode(', ', $this->foreignKey->getUnquotedLocalColumns())));
Expand All @@ -147,10 +150,12 @@ public function getCode() : array
$getter->setReturnType(AlterableResultIterator::class);

$code = sprintf(
'return $this->retrieveManyToOneRelationshipsStorage(%s, %s, %s);',
'return $this->retrieveManyToOneRelationshipsStorage(%s, %s, %s, %s, %s);',
var_export($this->foreignKey->getLocalTableName(), true),
var_export($tdbmFk->getCacheKey(), true),
$this->getFilters($this->foreignKey)
Psr2Utils::psr2InlineVarExport($this->foreignKey->getUnquotedLocalColumns()),
Psr2Utils::psr2InlineVarExport($this->foreignKey->getUnquotedForeignColumns()),
var_export($this->foreignKey->getForeignTableName(), true)
);
}

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

private function getFilters(ForeignKeyConstraint $fk) : string
{
$counter = 0;
$parameters = [];

$fkForeignColumns = $fk->getUnquotedForeignColumns();

foreach ($fk->getUnquotedLocalColumns() as $columnName) {
$fkColumn = $fkForeignColumns[$counter];
$parameters[] = sprintf('%s => $this->get(%s, %s)', var_export($fk->getLocalTableName().'.'.$columnName, true), var_export($fkColumn, true), var_export($this->foreignKey->getForeignTableName(), true));
++$counter;
}
$parametersCode = '['.implode(', ', $parameters).']';

return $parametersCode;
}

private $hasLocalUniqueIndex;
/**
* Check if the ForeignKey have an unique index
Expand Down
53 changes: 53 additions & 0 deletions src/Utils/Psr2Utils.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php


namespace TheCodingMachine\TDBM\Utils;


use function array_keys;
use function count;
use function implode;
use function is_array;
use function range;
use function var_export;

class Psr2Utils
{
/**
* @param mixed $var
* @param string $indent
* @return string
*/
public static function psr2VarExport($var, string $indent=''): string
{
if (is_array($var)) {
$indexed = array_keys($var) === range(0, count($var) - 1);
$r = [];
foreach ($var as $key => $value) {
$r[] = "$indent "
. ($indexed ? '' : self::psr2VarExport($key) . ' => ')
. self::psr2VarExport($value, "$indent ");
}
return "[\n" . implode(",\n", $r) . "\n" . $indent . ']';
}
return var_export($var, true);
}

/**
* @param mixed $var
* @return string
*/
public static function psr2InlineVarExport($var): string
{
if (is_array($var)) {
$indexed = array_keys($var) === range(0, count($var) - 1);
$r = [];
foreach ($var as $key => $value) {
$r[] = ($indexed ? '' : self::psr2InlineVarExport($key) . ' => ')
. self::psr2InlineVarExport($value);
}
return '[' . implode(',', $r) . ']';
}
return var_export($var, true);
}
}
22 changes: 20 additions & 2 deletions tests/TDBMDaoGeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

namespace TheCodingMachine\TDBM;

use Author;
use Doctrine\Common\Cache\ArrayCache;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
Expand All @@ -40,6 +41,7 @@
use TheCodingMachine\TDBM\Test\Dao\AlbumDao;
use TheCodingMachine\TDBM\Test\Dao\AllNullableDao;
use TheCodingMachine\TDBM\Test\Dao\AnimalDao;
use TheCodingMachine\TDBM\Test\Dao\ArticleDao;
use TheCodingMachine\TDBM\Test\Dao\ArtistDao;
use TheCodingMachine\TDBM\Test\Dao\BaseObjectDao;
use TheCodingMachine\TDBM\Test\Dao\Bean\AccountBean;
Expand Down Expand Up @@ -2229,18 +2231,34 @@ public function testManyToOneEagerLoading(): void
$countryIds[] = $user->getCountry()->getId();
}

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

$countryNames = [];
foreach ($users as $user) {
$countryNames[] = $user->getCountry()->getLabel();
}

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

/**
* @depends testDaoGeneration
*/
public function testManyToOneEagerLoadingOnTableWithInheritance(): void
{
$articleDao = new ArticleDao($this->tdbmService);
/** @var ArticleBean[] $articles */
$articles = $articleDao->findAll()->withOrder('id asc');
$names = [];
foreach ($articles as $article) {
$names[] = $article->getAuthor()->getName();
}
$this->assertCount(1, $names);
$this->assertSame('Bill Shakespeare', $names[0]);
}

/**
* @depends testDaoGeneration
*/
Expand Down

0 comments on commit fa7685f

Please sign in to comment.