Skip to content

Commit 236a4a6

Browse files
committed
Identical operator for single or collection valued associations
This allow to use `have` and `empty` operators indifferently on associations that may be single valued or collection valued. This makes it easier for API client, who don't need to know the exact DB structure, but care more about the general semantic of an existing (or non-existing) relation.
1 parent c4384ec commit 236a4a6

14 files changed

+220
-117
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace GraphQL\Doctrine\Definition\Operator;
6+
7+
use Doctrine\ORM\Mapping\ClassMetadata;
8+
use Doctrine\ORM\QueryBuilder;
9+
use GraphQL\Doctrine\Factory\UniqueNameFactory;
10+
11+
abstract class AbstractAssociationOperatorType extends AbstractOperator
12+
{
13+
final public function getDqlCondition(UniqueNameFactory $uniqueNameFactory, ClassMetadata $metadata, QueryBuilder $queryBuilder, string $alias, string $field, ?array $args): ?string
14+
{
15+
if ($args === null) {
16+
return null;
17+
}
18+
19+
if ($metadata->isSingleValuedAssociation($field)) {
20+
return $this->getSingleValuedDqlCondition($uniqueNameFactory, $metadata, $queryBuilder, $alias, $field, $args);
21+
}
22+
23+
return $this->getCollectionValuedDqlCondition($uniqueNameFactory, $metadata, $queryBuilder, $alias, $field, $args);
24+
}
25+
26+
abstract protected function getSingleValuedDqlCondition(UniqueNameFactory $uniqueNameFactory, ClassMetadata $metadata, QueryBuilder $queryBuilder, string $alias, string $field, array $args): ?string;
27+
28+
abstract protected function getCollectionValuedDqlCondition(UniqueNameFactory $uniqueNameFactory, ClassMetadata $metadata, QueryBuilder $queryBuilder, string $alias, string $field, array $args): ?string;
29+
}

