Skip to content

Commit 4727a87

Browse files
authored
Merge pull request #1803 from soyuka/merge-2.2
Merge 2.2
2 parents d2b657e + c887ac7 commit 4727a87

File tree

16 files changed

+441
-92
lines changed

16 files changed

+441
-92
lines changed

features/graphql/mutation.feature

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,45 @@ Feature: GraphQL mutation support
221221
And the JSON node "data.updateCompositeRelation.value" should be equal to "Modified value."
222222
And the JSON node "data.updateCompositeRelation.clientMutationId" should be equal to "myId"
223223

224+
Scenario: Create an item with a custom UUID
225+
When I send the following GraphQL request:
226+
"""
227+
mutation {
228+
createWritableId(input: {_id: "c6b722fe-0331-48c4-a214-f81f9f1ca082", name: "Foo", clientMutationId: "m"}) {
229+
id
230+
_id
231+
name
232+
clientMutationId
233+
}
234+
}
235+
"""
236+
And the response should be in JSON
237+
And the header "Content-Type" should be equal to "application/json"
238+
And the JSON node "data.createWritableId.id" should be equal to "/writable_ids/c6b722fe-0331-48c4-a214-f81f9f1ca082"
239+
And the JSON node "data.createWritableId._id" should be equal to "c6b722fe-0331-48c4-a214-f81f9f1ca082"
240+
And the JSON node "data.createWritableId.name" should be equal to "Foo"
241+
And the JSON node "data.createWritableId.clientMutationId" should be equal to "m"
242+
243+
Scenario: Update an item with a custom UUID
244+
When I send the following GraphQL request:
245+
"""
246+
mutation {
247+
updateWritableId(input: {id: "/writable_ids/c6b722fe-0331-48c4-a214-f81f9f1ca082", _id: "f8a708b2-310f-416c-9aef-b1b5719dfa47", name: "Foo", clientMutationId: "m"}) {
248+
id
249+
_id
250+
name
251+
clientMutationId
252+
}
253+
}
254+
"""
255+
And the response should be in JSON
256+
And the header "Content-Type" should be equal to "application/json"
257+
And the JSON node "data.updateWritableId.id" should be equal to "/writable_ids/f8a708b2-310f-416c-9aef-b1b5719dfa47"
258+
And the JSON node "data.updateWritableId._id" should be equal to "f8a708b2-310f-416c-9aef-b1b5719dfa47"
259+
And the JSON node "data.updateWritableId.name" should be equal to "Foo"
260+
And the JSON node "data.updateWritableId.clientMutationId" should be equal to "m"
261+
262+
@dropSchema
224263
Scenario: Trigger a validation error
225264
When I send the following GraphQL request:
226265
"""

src/Api/CachedIdentifiersExtractor.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,20 @@ final class CachedIdentifiersExtractor implements IdentifiersExtractorInterface
3333
private $cacheItemPool;
3434
private $propertyAccessor;
3535
private $decorated;
36+
private $resourceClassResolver;
3637
private $localCache = [];
3738
private $localResourceCache = [];
3839

39-
public function __construct(CacheItemPoolInterface $cacheItemPool, IdentifiersExtractorInterface $decorated, PropertyAccessorInterface $propertyAccessor = null)
40+
public function __construct(CacheItemPoolInterface $cacheItemPool, IdentifiersExtractorInterface $decorated, PropertyAccessorInterface $propertyAccessor = null, ResourceClassResolverInterface $resourceClassResolver = null)
4041
{
4142
$this->cacheItemPool = $cacheItemPool;
4243
$this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor();
4344
$this->decorated = $decorated;
45+
$this->resourceClassResolver = $resourceClassResolver;
46+
47+
if (null === $this->resourceClassResolver) {
48+
@trigger_error(sprintf('Not injecting %s in the CachedIdentifiersExtractor might introduce cache issues with object identifiers.', ResourceClassResolverInterface::class), E_USER_DEPRECATED);
49+
}
4450
}
4551

4652
/**
@@ -77,6 +83,11 @@ public function getIdentifiersFromItem($item): array
7783
}
7884

7985
$relatedResourceClass = $this->getObjectClass($identifiers[$propertyName]);
86+
87+
if (null !== $this->resourceClassResolver && !$this->resourceClassResolver->isResourceClass($relatedResourceClass)) {
88+
continue;
89+
}
90+
8091
if (!$relatedIdentifiers = $this->localCache[$relatedResourceClass] ?? false) {
8192
$relatedCacheKey = self::CACHE_KEY_PREFIX.md5($relatedResourceClass);
8293
try {

src/Api/IdentifiersExtractor.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,18 @@ final class IdentifiersExtractor implements IdentifiersExtractorInterface
3232
private $propertyNameCollectionFactory;
3333
private $propertyMetadataFactory;
3434
private $propertyAccessor;
35+
private $resourceClassResolver;
3536

36-
public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, PropertyAccessorInterface $propertyAccessor = null)
37+
public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, PropertyAccessorInterface $propertyAccessor = null, ResourceClassResolverInterface $resourceClassResolver = null)
3738
{
3839
$this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
3940
$this->propertyMetadataFactory = $propertyMetadataFactory;
4041
$this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor();
42+
$this->resourceClassResolver = $resourceClassResolver;
43+
44+
if (null === $this->resourceClassResolver) {
45+
@trigger_error(sprintf('Not injecting %s in the CachedIdentifiersExtractor might introduce cache issues with object identifiers.', ResourceClassResolverInterface::class), E_USER_DEPRECATED);
46+
}
4147
}
4248

4349
/**
@@ -70,16 +76,17 @@ public function getIdentifiersFromItem($item): array
7076
}
7177

7278
$identifier = $identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName);
79+
7380
if (!\is_object($identifier)) {
7481
continue;
7582
}
7683

77-
if (method_exists($identifier, '__toString')) {
78-
$identifiers[$propertyName] = (string) $identifier;
84+
$relatedResourceClass = $this->getObjectClass($identifier);
85+
86+
if (null !== $this->resourceClassResolver && !$this->resourceClassResolver->isResourceClass($relatedResourceClass)) {
7987
continue;
8088
}
8189

82-
$relatedResourceClass = $this->getObjectClass($identifier);
8390
$relatedItem = $identifier;
8491
unset($identifiers[$propertyName]);
8592
foreach ($this->propertyNameCollectionFactory->create($relatedResourceClass) as $relatedPropertyName) {

src/Api/ResourceClassResolver.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ final class ResourceClassResolver implements ResourceClassResolverInterface
2828
use ClassInfoTrait;
2929

3030
private $resourceNameCollectionFactory;
31+
private $localIsResourceClassCache = [];
3132

3233
public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory)
3334
{
@@ -68,12 +69,16 @@ public function getResourceClass($value, string $resourceClass = null, bool $str
6869
*/
6970
public function isResourceClass(string $type): bool
7071
{
72+
if (isset($this->localIsResourceClassCache[$type])) {
73+
return $this->localIsResourceClassCache[$type];
74+
}
75+
7176
foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) {
7277
if ($type === $resourceClass) {
73-
return true;
78+
return $this->localIsResourceClassCache[$type] = true;
7479
}
7580
}
7681

77-
return false;
82+
return $this->localIsResourceClassCache[$type] = false;
7883
}
7984
}

src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
use Symfony\Component\DependencyInjection\ContainerBuilder;
3434
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
3535
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
36+
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
3637
use Symfony\Component\Finder\Finder;
3738
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
3839
use Symfony\Component\Validator\Validator\ValidatorInterface;
@@ -115,6 +116,9 @@ public function load(array $configs, ContainerBuilder $container)
115116

116117
$bundles = $container->getParameter('kernel.bundles');
117118
if (isset($bundles['SecurityBundle'])) {
119+
if (class_exists(ExpressionLanguage::class)) {
120+
$loader->load('security_expression_language.xml');
121+
}
118122
$loader->load('security.xml');
119123
}
120124

src/Bridge/Symfony/Bundle/Resources/config/api.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,12 +217,14 @@
217217
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
218218
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
219219
<argument type="service" id="api_platform.property_accessor" />
220+
<argument type="service" id="api_platform.resource_class_resolver" />
220221
</service>
221222

222223
<service id="api_platform.identifiers_extractor.cached" class="ApiPlatform\Core\Api\CachedIdentifiersExtractor" decorates="api_platform.identifiers_extractor" public="false">
223224
<argument type="service" id="api_platform.cache.identifiers_extractor" />
224225
<argument type="service" id="api_platform.identifiers_extractor.cached.inner" />
225226
<argument type="service" id="api_platform.property_accessor" />
227+
<argument type="service" id="api_platform.resource_class_resolver" />
226228
</service>
227229

228230
<!-- Subresources -->

src/Bridge/Symfony/Bundle/Resources/config/security.xml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
66

77
<services>
8-
<service id="api_platform.security.expression_language" class="ApiPlatform\Core\Security\ExpressionLanguage" public="false" />
9-
108
<service id="api_platform.security.resource_access_checker" class="ApiPlatform\Core\Security\ResourceAccessChecker" public="false">
119
<argument type="service" id="api_platform.security.expression_language" on-invalid="null" />
1210
<argument type="service" id="security.authentication.trust_resolver" on-invalid="null" />
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
6+
<services>
7+
<service id="api_platform.security.expression_language" class="ApiPlatform\Core\Security\ExpressionLanguage" public="false" />
8+
</services>
9+
</container>

src/DataProvider/ChainSubresourceDataProvider.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,19 @@ public function __construct(array $dataProviders)
3737
*/
3838
public function getSubresource(string $resourceClass, array $identifiers, array $context, string $operationName = null)
3939
{
40-
foreach ($this->dataProviders as $dataProviders) {
40+
foreach ($this->dataProviders as $dataProvider) {
4141
try {
42-
return $dataProviders->getSubresource($resourceClass, $identifiers, $context, $operationName);
42+
if ($dataProvider instanceof RestrictedDataProviderInterface && !$dataProvider->supports($resourceClass, $operationName, $context)) {
43+
continue;
44+
}
45+
46+
return $dataProvider->getSubresource($resourceClass, $identifiers, $context, $operationName);
4347
} catch (ResourceClassNotSupportedException $e) {
48+
@trigger_error(sprintf('Throwing a "%s" in a data provider is deprecated in favor of implementing "%s"', ResourceClassNotSupportedException::class, RestrictedDataProviderInterface::class), E_USER_DEPRECATED);
4449
continue;
4550
}
4651
}
4752

48-
return null;
53+
return ($context['collection'] ?? false) ? [] : null;
4954
}
5055
}

src/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ public function __invoke(string $resourceClass = null, string $rootClass = null,
8989
switch ($operationName) {
9090
case 'create':
9191
case 'update':
92-
unset($args['input']['id']);
9392
$context = null === $item ? ['resource_class' => $resourceClass] : ['resource_class' => $resourceClass, 'object_to_populate' => $item];
9493
$item = $this->normalizer->denormalize($args['input'], $resourceClass, ItemNormalizer::FORMAT, $context);
9594
$this->validate($item, $info, $resourceMetadata, $operationName);

0 commit comments

Comments
 (0)