Skip to content

Commit 488b222

Browse files
authored
Merge pull request #128 from TomHAnderson/feature/include-criteria
Created ExcludeCriteria trait and added includeCriteria to all attributes
2 parents 07a001a + 201e5ea commit 488b222

File tree

9 files changed

+257
-21
lines changed

9 files changed

+257
-21
lines changed

src/Attribute/Association.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@
99
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
1010
class Association
1111
{
12-
/** @param string[] $excludeCriteria */
12+
use ExcludeCriteria;
13+
14+
/**
15+
* @param string[] $excludeCriteria
16+
* @param string[] $includeCriteria
17+
*/
1318
public function __construct(
1419
protected string $group = 'default',
1520
protected string|null $strategy = null,
1621
protected string|null $description = null,
1722
protected array $excludeCriteria = [],
23+
protected array $includeCriteria = [],
1824
protected string|null $filterCriteriaEventName = null,
1925
) {
2026
}
@@ -34,12 +40,6 @@ public function getDescription(): string|null
3440
return $this->description;
3541
}
3642

37-
/** @return string[] */
38-
public function getExcludeCriteria(): array
39-
{
40-
return $this->excludeCriteria;
41-
}
42-
4343
public function getFilterCriteriaEventName(): string|null
4444
{
4545
return $this->filterCriteriaEventName;

src/Attribute/Entity.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
1010
final class Entity
1111
{
12+
use ExcludeCriteria;
13+
1214
/** @var string The GraphQL group */
1315
private string $group;
1416

@@ -30,6 +32,7 @@ final class Entity
3032
/**
3133
* @param mixed[] $filters
3234
* @param string[] $excludeCriteria
35+
* @param string[] $includeCriteria
3336
*/
3437
public function __construct(
3538
string $group = 'default',
@@ -39,6 +42,7 @@ public function __construct(
3942
array $filters = [],
4043
private string|null $namingStrategy = null,
4144
private array $excludeCriteria = [],
45+
private array $includeCriteria = [],
4246
) {
4347
$this->group = $group;
4448
$this->byValue = $byValue;
@@ -76,10 +80,4 @@ public function getNamingStrategy(): string|null
7680
{
7781
return $this->namingStrategy;
7882
}
79-
80-
/** @return string[] */
81-
public function getExcludeCriteria(): array
82-
{
83-
return $this->excludeCriteria;
84-
}
8583
}

src/Attribute/ExcludeCriteria.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace ApiSkeletons\Doctrine\GraphQL\Attribute;
6+
7+
use ApiSkeletons\Doctrine\GraphQL\Criteria\Filters;
8+
use Exception;
9+
10+
use function array_diff;
11+
use function array_intersect;
12+
13+
trait ExcludeCriteria
14+
{
15+
/** @return string[] */
16+
public function getExcludeCriteria(): array
17+
{
18+
if ($this->includeCriteria && $this->excludeCriteria) {
19+
throw new Exception('includeCriteria and excludeCriteria are mutually exclusive.');
20+
}
21+
22+
if ($this->includeCriteria) {
23+
$this->excludeCriteria = array_diff(Filters::toArray(), $this->includeCriteria);
24+
} elseif ($this->excludeCriteria) {
25+
$this->excludeCriteria = array_intersect(Filters::toArray(), $this->excludeCriteria);
26+
}
27+
28+
return $this->excludeCriteria;
29+
}
30+
}

src/Attribute/Field.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@
99
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
1010
class Field
1111
{
12-
/** @param string[] $excludeCriteria */
12+
use ExcludeCriteria;
13+
14+
/**
15+
* @param string[] $excludeCriteria
16+
* @param string[] $includeCriteria
17+
*/
1318
public function __construct(
1419
protected string $group = 'default',
1520
protected string|null $strategy = null,
1621
protected string|null $description = null,
1722
protected string|null $type = null,
1823
private array $excludeCriteria = [],
24+
private array $includeCriteria = [],
1925
) {
2026
}
2127

@@ -38,10 +44,4 @@ public function getType(): string|null
3844
{
3945
return $this->type;
4046
}
41-
42-
/** @return string[] */
43-
public function getExcludeCriteria(): array
44-
{
45-
return $this->excludeCriteria;
46-
}
4747
}

test/Entity/Artist.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace ApiSkeletonsTest\Doctrine\GraphQL\Entity;
66

77
use ApiSkeletons\Doctrine\GraphQL\Attribute as GraphQL;
8+
use ApiSkeletons\Doctrine\GraphQL\Criteria\Filters;
89
use Doctrine\Common\Collections\ArrayCollection;
910
use Doctrine\Common\Collections\Collection;
1011
use Doctrine\ORM\Mapping as ORM;
@@ -45,7 +46,8 @@ class Artist
4546

4647
/** @var Collection<id, Performance> */
4748
#[GraphQL\Association(description: 'Performances')]
48-
#[GraphQL\Association(group: 'ExcludeCriteriaTest', excludeCriteria: ['neq'])]
49+
#[GraphQL\Association(group: 'ExcludeCriteriaTest', excludeCriteria: [Filters::NEQ])]
50+
#[GraphQL\Association(group: 'IncludeCriteriaTest', includeCriteria: [Filters::EQ])]
4951
#[GraphQL\Association(group: 'DuplicateGroup')]
5052
#[GraphQL\Association(group: 'DuplicateGroup')]
5153
#[GraphQL\Association(group: 'DuplicateGroupAssociation')]

test/Entity/Performance.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace ApiSkeletonsTest\Doctrine\GraphQL\Entity;
66

77
use ApiSkeletons\Doctrine\GraphQL\Attribute as GraphQL;
8+
use ApiSkeletons\Doctrine\GraphQL\Criteria\Filters;
89
use DateTime;
910
use Doctrine\Common\Collections\ArrayCollection;
1011
use Doctrine\Common\Collections\Collection;
@@ -15,23 +16,45 @@
1516
*/
1617
#[GraphQL\Entity(typeName: 'performance', description: 'Performances')]
1718
#[GraphQL\Entity(group: 'ExcludeCriteriaTest', excludeCriteria: ['contains'])]
19+
#[GraphQL\Entity(group: 'IncludeCriteriaTest', includeCriteria: [
20+
Filters::EQ,
21+
Filters::NEQ,
22+
Filters::CONTAINS,
23+
])]
24+
#[GraphQL\Entity(
25+
group: 'IncludeExcludeCriteriaTest',
26+
excludeCriteria: [Filters::IN],
27+
includeCriteria: [
28+
Filters::EQ,
29+
Filters::NEQ,
30+
Filters::CONTAINS,
31+
],
32+
)]
1833
#[GraphQL\Entity(group: 'FilterCriteriaEvent')]
1934
#[ORM\Entity]
2035
class Performance
2136
{
2237
#[GraphQL\Field(description: 'Venue name')]
2338
#[GraphQL\Field(description: 'Venue name', group: 'ExcludeCriteriaTest')]
39+
#[GraphQL\Field(group: 'IncludeCriteriaTest')]
2440
#[GraphQL\Field(group: 'FilterCriteriaEvent')]
2541
#[ORM\Column(type: 'string', nullable: true)]
2642
private string|null $venue = null;
2743

2844
#[GraphQL\Field(description: 'City name')]
2945
#[GraphQL\Field(group: 'FilterCriteriaEvent')]
46+
#[GraphQL\Field(group: 'IncludeCriteriaTest', includeCriteria: [
47+
Filters::EQ,
48+
Filters::NEQ,
49+
])]
3050
#[ORM\Column(type: 'string', nullable: true)]
3151
private string|null $city = null;
3252

