Skip to content

Commit

Permalink
embeddables: implement DbalCollection filtering support
Browse files Browse the repository at this point in the history
  • Loading branch information
hrach committed Dec 30, 2019
1 parent bc362fd commit c4a327a
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 63 deletions.
62 changes: 38 additions & 24 deletions src/Mapper/Dbal/QueryBuilderHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Nextras\Dbal\QueryBuilder\QueryBuilder;
use Nextras\Orm\Collection\Helpers\ConditionParserHelper;
use Nextras\Orm\Collection\ICollection;
use Nextras\Orm\Entity\Embeddable\EmbeddableContainer;
use Nextras\Orm\Entity\Reflection\EntityMetadata;
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
use Nextras\Orm\Entity\Reflection\PropertyRelationshipMetadata as Relationship;
Expand All @@ -26,7 +27,7 @@


/**
* QueryBuilder helper for Nextras\Dbal.
* QueryBuilder helper for Nextras Dbal.
*/
class QueryBuilderHelper
{
Expand Down Expand Up @@ -128,39 +129,51 @@ public function normalizeValue($value, ColumnReference $columnReference)
*/
private function processTokens(array $tokens, ?string $sourceEntity, QueryBuilder $builder): ColumnReference
{
$lastToken = array_pop($tokens);
$lastToken = \array_pop($tokens);
\assert($lastToken !== null);

$currentMapper = $this->mapper;
$currentAlias = $builder->getFromAlias();
$currentReflection = $currentMapper->getStorageReflection();
$currentEntityMetadata = $currentMapper->getRepository()->getEntityMetadata($sourceEntity);
$propertyPrefixTokens = "";

foreach ($tokens as $tokenIndex => $token) {
$property = $currentEntityMetadata->getProperty($token);
if ($property->relationship === null) {
throw new InvalidArgumentException("Entity {$currentEntityMetadata->className}::\${$token} does not contain a relationship.");
if ($property->relationship !== null) {
[
$currentAlias,
$currentReflection,
$currentEntityMetadata,
$currentMapper,
] = $this->processRelationship(
$tokens,
$builder,
$property,
$currentReflection,
$currentMapper,
$currentAlias,
$token,
$tokenIndex
);
} elseif ($property->wrapper === EmbeddableContainer::class) {
\assert($property->args !== null);
$currentEntityMetadata = $property->args[EmbeddableContainer::class]['metadata'];
$propertyPrefixTokens .= "$token->";
} else {
throw new InvalidArgumentException("Entity {$currentEntityMetadata->className}::\${$token} does not contain a relationship or an embeddable.");
}

[
$currentAlias,
$currentReflection,
$currentEntityMetadata,
$currentMapper,
] = $this->processRelationship(
$tokens,
$builder,
$property,
$currentReflection,
$currentMapper,
$currentAlias,
$token,
$tokenIndex
);
}

$propertyMetadata = $currentEntityMetadata->getProperty($lastToken);
$column = $this->toColumnExpr($currentEntityMetadata, $propertyMetadata, $currentReflection, $currentAlias);
$column = $this->toColumnExpr(
$currentEntityMetadata,
$propertyMetadata,
$currentReflection,
$currentAlias,
$propertyPrefixTokens
);

return new ColumnReference($column, $propertyMetadata, $currentEntityMetadata, $currentReflection);
}

Expand Down Expand Up @@ -249,15 +262,16 @@ private function toColumnExpr(
EntityMetadata $entityMetadata,
PropertyMetadata $propertyMetadata,
IStorageReflection $storageReflection,
string $alias
string $alias,
string $propertyPrefixTokens
)
{
if ($propertyMetadata->isPrimary && $propertyMetadata->isVirtual) { // primary-proxy
$primaryKey = $entityMetadata->getPrimaryKey();
if (count($primaryKey) > 1) { // composite primary key
$pair = [];
foreach ($primaryKey as $columnName) {
$columnName = $storageReflection->convertEntityToStorageKey($columnName);
$columnName = $storageReflection->convertEntityToStorageKey($propertyPrefixTokens . $columnName);
$pair[] = "{$alias}.{$columnName}";
}
return $pair;
Expand All @@ -268,7 +282,7 @@ private function toColumnExpr(
$propertyName = $propertyMetadata->name;
}

$columnName = $storageReflection->convertEntityToStorageKey($propertyName);
$columnName = $storageReflection->convertEntityToStorageKey($propertyPrefixTokens . $propertyName);
$columnExpr = "{$alias}.{$columnName}";
return $columnExpr;
}
Expand Down
69 changes: 33 additions & 36 deletions src/Mapper/Dbal/StorageReflection/StorageReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ abstract class StorageReflection implements IStorageReflection
use SmartObject;
const TO_STORAGE = 0;
const TO_ENTITY = 1;
const TO_STORAGE_EXPANSION = 2;
const TO_ENTITY_COMPRESSION = 3;
const TO_STORAGE_FLATTENING = 2;

/** @var string */
public $manyHasManyStorageNamePattern = '%s_x_%s';
Expand Down Expand Up @@ -110,14 +109,12 @@ public function convertEntityToStorage(array $in): array
{
$out = [];

if (isset($this->mappings[self::TO_STORAGE_EXPANSION])) {
foreach ($this->mappings[self::TO_STORAGE_EXPANSION] as $key => $maps) {
if (isset($in[$key]) || array_key_exists($key, $in)) {
foreach ($maps as $from => $to) {
$in[$to] = $in[$key][$from] ?? null;
}
unset($in[$key]);
}
if (isset($this->mappings[self::TO_STORAGE_FLATTENING])) {
foreach ($this->mappings[self::TO_STORAGE_FLATTENING] as $to => $from) {
$in[$to] = Arrays::get($in, $from, null);
}
foreach ($this->mappings[self::TO_STORAGE_FLATTENING] as $to => $from) {
unset($in[$from[0]]);
}
}

Expand Down Expand Up @@ -148,16 +145,6 @@ public function convertStorageToEntity(array $in): array
{
$out = [];

if (isset($this->mappings[self::TO_ENTITY_COMPRESSION])) {
foreach ($this->mappings[self::TO_ENTITY_COMPRESSION] as $key => $path) {
if (isset($in[$key]) || array_key_exists($key, $in)) {
$ref = &Arrays::getRef($out, $path);
$ref = $in[$key];
unset($in[$key]);
}
}
}

foreach ($in as $key => $val) {
if (isset($this->mappings[self::TO_ENTITY][$key][0])) {
$newKey = $this->mappings[self::TO_ENTITY][$key][0];
Expand All @@ -167,7 +154,12 @@ public function convertStorageToEntity(array $in): array

if (isset($this->mappings[self::TO_ENTITY][$key][1])) {
$converter = $this->mappings[self::TO_ENTITY][$key][1];
$out[$newKey] = $converter($val, $newKey);
$val = $converter($val, $newKey);
}

if (\stripos($newKey, '->') !== false) {
$ref = &Arrays::getRef($out, \explode('->', $newKey));
$ref = $val;
} else {
$out[$newKey] = $val;
}
Expand Down Expand Up @@ -319,35 +311,40 @@ protected function getDefaultMappings(): array
$mappings[self::TO_STORAGE][$entityKey] = [$storageKey, null];
}

/** @var array<array<EntityMetadata, string[]>> $toProcess */
/** @phpstan-var array<array<EntityMetadata, string[]>> $toProcess */
$toProcess = [[$this->entityMetadata, []]];
while (([$metadata, $path] = \array_shift($toProcess)) !== null) {
while (([$metadata, $tokens] = \array_shift($toProcess)) !== null) {
foreach ($metadata->getProperties() as $property) {
if ($property->wrapper !== EmbeddableContainer::class) continue;
if ($property->wrapper !== EmbeddableContainer::class) {
continue;
}

$subMetadata = $property->args[EmbeddableContainer::class]['metadata'];
\assert($subMetadata instanceof EntityMetadata);

$map = [];
$path[] = $property->name;
$tokens[] = $property->name;

foreach ($subMetadata->getProperties() as $subProperty) {
$propertyPath = $path;
$propertyPath[] = $subProperty->name;
$storageKey = \implode($this->embeddableSeparatorPattern, array_map(function($key) {
return $this->formatStorageKey($key);
}, $propertyPath));
$propertyTokens = $tokens;
$propertyTokens[] = $subProperty->name;

$propertyKey = \implode('->', $propertyTokens);
$storageKey = \implode(
$this->embeddableSeparatorPattern,
\array_map(function($key) {
return $this->formatStorageKey($key);
}, $propertyTokens)
);

$mappings[self::TO_ENTITY_COMPRESSION][$storageKey] = $propertyPath;
$map[$subProperty->name] = $storageKey;
$mappings[self::TO_ENTITY][$storageKey] = [$propertyKey];
$mappings[self::TO_STORAGE][$propertyKey] = [$storageKey];
$mappings[self::TO_STORAGE_FLATTENING][$propertyKey] = $propertyTokens;

if ($subProperty->wrapper === EmbeddableContainer::class) {
\assert($subProperty->args !== null);
$toProcess[] = [$subProperty->args[EmbeddableContainer::class]['metadata'], $path];
$toProcess[] = [$subProperty->args[EmbeddableContainer::class]['metadata'], $tokens];
}
}

$mappings[self::TO_STORAGE_EXPANSION][$property->name] = $map;
}
}

Expand Down
2 changes: 1 addition & 1 deletion tests/db/mssql-init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ CREATE TABLE books (
published_at datetimeoffset NOT NULL,
printed_at datetimeoffset,
ean_id int,
price_cents int,
price int,
price_currency char(3),
PRIMARY KEY (id),
CONSTRAINT books_authors FOREIGN KEY (author_id) REFERENCES authors (id),
Expand Down
2 changes: 1 addition & 1 deletion tests/db/mysql-init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ CREATE TABLE books (
published_at DATETIME NOT NULL,
printed_at DATETIME,
ean_id int,
price_cents int,
price int,
price_currency char(3),
PRIMARY KEY (id),
CONSTRAINT books_authors FOREIGN KEY (author_id) REFERENCES authors (id),
Expand Down
2 changes: 1 addition & 1 deletion tests/db/pgsql-init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ CREATE TABLE "books" (
"published_at" TIMESTAMP NOT NULL,
"printed_at" TIMESTAMP,
"ean_id" int,
"price_cents" int,
"price" int,
"price_currency" char(3),
PRIMARY KEY ("id"),
CONSTRAINT "books_authors" FOREIGN KEY ("author_id") REFERENCES authors ("id"),
Expand Down
8 changes: 8 additions & 0 deletions tests/inc/model/book/BooksMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,12 @@ public function findFirstBook()
{
return $this->toEntity($this->builder()->where('id = 1'));
}


protected function createStorageReflection()
{
$reflection = parent::createStorageReflection();
$reflection->setMapping('price->cents', 'price');
return $reflection;
}
}

0 comments on commit c4a327a

Please sign in to comment.