Skip to content

Commit

Permalink
Add embeddable support
Browse files Browse the repository at this point in the history
  • Loading branch information
pamil committed Oct 16, 2019
1 parent c580f8e commit b86d605
Show file tree
Hide file tree
Showing 12 changed files with 178 additions and 387 deletions.
120 changes: 69 additions & 51 deletions src/Bundle/Doctrine/ORM/ExpressionBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
namespace Sylius\Bundle\GridBundle\Doctrine\ORM;

use Doctrine\ORM\Query\Expr\Comparison;
use Doctrine\ORM\Query\Expr\From;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Sylius\Component\Grid\Data\ExpressionBuilderInterface;

Expand Down Expand Up @@ -152,15 +154,6 @@ public function addOrderBy(string $field, string $direction)
return $this->queryBuilder->addOrderBy($this->resolveFieldByAddingJoins($field), $direction);
}

private function getFieldName(string $field): string
{
if (false === strpos($field, '.')) {
return sprintf('%s.%s', $this->getRootAlias(), $field);
}

return $field;
}

private function getParameterName(string $field): string
{
$parameterName = str_replace('.', '_', $field);
Expand Down Expand Up @@ -195,70 +188,95 @@ private function adjustField(string $field): string

private function resolveFieldByAddingJoins(string $field): string
{
if (0 === substr_count($field, '.')) {
return $this->getFieldName($field);
}
$entityManager = $this->queryBuilder->getEntityManager();

$key = 0;
$field = $this->getAbsolutePath($field);
$rootField = explode('.', $field, 2)[0];

$newAlias = $this->getAlias($key);
$fields = explode('.', $field);
foreach ($fields as $field) {
if ($key === count($fields) - 1) {
break;
}
$metadata = null;

if ($this->hasAlias($field)) {
$newAlias = $field;
++$key;

continue;
/** @var From[] $froms */
$froms = $this->queryBuilder->getDQLPart('from');
foreach ($froms as $from) {
if ($from->getAlias() === $rootField) {
$metadata = $entityManager->getClassMetadata($from->getFrom());
}
}

$joinAlias = $newAlias;
$newAlias = $this->getAlias(++$key);

$this->queryBuilder->innerJoin(sprintf('%s.%s', $joinAlias, $field), $newAlias);
if ($metadata === null) {
throw new \RuntimeException(sprintf('Could not get metadata for "%s".', $field));
}

return sprintf('%s.%s', $newAlias, $fields[$key]);
}
$explodedField = explode('.', $field, 3);
while (count($explodedField) === 3) {
if (isset($metadata->embeddedClasses[$explodedField[1]])) {
return implode('.', $explodedField);
}

private function getAlias(int $number): string
{
$rootAlias = $this->getRootAlias();
$alias = $rootAlias . ($number === 0 ? '' : (string) $number);
$joins = $this->queryBuilder->getDQLParts()['join'];
$metadata = $entityManager->getClassMetadata($metadata->getAssociationMapping($explodedField[1])['targetEntity']);
$relatedField = sprintf('%s.%s', $explodedField[0], $explodedField[1]);

if (empty($joins)) {
return $alias;
}
/** @var Join[] $joins */
$joins = array_merge([], ...array_values($this->queryBuilder->getDQLPart('join')));
foreach ($joins as $join) {
if ($join->getJoin() === $relatedField) {
unset($explodedField[0]);
$explodedField[1] = $join->getAlias();
$explodedField = explode('.', implode('.', $explodedField), 3);

foreach ($joins[$rootAlias] as $existentJoin) {
if ($existentJoin->getAlias() === $alias) {
return $this->getAlias($number + 1);
continue 2;
}
}

$this->queryBuilder->innerJoin($relatedField, md5($relatedField));
unset($explodedField[0]);
$explodedField[1] = md5($relatedField);
$explodedField = explode('.', implode('.', $explodedField), 3);
}

return $alias;
return implode('.', $explodedField);
}

private function hasAlias(string $alias): bool
/**
* This method returns an absolute path of a property path.
*
* Given the following query:
*
* SELECT bo FROM Book bo INNER JOIN Author au ON bo.author_id = au.id
*
* It will behave as follows:
*
* book.title => book.title
* title => book.title
* au => book.author
* au.name => book.author.name
*/
private function getAbsolutePath(string $field): string
{
$rootAlias = $this->getRootAlias();
$explodedField = explode('.', $field);

$joins = $this->queryBuilder->getDQLParts()['join'];
if (!in_array($explodedField[0], $this->queryBuilder->getAllAliases(), true)) {
$field = sprintf('%s.%s', $this->getRootAlias(), $field);

if (empty($joins)) {
return false;
$explodedField = explode('.', $field);
}

foreach ($joins[$rootAlias] as $existentJoin) {
if ($existentJoin->getAlias() === $alias) {
return true;
/** @var Join[] $joins */
$joins = array_merge([], ...array_values($this->queryBuilder->getDQLPart('join')));
while (!in_array($explodedField[0], $this->queryBuilder->getRootAliases(), true)) {
foreach ($joins as $join) {
if ($join->getAlias() === $explodedField[0]) {
$explodedField[0] = $join->getJoin();
$field = implode('.', $explodedField);
$explodedField = explode('.', $field);

continue 2;
}
}

throw new \RuntimeException(sprintf('Could not get mapping for "%s".', $field));
}

return false;
return $field;
}
}
11 changes: 11 additions & 0 deletions src/Bundle/Tests/DataFixtures/ORM/fixtures.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,20 @@ AppBundle\Entity\Book:
book_jurassic_park:
title: "Jurassic Park"
author: "@author_michael_crichton"
price: "@price_1"
book_the_lost_world:
title: "The Lost World"
author: "@author_michael_crichton"
price: "@price_2"
book_{1..100}:
title: "Book <current()>"
author: "@author_<numberBetween(1, 19)>"
price: "@price_<numberBetween(1, 2)>"

AppBundle\Entity\Price:
price_1:
amount: 4200
currencyCode: "EUR"
price_2:
amount: 1000
currencyCode: "GBP"
11 changes: 11 additions & 0 deletions src/Bundle/Tests/Functional/GridApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,17 @@ public function it_filters_books_by_authors_nationality(): void
$this->assertSame('Jurassic Park', $this->getFirstItemFromCurrentResponse()['title']);
}

/** @test */
public function it_filters_books_by_author_and_currency(): void
{
$authorId = $this->data['author_michael_crichton']->getId();

$this->client->request('GET', sprintf('/books/?criteria[author]=%d&criteria[currencyCode]=%s', $authorId, 'EUR'));

$this->assertCount(1, $this->getItemsFromCurrentResponse());
$this->assertSame('Jurassic Park', $this->getFirstItemFromCurrentResponse()['title']);
}

/** @test */
public function it_sorts_books_ascending_by_author(): void
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"id": "@integer@",
"name": "American"
}
}
},
"price": "@array@"
},
{
"id": "@integer@",
Expand All @@ -38,7 +39,8 @@
"id": "@integer@",
"name": "American"
}
}
},
"price": "@array@"
}
]
}
Expand Down
Loading

0 comments on commit b86d605

Please sign in to comment.