3353
#[GraphQL\Field(description: 'State name')]
3454
#[GraphQL\Field(group: 'FilterCriteriaEvent')]
55+
#[GraphQL\Field(group: 'IncludeCriteriaTest', excludeCriteria: [
56+
Filters::EQ,
57+
])]
3558
#[ORM\Column(type: 'string', nullable: true)]
3659
private string|null $state = null;
3760

@@ -41,13 +64,15 @@ class Performance
4164

4265
#[GraphQL\Field(description: 'Primary key')]
4366
#[GraphQL\Field(group: 'ExcludeCriteriaTest')]
67+
#[GraphQL\Field(group: 'IncludeCriteriaTest')]
4468
#[ORM\Id]
4569
#[ORM\Column(type: 'integer')]
4670
#[ORM\GeneratedValue(strategy: 'AUTO')]
4771
private int $id;
4872

4973
/** @var Collection<id, Recording> */
5074
#[GraphQL\Association(description: 'Recordings by artist')]
75+
#[GraphQL\Association(group: 'IncludeCriteriaTest', includeCriteria: [Filters::CONTAINS])]
5176
#[ORM\OneToMany(targetEntity: 'ApiSkeletonsTest\Doctrine\GraphQL\Entity\Recording', mappedBy: 'performance')]
5277
private Collection $recordings;
5378