src/Definition/Operator/AbstractOperator.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,17 @@
1919
*/
2020
abstract class AbstractOperator extends InputObjectType
2121
{
22+
/**
23+
* Types registry
24+
*
25+
* @var Types
26+
*/
27+
protected $types;
28+
2229
final public function __construct(Types $types, LeafType $leafType)
2330
{
24-
$config = $this->getConfiguration($types, $leafType);
31+
$this->types = $types;
32+
$config = $this->getConfiguration($leafType);
2533

2634
// Override type name to be predictable
2735
$config['name'] = Utils::getOperatorTypeName(get_class($this), $leafType);
@@ -40,12 +48,11 @@ final public function __construct(Types $types, LeafType $leafType)
4048
* will be overridden in all cases. This is because we must have a predictable name
4149
* that is based only on the class name.
4250
*
43-
* @param Types $types
4451
* @param LeafType $leafType
4552
*
4653
* @return array
4754
*/
48-
abstract protected function getConfiguration(Types $types, LeafType $leafType): array;
55+
abstract protected function getConfiguration(LeafType $leafType): array;
4956

5057
/**
5158
* Return the DQL condition to apply the filter

src/Definition/Operator/AbstractSimpleOperator.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use Doctrine\ORM\Mapping\ClassMetadata;
88
use Doctrine\ORM\QueryBuilder;
99
use GraphQL\Doctrine\Factory\UniqueNameFactory;
10-
use GraphQL\Doctrine\Types;
1110
use GraphQL\Type\Definition\LeafType;
1211
use GraphQL\Type\Definition\Type;
1312

@@ -18,17 +17,17 @@ abstract class AbstractSimpleOperator extends AbstractOperator
1817
{
1918
abstract protected function getDqlOperator(bool $isNot): string;
2019

21-
final protected function getConfiguration(Types $types, LeafType $leafType): array
20+
final protected function getConfiguration(LeafType $leafType): array
2221
{
2322
return [
2423
'fields' => [
2524
[
2625
'name' => 'value',
27-
'type' => Type::nonNull($leafType),
26+
'type' => self::nonNull($leafType),
2827
],
2928
[
3029
'name' => 'not',
31-
'type' => Type::boolean(),
30+
'type' => self::boolean(),
3231
'defaultValue' => false,
3332
],
3433
],

src/Definition/Operator/BetweenOperatorType.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,26 @@
77
use Doctrine\ORM\Mapping\ClassMetadata;
88
use Doctrine\ORM\QueryBuilder;
99
use GraphQL\Doctrine\Factory\UniqueNameFactory;
10-
use GraphQL\Doctrine\Types;
1110
use GraphQL\Type\Definition\LeafType;
1211
use GraphQL\Type\Definition\Type;
1312

1413
final class BetweenOperatorType extends AbstractOperator
1514
{
16-
protected function getConfiguration(Types $types, LeafType $leafType): array
15+
protected function getConfiguration(LeafType $leafType): array
1716
{
1817
return [
1918
'fields' => [
2019
[
2120
'name' => 'from',
22-
'type' => Type::nonNull($leafType),
21+
'type' => self::nonNull($leafType),
2322
],
2423
[
2524
'name' => 'to',
26-
'type' => Type::nonNull($leafType),
25+
'type' => self::nonNull($leafType),
2726
],
2827
[
2928
'name' => 'not',
30-
'type' => Type::boolean(),
29+
'type' => self::boolean(),
3130
'defaultValue' => false,
3231
],
3332
],

src/Definition/Operator/ContainOperatorType.php

Lines changed: 0 additions & 45 deletions
This file was deleted.

src/Definition/Operator/EmptyOperatorType.php

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,33 @@
77
use Doctrine\ORM\Mapping\ClassMetadata;
88
use Doctrine\ORM\QueryBuilder;
99
use GraphQL\Doctrine\Factory\UniqueNameFactory;
10-
use GraphQL\Doctrine\Types;
1110
use GraphQL\Type\Definition\LeafType;
12-
use GraphQL\Type\Definition\Type;
1311

14-
final class EmptyOperatorType extends AbstractOperator
12+
final class EmptyOperatorType extends AbstractAssociationOperatorType
1513
{
16-
protected function getConfiguration(Types $types, LeafType $leafType): array
14+
protected function getConfiguration(LeafType $leafType): array
1715
{
1816
return [
17+
'description' => 'When used on single valued association, it will use `IS NULL` operator. On collection valued association it will use `IS EMPTY` operator.',
1918
'fields' => [
2019
[
2120
'name' => 'not',
22-
'type' => Type::boolean(),
21+
'type' => self::boolean(),
2322
'defaultValue' => false,
2423
],
2524
],
2625
];
2726
}
2827

29-
public function getDqlCondition(UniqueNameFactory $uniqueNameFactory, ClassMetadata $metadata, QueryBuilder $queryBuilder, string $alias, string $field, ?array $args): ?string
28+
protected function getSingleValuedDqlCondition(UniqueNameFactory $uniqueNameFactory, ClassMetadata $metadata, QueryBuilder $queryBuilder, string $alias, string $field, array $args): ?string
3029
{
31-
if ($args === null) {
32-
return null;
33-
}
30+
$null = $this->types->getOperator(NullOperatorType::class, self::id());
3431

32+
return $null->getDqlCondition($uniqueNameFactory, $metadata, $queryBuilder, $alias, $field, $args);
33+
}
34+
35+
protected function getCollectionValuedDqlCondition(UniqueNameFactory $uniqueNameFactory, ClassMetadata $metadata, QueryBuilder $queryBuilder, string $alias, string $field, array $args): ?string
36+
{
3537
$not = $args['not'] ? 'NOT ' : '';
3638

3739
return $alias . '.' . $field . ' IS ' . $not . 'EMPTY';
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace GraphQL\Doctrine\Definition\Operator;
6+
7+
use Doctrine\ORM\Mapping\ClassMetadata;
8+
use Doctrine\ORM\QueryBuilder;
9+
use GraphQL\Doctrine\Factory\UniqueNameFactory;
10+
use GraphQL\Type\Definition\LeafType;
11+
12+
final class HaveOperatorType extends AbstractAssociationOperatorType
13+
{
14+
protected function getConfiguration(LeafType $leafType): array
15+
{
16+
return [
17+
'description' => 'When used on single valued association, it will use `IN` operator. On collection valued association it will use `MEMBER OF` operator.',
18+
'fields' => [
19+
[
20+
'name' => 'values',
21+
'type' => self::nonNull(self::listOf(self::nonNull(self::id()))),
22+
],
23+
[
24+
'name' => 'not',
25+
'type' => self::boolean(),
26+
'defaultValue' => false,
27+
],
28+
],
29+
];
30+
}
31+
32+
protected function getSingleValuedDqlCondition(UniqueNameFactory $uniqueNameFactory, ClassMetadata $metadata, QueryBuilder $queryBuilder, string $alias, string $field, array $args): ?string
33+
{
34+
$in = $this->types->getOperator(InOperatorType::class, self::id());
35+
36+
return $in->getDqlCondition($uniqueNameFactory, $metadata, $queryBuilder, $alias, $field, $args);
37+
}
38+
39+
protected function getCollectionValuedDqlCondition(UniqueNameFactory $uniqueNameFactory, ClassMetadata $metadata, QueryBuilder $queryBuilder, string $alias, string $field, array $args): ?string
40+
{
41+
$values = $uniqueNameFactory->createParameterName();
42+
$queryBuilder->setParameter($values, $args['values']);
43+
$not = $args['not'] ? 'NOT ' : '';
44+
45+
return ':' . $values . ' ' . $not . 'MEMBER OF ' . $alias . '.' . $field;
46+
}
47+
}

src/Definition/Operator/InOperatorType.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,22 @@
77
use Doctrine\ORM\Mapping\ClassMetadata;
88
use Doctrine\ORM\QueryBuilder;
99
use GraphQL\Doctrine\Factory\UniqueNameFactory;
10-
use GraphQL\Doctrine\Types;
1110
use GraphQL\Type\Definition\LeafType;
1211
use GraphQL\Type\Definition\Type;
1312

1413
final class InOperatorType extends AbstractOperator
1514
{
16-
protected function getConfiguration(Types $types, LeafType $leafType): array
15+
protected function getConfiguration(LeafType $leafType): array
1716
{
1817
return [
1918
'fields' => [
2019
[
2120
'name' => 'values',
22-
'type' => Type::nonNull(Type::listOf(Type::nonNull($leafType))),
21+
'type' => self::nonNull(self::listOf(self::nonNull($leafType))),
2322
],
2423
[
2524
'name' => 'not',
26-
'type' => Type::boolean(),
25+
'type' => self::boolean(),
2726
'defaultValue' => false,
2827
],
2928
],

src/Definition/Operator/NullOperatorType.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,18 @@
77
use Doctrine\ORM\Mapping\ClassMetadata;
88
use Doctrine\ORM\QueryBuilder;
99
use GraphQL\Doctrine\Factory\UniqueNameFactory;
10-
use GraphQL\Doctrine\Types;
1110
use GraphQL\Type\Definition\LeafType;
1211
use GraphQL\Type\Definition\Type;
1312

1413
final class NullOperatorType extends AbstractOperator
1514
{
16-
protected function getConfiguration(Types $types, LeafType $leafType): array
15+
protected function getConfiguration(LeafType $leafType): array
1716
{
1817
return [
1918
'fields' => [
2019
[
2120
'name' => 'not',
22-
'type' => Type::boolean(),
21+
'type' => self::boolean(),
2322
'defaultValue' => false,
2423
],
2524
],

0 commit comments

Comments
 (0)