Skip to content

Commit 821a077

Browse files
soyukaalanpoulainvincentchalamon
authored
feat: link implementation (#4562)
* feat: link implementation * fix phpstan * fix cs * bump ci rector * fix(graphql): merge links with to class instead of from class and use context to choose the right provider * fix: last links issues * fix: openapi post identifiers * bump patch * 2 tests * fix filters * feat(graphql): use processor in write stage (wip) * feat(elasticsearch): add Elasticsearch providers (#29) * fix(graphql): fix tests related to write stage changes * fix(state): chain processor should return null when a processor is not found in process * feat: introduce delete attribute in metadata * fix: tests for elasticsearch * fix: ES ItemProvider * fix: phpunit tests * fix: remove rector.patch * fix: force rector/rector to 0.12.5 * fix: graphql arguments * Revert "GraphQL: Fix GraphQL fetching with Elasticsearch" This reverts commit a625fe7. * fix: convert ApiProperty annotations to attributes through rectorphp * fix: convert ApiFilter annotations to attributes through rectorphp Co-authored-by: Alan Poulain <[email protected]> Co-authored-by: Vincent <[email protected]>
1 parent a3b833f commit 821a077

File tree

112 files changed

+3544
-1424
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

112 files changed

+3544
-1424
lines changed

.github/workflows/ci.yml

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ jobs:
6868
- name: Update project dependencies
6969
run: composer update --no-interaction --no-progress --ansi
7070
- name: Require Symfony components
71-
run: composer require symfony/intl symfony/uid --dev --no-interaction --no-progress --ansi
71+
run: composer require symfony/uid --dev --no-interaction --no-progress --ansi
7272
- name: Install PHPUnit
7373
run: vendor/bin/simple-phpunit --version
7474
- name: Cache PHPStan results
@@ -142,7 +142,7 @@ jobs:
142142
run: composer update --no-interaction --no-progress --ansi
143143
- name: Require Symfony components
144144
if: (!startsWith(matrix.php, '7.1'))
145-
run: composer require symfony/intl symfony/uid --dev --no-interaction --no-progress --ansi
145+
run: composer require symfony/uid --dev --no-interaction --no-progress --ansi
146146
- name: Install PHPUnit
147147
run: vendor/bin/simple-phpunit --version
148148
- name: Clear test app cache
@@ -238,7 +238,7 @@ jobs:
238238
run: composer update --no-interaction --no-progress --ansi
239239
- name: Require Symfony components
240240
if: (!startsWith(matrix.php, '7.1'))
241-
run: composer require symfony/intl symfony/uid --dev --no-interaction --no-progress --ansi
241+
run: composer require symfony/uid --dev --no-interaction --no-progress --ansi
242242
- name: Install PHPUnit
243243
run: vendor/bin/simple-phpunit --version
244244
- name: Clear test app cache
@@ -358,7 +358,7 @@ jobs:
358358
- name: Update project dependencies
359359
run: composer update --no-interaction --no-progress --ansi --prefer-lowest
360360
- name: Require Symfony components
361-
run: composer require symfony/intl symfony/uid --dev --no-interaction --no-progress --ansi
361+
run: composer require symfony/uid --dev --no-interaction --no-progress --ansi
362362
- name: Clear test app cache
363363
run: tests/Fixtures/app/console cache:clear --ansi
364364
- name: Install PHPUnit
@@ -398,7 +398,7 @@ jobs:
398398
- name: Update project dependencies
399399
run: composer update --no-interaction --no-progress --ansi --prefer-lowest
400400
- name: Require Symfony components
401-
run: composer require symfony/intl symfony/uid --dev --no-interaction --no-progress --ansi
401+
run: composer require symfony/uid --dev --no-interaction --no-progress --ansi
402402
- name: Install PHPUnit
403403
run: vendor/bin/simple-phpunit --version
404404
- name: Clear test app cache
@@ -449,7 +449,7 @@ jobs:
449449
- name: Update project dependencies
450450
run: composer update --no-interaction --no-progress --ansi
451451
- name: Require Symfony components
452-
run: composer require symfony/intl symfony/uid --dev --no-interaction --no-progress --ansi
452+
run: composer require symfony/uid --dev --no-interaction --no-progress --ansi
453453
- name: Install PHPUnit
454454
run: vendor/bin/simple-phpunit --version
455455
- name: Clear test app cache
@@ -501,7 +501,7 @@ jobs:
501501
- name: Update project dependencies
502502
run: composer update --no-interaction --no-progress --ansi
503503
- name: Require Symfony components
504-
run: composer require symfony/intl symfony/uid --dev --no-interaction --no-progress --ansi
504+
run: composer require symfony/uid --dev --no-interaction --no-progress --ansi
505505
- name: Install PHPUnit
506506
run: vendor/bin/simple-phpunit --version
507507
- name: Clear test app cache
@@ -550,7 +550,7 @@ jobs:
550550
run: |
551551
composer update --no-interaction --no-progress --ansi
552552
- name: Require Symfony components
553-
run: composer require symfony/intl symfony/uid --dev --no-interaction --no-progress --ansi
553+
run: composer require symfony/uid --dev --no-interaction --no-progress --ansi
554554
- name: Install PHPUnit
555555
run: vendor/bin/simple-phpunit --version
556556
- name: Clear test app cache
@@ -640,7 +640,7 @@ jobs:
640640
- name: Update project dependencies
641641
run: composer update --no-interaction --no-progress --ansi
642642
- name: Require Symfony components
643-
run: composer require symfony/intl symfony/uid --dev --no-interaction --no-progress --ansi
643+
run: composer require symfony/uid --dev --no-interaction --no-progress --ansi
644644
- name: Install Elasticsearch-php # there is a version matrix for this package: https://packagist.org/packages/elasticsearch/elasticsearch
645645
run: composer require "elasticsearch/elasticsearch:v7.14.0" --no-interaction --no-progress --ansi
646646
- name: Install PHPUnit
@@ -684,7 +684,7 @@ jobs:
684684
- name: Update project dependencies
685685
run: composer update --no-interaction --no-progress --ansi
686686
- name: Require Symfony components
687-
run: composer require symfony/intl symfony/uid --dev --no-interaction --no-progress --ansi
687+
run: composer require symfony/uid --dev --no-interaction --no-progress --ansi
688688
- name: Install PHPUnit
689689
run: vendor/bin/simple-phpunit --version
690690
- name: Clear test app cache
@@ -734,7 +734,7 @@ jobs:
734734
- name: Update project dependencies
735735
run: composer update --no-interaction --no-progress --ansi
736736
- name: Require Symfony components
737-
run: composer require symfony/intl symfony/uid --dev --no-interaction --no-progress --ansi
737+
run: composer require symfony/uid --dev --no-interaction --no-progress --ansi
738738
- name: Flag held back Symfony packages
739739
env:
740740
symfony_version: ${{ matrix.symfony }}
@@ -796,7 +796,7 @@ jobs:
796796
- name: Update project dependencies
797797
run: composer update --no-interaction --no-progress --ansi
798798
- name: Require Symfony components
799-
run: composer require symfony/intl symfony/uid --dev --no-interaction --no-progress --ansi
799+
run: composer require symfony/uid --dev --no-interaction --no-progress --ansi
800800
- name: Flag held back Symfony packages
801801
env:
802802
symfony_version: ${{ matrix.symfony }}
@@ -846,25 +846,26 @@ jobs:
846846
path: ${{ steps.composercache.outputs.dir }}
847847
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
848848
restore-keys: ${{ runner.os }}-composer-
849+
- name: Update project dependencies
850+
run: composer update --no-interaction --no-progress --ansi
849851
- name: Require Symfony components and Rector dependencies
850-
run: composer require symfony/intl symfony/uid rector/rector --dev --no-interaction --no-progress --ansi
852+
run: composer require symfony/uid rector/rector:0.12.5 --dev --no-interaction --no-progress --ansi
851853
- name: Install PHPUnit
852854
run: vendor/bin/simple-phpunit --version
853855
- name: Clear test app cache
854856
run: rm -Rf tests/Fixtures/app/var/cache/*
855857
- name: Convert annotations to attributes
856858
run: |
857-
tests/Fixtures/app/console api:rector:upgrade tests/Fixtures/TestBundle/Document --transform-apisubresource -s
858-
tests/Fixtures/app/console api:rector:upgrade tests/Fixtures/TestBundle/Document --annotation-to-api-resource -s
859-
tests/Fixtures/app/console api:rector:upgrade tests/Fixtures/TestBundle/Entity --transform-apisubresource -s
860-
tests/Fixtures/app/console api:rector:upgrade tests/Fixtures/TestBundle/Entity --annotation-to-api-resource -s
859+
tests/Fixtures/app/console api:rector:upgrade tests/Fixtures/TestBundle/Document --transform-apisubresource -s -n
860+
tests/Fixtures/app/console api:rector:upgrade tests/Fixtures/TestBundle/Document --annotation-to-api-resource -s -n
861+
tests/Fixtures/app/console api:rector:upgrade tests/Fixtures/TestBundle/Entity --transform-apisubresource -s -n
862+
tests/Fixtures/app/console api:rector:upgrade tests/Fixtures/TestBundle/Entity --annotation-to-api-resource -s -n
861863
- name: Clear test app cache
862864
run: rm -Rf tests/Fixtures/app/var/cache/*
863865
- name: Run Behat tests
864866
run: |
865867
mkdir -p build/logs/behat
866868
vendor/bin/behat --out=std --format=progress --format=junit --out=build/logs/behat/junit --profile=default --no-interaction
867-
continue-on-error: true
868869
- name: Upload test artifacts
869870
if: always()
870871
uses: actions/upload-artifact@v1
@@ -937,7 +938,7 @@ jobs:
937938
- name: Update project dependencies
938939
run: composer update --no-interaction --no-progress --ansi
939940
- name: Require Symfony components
940-
run: composer require symfony/intl symfony/uid --dev --no-interaction --no-progress --ansi
941+
run: composer require symfony/uid --dev --no-interaction --no-progress --ansi
941942
- name: Install phpunit
942943
run: vendor/bin/simple-phpunit --version
943944
- name: Clear test app cache
@@ -985,7 +986,7 @@ jobs:
985986
- name: Update project dependencies
986987
run: composer update --no-interaction --no-progress --ansi
987988
- name: Require Symfony components
988-
run: composer require symfony/intl symfony/uid --dev --no-interaction --no-progress --ansi
989+
run: composer require symfony/uid --dev --no-interaction --no-progress --ansi
989990
- name: Install phpunit
990991
run: vendor/bin/simple-phpunit --version
991992
- name: Clear test app cache
@@ -1032,7 +1033,7 @@ jobs:
10321033
- name: Update project dependencies
10331034
run: composer update --no-interaction --no-progress --ansi
10341035
- name: Require Symfony components
1035-
run: composer require symfony/intl symfony/uid --dev --no-interaction --no-progress --ansi
1036+
run: composer require symfony/uid --dev --no-interaction --no-progress --ansi
10361037
- name: Install PHPUnit
10371038
run: vendor/bin/simple-phpunit --version
10381039
- name: Clear test app cache
@@ -1076,7 +1077,7 @@ jobs:
10761077
- name: Update project dependencies
10771078
run: composer update --no-interaction --no-progress --ansi
10781079
- name: Require Symfony components
1079-
run: composer require symfony/intl symfony/uid --dev --no-interaction --no-progress --ansi
1080+
run: composer require symfony/uid --dev --no-interaction --no-progress --ansi
10801081
- name: Install PHPUnit
10811082
run: vendor/bin/simple-phpunit --version
10821083
- name: Clear test app cache

CHANGELOG.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
* GraphQL: **BC** Fix security on association collection properties. The collection resource `item_query` security is no longer used. `ApiProperty` security can now be used to secure collection (or any other) properties. (#4143)
3333
* Deprecate `allow_plain_identifiers` option (#4167)
3434
* Exception: Add the ability to customize multiple status codes based on the validation exception (#4017)
35-
* GraphQL: Fix graphql fetching with Elasticsearch (#4217)
3635
* ApiLoader: Support `_format` resolving (#4292)
3736
* Metadata: new namespace `ApiPlatform\Metadata` instead of `ApiPlatform\Core\Metadata`, for example `ApiPlatform\Metadata\ApiResource` (#4351)
3837
* Metadata: deprecation of `ApiPlatform\Core\Annotation` (#4351)

behat.yml.dist

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ elasticsearch:
8484
contexts:
8585
- 'ApiPlatform\Core\Tests\Behat\CommandContext'
8686
- 'ApiPlatform\Core\Tests\Behat\ElasticsearchContext'
87-
- 'ApiPlatform\Core\Tests\Behat\GraphqlContext'
8887
- 'ApiPlatform\Core\Tests\Behat\JsonContext'
8988
- 'Behat\MinkExtension\Context\MinkContext'
9089
- 'behatch:context:rest'

docs/adr/0003-uri-variables.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ We will use a POPO to define URI variables, for now these options are available:
3737
uriVariables: [
3838
'companyId' => new UriVariable(
3939
targetClass: Company::class,
40-
inverseProperty: null,
40+
targetProperty: null,
4141
property: 'company'
4242
identifiers: ['id'],
4343
compositeIdentifier: true,
@@ -53,7 +53,7 @@ Where `uriVariables` keys are the URI template's variable names. Its value is a
5353

5454
- `targetClass` is the PHP FQDN of the class this value belongs to
5555
- `property` represents the property, the URI Variable is mapped to in the current class
56-
- `inverseProperty` represents the property, the URI Variable is mapped to in the related class and is not available in the current class
56+
- `targetProperty` represents the property, the URI Variable is mapped to in the related class and is not available in the current class
5757
- `identifiers` are the properties of the targetClass to which we map the URI variable
5858
- `compositeIdentifier` is used to match a single variable to multiple identifiers (`ida=1;idb=2` to `class::ida` and `class::idb`)
5959

@@ -122,7 +122,7 @@ class Company {
122122
}
123123
```
124124

125-
Note that the above is a shortcut for: `new UriVariable(targetClass: Employee::class, inverseProperty: 'company')`
125+
Note that the above is a shortcut for: `new UriVariable(targetClass: Employee::class, targetProperty: 'company')`
126126

127127
Corresponding DQL:
128128

@@ -259,7 +259,7 @@ class Employee {
259259
#[ApiResource("/employees/{employeeId}/company", uriVariables: [
260260
'employeeId' => new UriVariable(
261261
targetClass: Employee::class,
262-
inverseProperty: 'company'
262+
targetProperty: 'company'
263263
property: null,
264264
identifiers: ['id'],
265265
compositeIdentifier: true

docs/adr/0004-link.md

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Link
2+
3+
* Status: accepted
4+
* Deciders: @dunglas, @soyuka, @alanpoulain
5+
6+
Implementation: [#4536][pull/4536]
7+
8+
## Context and Problem Statement
9+
10+
The [URI Variables](0003-uri-variables.md) ADR introduces a new `UriVariable` POPO.
11+
In GraphQL, having URI variables make no sense: this object needs either an alias or needs to be named differently.
12+
13+
## Considered Options
14+
15+
* Create a `Traverser` alias for GraphQL.
16+
* Rename `UriVariable` to `Link`.
17+
18+
## Decision Outcome
19+
20+
We chose to rename `UriVariable` to `Link` in order to simplify the codebase.
21+
However the `uriVariables` parameter in the REST operations will not be renamed since it makes sense to have this name.
22+
GraphQL operations don't need to have links at the operation level, a `Link` attribute on the property will be used instead if necessary (the main use case is when a `toProperty` is necessary).
23+
24+
To follow this renaming, the properties in `Link` are also renamed:
25+
- `targetClass` becomes `fromClass`
26+
- `inverseProperty` becomes `fromProperty`
27+
- `property` becomes `toProperty`
28+
29+
New properties are also necessary:
30+
- `toClass` for GraphQL because GraphQL needs to find the right `Link`
31+
- `expandedValue` for REST in order to convert an URI variable to the corresponding route part (for instance in the case of the URI template `/questions/{questionId}/{questionAnswer}/related_questions`, the expanded value for `questionAnswer` could be `answer`)
32+
33+
### Classical Example
34+
35+
```php
36+
<?php
37+
38+
#[Query]
39+
#[Get]
40+
class Company
41+
{
42+
public $id;
43+
44+
#[ORM\OneToMany(targetEntity: Employee::class, mappedBy: 'company')]
45+
// will automatically create:
46+
#[Link(fromClass: Company::class, fromProperty: 'employees')]
47+
/** @var Employee[] */
48+
public iterable $employees;
49+
}
50+
```
51+
52+
```php
53+
<?php
54+
55+
#[Query]
56+
#[GetCollection('/companies/{companyId}/employees', uriVariables: [
57+
'companyId' => new Link(
58+
fromClass: Company::class,
59+
fromProperty: 'employees'
60+
)
61+
])]
62+
class Employee
63+
{
64+
public $id;
65+
66+
#[ORM\ManyToOne(targetEntity: Company::class, inversedBy: 'employees')]
67+
public Company $company;
68+
}
69+
```
70+
71+
The GraphQL query equivalent to a `GET` to `/companies/2/employees` can now be done:
72+
73+
```graphql
74+
{
75+
companies(id: "/companies/2") {
76+
employees {
77+
edges {
78+
node {
79+
id
80+
}
81+
}
82+
}
83+
}
84+
}
85+
```
86+
87+
### Inverted Example
88+
89+
In this example, the relation between the employee and the company is only hold by the employee.
90+
91+
```php
92+
<?php
93+
94+
#[Query]
95+
#[GetCollection('/companies/{companyId}/employees', uriVariables: [
96+
'companyId' => new Link(
97+
fromClass: Company::class,
98+
toProperty: 'company'
99+
)
100+
])]
101+
class Employee
102+
{
103+
public $id;
104+
105+
#[ORM\ManyToMany(targetEntity: Company::class)]
106+
#[ORM\JoinTable(name: 'employees_companies')]
107+
#[ORM\JoinColumn(name: 'employee_id', referencedColumnName: 'id')]
108+
#[ORM\InverseJoinColumn(name: 'company_id', referencedColumnName: 'id', unique: true)]
109+
public Company $company;
110+
}
111+
```
112+
113+
```php
114+
<?php
115+
116+
#[Query]
117+
#[Get]
118+
class Company
119+
{
120+
public $id;
121+
122+
#[Link('company')]
123+
// equivalent to:
124+
#[Link(fromClass: Company::class, toClass: Employee::class, toProperty: 'company')]
125+
/** @var Employee[] */
126+
public iterable $employees;
127+
}
128+
```
129+
130+
The GraphQL query equivalent to a `GET` to `/companies/2/employees` can now be done:
131+
132+
```graphql
133+
{
134+
companies(id: "/companies/2") {
135+
employees {
136+
edges {
137+
node {
138+
id
139+
}
140+
}
141+
}
142+
}
143+
}
144+
```
145+
146+
[pull/4536]: https://github.com/api-platform/core/pull/4536 "Link implementation"

features/doctrine/eager_loading.feature

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Feature: Eager Loading
1717
LEFT JOIN thirdLevel_a1.fourthLevel fourthLevel_a2
1818
LEFT JOIN o.relatedToDummyFriend relatedToDummyFriend_a3
1919
LEFT JOIN relatedToDummyFriend_a3.dummyFriend dummyFriend_a4
20-
WHERE o.id = :id_id
20+
WHERE o.id = :id_p1
2121
"""
2222

2323
Scenario: Eager loading for the search filter
@@ -74,7 +74,7 @@ Feature: Eager Loading
7474
FROM ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyTravel o
7575
LEFT JOIN o.car car_a1
7676
LEFT JOIN o.passenger passenger_a2
77-
WHERE o.id = :id_id
77+
WHERE o.id = :id_p1
7878
"""
7979

8080
Scenario: Eager loading for a relation with complex sub-query filter

0 commit comments

Comments
 (0)