test/Entity/Recording.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414
*/
1515
#[GraphQL\Entity(typeName: 'recording', description: 'Performance recordings')]
1616
#[GraphQL\Entity(typeName: 'entitytestrecording', description: 'Entity Test Recordings', group: 'entityTest')]
17+
#[GraphQL\Entity(group: 'IncludeCriteriaTest')]
1718
#[GraphQL\Entity(group: 'CustomFieldStrategyTest')]
1819
#[ORM\Entity]
1920
class Recording
2021
{
2122
#[GraphQL\Field(description: 'Source')]
2223
#[GraphQL\Field(description: 'Entity Test Source', group: 'entityTest')]
2324
#[GraphQL\Field(group: 'CustomFieldStrategyTest')]
25+
#[GraphQL\Field(group: 'IncludeCriteriaTest')]
2426
#[ORM\Column(type: 'text', nullable: false)]
2527
private string $source;
2628

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace ApiSkeletonsTest\Doctrine\GraphQL\Feature\Criteria;
6+
7+
use ApiSkeletons\Doctrine\GraphQL\Config;
8+
use ApiSkeletons\Doctrine\GraphQL\Driver;
9+
use ApiSkeletonsTest\Doctrine\GraphQL\AbstractTest;
10+
use ApiSkeletonsTest\Doctrine\GraphQL\Entity\Performance;
11+
use GraphQL\GraphQL;
12+
use GraphQL\Type\Definition\ObjectType;
13+
use GraphQL\Type\Schema;
14+
15+
class IncludeCriteriaTest extends AbstractTest
16+
{
17+
public function testExcludeCriteria(): void
18+
{
19+
$config = new Config(['group' => 'IncludeCriteriaTest']);
20+
21+
$driver = new Driver($this->getEntityManager(), $config);
22+
23+
$schema = new Schema([
24+
'query' => new ObjectType([
25+
'name' => 'query',
26+
'fields' => [
27+
'performances' => [
28+
'type' => $driver->connection($driver->type(Performance::class)),
29+
'args' => [
30+
'filter' => $driver->filter(Performance::class),
31+
'pagination' => $driver->pagination(),
32+
],
33+
'resolve' => $driver->resolve(Performance::class),
34+
],
35+
],
36+
]),
37+
]);
38+
39+
// Test entity level included filters
40+
$query = '{ performances (filter: { venue: {contains: "Fillmore" } } ) { edges { node { venue } } } }';
41+
$result = GraphQL::executeQuery($schema, $query);
42+
$data = $result->toArray()['data'];
43+
$this->assertEquals('Fillmore Auditorium', $data['performances']['edges'][0]['node']['venue']);
44+
45+
$query = '{ performances (filter: { venue: {eq: "Fillmore Auditorium" } } ) { edges { node { venue } } } }';
46+
$result = GraphQL::executeQuery($schema, $query);
47+
$data = $result->toArray()['data'];
48+
$this->assertEquals('Fillmore Auditorium', $data['performances']['edges'][0]['node']['venue']);
49+
50+
// Test entity level excluded filters
51+
$query = '{ performances (filter: { venue: {in: ["Fillmore Auditorium"] } } ) { edges { node { venue } } } }';
52+
$result = GraphQL::executeQuery($schema, $query);
53+
foreach ($result->errors as $error) {
54+
$this->assertEquals('Field "in" is not defined by type "ApiSkeletonsTest_Doctrine_GraphQL_Entity_Performance_IncludeCriteriaTest_filter_venue_filters".', $error->getMessage());
55+
}
56+
57+
// Test entity>field level included filters
58+
$query = '{ performances (filter: { city: {eq: "Salt Lake City" } } ) { edges { node { city } } } }';
59+
$result = GraphQL::executeQuery($schema, $query);
60+
$data = $result->toArray()['data'];
61+
$this->assertEquals('Salt Lake City', $data['performances']['edges'][0]['node']['city']);
62+
63+
// Test entity>field level excluded filters
64+
$query = '{ performances (filter: { city: {contains: "Salt Lake City" } } ) { edges { node { city } } } }';
65+
$result = GraphQL::executeQuery($schema, $query);
66+
foreach ($result->errors as $error) {
67+
$this->assertEquals('Field "contains" is not defined by type "ApiSkeletonsTest_Doctrine_GraphQL_Entity_Performance_IncludeCriteriaTest_filter_city_filters".', $error->getMessage());
68+
}
69+
70+
// Test entity>field level included filters excluded by field level exclude
71+
$query = '{ performances (filter: { state: { eq: "UT" } } ) { edges { node { state } } } }';
72+
$result = GraphQL::executeQuery($schema, $query);
73+
foreach ($result->errors as $error) {
74+
$this->assertEquals('Field "eq" is not defined by type "ApiSkeletonsTest_Doctrine_GraphQL_Entity_Performance_IncludeCriteriaTest_filter_state_filters". Did you mean "neq"?', $error->getMessage());
75+
}
76+
77+
// Test entity>association level included filters
78+
$query = '{
79+
performances (
80+
filter: {
81+
venue: { eq: "Delta Center" }
82+
}
83+
) {
84+
edges {
85+
node {
86+
recordings(
87+
filter: {
88+
source: { contains: "DSBD" }
89+
}
90+
) {
91+
edges {
92+
node {
93+
source
94+
}
95+
}
96+
}
97+
}
98+
}
99+
}
100+
}';
101+
$result = GraphQL::executeQuery($schema, $query);
102+
$data = $result->toArray()['data'];
103+
$this->assertEquals(
104+
'DSBD > 1C > DAT; Seeded to etree by Dan Stephens',
105+
$data['performances']['edges'][0]['node']['recordings']['edges'][0]['node']['source'],
106+
);
107+
108+
// Test entity>association level included filters
109+
$query = '{
110+
performances (
111+
filter: {
112+
venue: { eq: "Delta Center" }
113+
}
114+
) {
115+
edges {
116+
node {
117+
recordings(
118+
filter: {
119+
source: { eq: "DSBD" }
120+
}
121+
) {
122+
edges {
123+
node {
124+
source
125+
}
126+
}
127+
}
128+
}
129+
}
130+
}
131+
}';
132+
$result = GraphQL::executeQuery($schema, $query);
133+
foreach ($result->errors as $error) {
134+
$this->assertEquals('Field "eq" is not defined by type "ApiSkeletonsTest_Doctrine_GraphQL_Entity_Performance_IncludeCriteriaTest_recordings_filter_source_filters".', $error->getMessage());
135+
}
136+
}
137+
}

0 commit comments

Comments
 (0)