Skip to content

Commit

Permalink
Merge pull request #5274 from soyuka/main
Browse files Browse the repository at this point in the history
Merge 3.0
  • Loading branch information
soyuka committed Dec 16, 2022
2 parents 7f09a26 + 2dcddb4 commit ac71153
Show file tree
Hide file tree
Showing 23 changed files with 678 additions and 41 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,15 @@
# Changelog

## v3.0.7

### Bug fixes

* [27af3216f](https://github.com/api-platform/core/commit/27af3216f2beac654acb7881b52b3e2e29bf9078) fix(symfony): wire Symfony JsonEncoder if it exists (#5240)
* [31215c623](https://github.com/api-platform/core/commit/31215c62365c6b9095486c307d29837e53c0357a) ci: fix mongod startup (#5248)
* [55be4ca41](https://github.com/api-platform/core/commit/55be4ca41b6a97004d4be623d55bd5e7a3004b16) fix: get back return phpdoc on ProviderInterface
* [6d38cd941](https://github.com/api-platform/core/commit/6d38cd94140edd573ef9b09997204ef345360880) fix(metadata): include routePrefix in default operation name (#5203) (#5252)
* [b52161f](https://github.com/api-platform/core/commit/b52161f75cbfb8fd42b79db8b62e38747c84f089) perf(symfony): use default cache pool config in development environment (#5242)

## v3.0.6

### Bug fixes
Expand Down
48 changes: 48 additions & 0 deletions features/graphql/query.feature
Expand Up @@ -20,6 +20,54 @@ Feature: GraphQL query support
And the JSON node "data.dummy.name" should be equal to "Dummy #1"
And the JSON node "data.dummy.name_converted" should be equal to "Converted 1"

@createSchema
Scenario: Retrieve an item with different relations to the same resource
Given there are 2 multiRelationsDummy objects having each a manyToOneRelation, 2 manyToManyRelations and 3 oneToManyRelations
When I send the following GraphQL request:
"""
{
multiRelationsDummy(id: "/multi_relations_dummies/2") {
id
name
manyToOneRelation {
id
name
}
manyToManyRelations {
edges{
node {
id
name
}
}
}
oneToManyRelations {
edges{
node {
id
name
}
}
}
}
}
"""
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/json"
And the JSON node "data.multiRelationsDummy.id" should be equal to "/multi_relations_dummies/2"
And the JSON node "data.multiRelationsDummy.name" should be equal to "Dummy #2"
And the JSON node "data.multiRelationsDummy.manyToOneRelation.id" should not be null
And the JSON node "data.multiRelationsDummy.manyToOneRelation.name" should be equal to "RelatedManyToOneDummy #2"
And the JSON node "data.multiRelationsDummy.manyToManyRelations.edges" should have 2 element
And the JSON node "data.multiRelationsDummy.manyToManyRelations.edges[1].node.id" should not be null
And the JSON node "data.multiRelationsDummy.manyToManyRelations.edges[0].node.name" should be equal to "RelatedManyToManyDummy12"
And the JSON node "data.multiRelationsDummy.manyToManyRelations.edges[1].node.name" should be equal to "RelatedManyToManyDummy22"
And the JSON node "data.multiRelationsDummy.oneToManyRelations.edges" should have 3 element
And the JSON node "data.multiRelationsDummy.oneToManyRelations.edges[1].node.id" should not be null
And the JSON node "data.multiRelationsDummy.oneToManyRelations.edges[0].node.name" should be equal to "RelatedOneToManyDummy12"
And the JSON node "data.multiRelationsDummy.oneToManyRelations.edges[2].node.name" should be equal to "RelatedOneToManyDummy32"

@createSchema
Scenario: Retrieve a Relay Node
Given there are 2 dummy objects with relatedDummy
Expand Down
141 changes: 141 additions & 0 deletions features/hal/table_inheritance.feature
@@ -0,0 +1,141 @@
Feature: Table inheritance
In order to use the api with Doctrine table inheritance
As a client software developer
I need to be able to create resources and fetch them on the upper entity

Background:
Given I add "Accept" header equal to "application/hal+json"
And I add "Content-Type" header equal to "application/json"

@createSchema
Scenario: Create a table inherited resource
And I send a "POST" request to "/dummy_table_inheritance_children" with body:
"""
{
"name": "foo",
"nickname": "bar"
}
"""
Then the response status code should be 201
And the response should be in JSON
And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8"
And the JSON should be equal to:
"""
{
"_links": {
"self": {
"href": "/dummy_table_inheritance_children/1"
}
},
"nickname": "bar",
"id": 1,
"name": "foo"
}
"""

Scenario: Get the parent entity collection
When some dummy table inheritance data but not api resource child are created
When I send a "GET" request to "/dummy_table_inheritances"
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8"
And the JSON should be equal to:
"""
{
"_links": {
"self": {
"href": "/dummy_table_inheritances"
},
"item": [
{
"href": "/dummy_table_inheritance_children/1"
},
{
"href": "/dummy_table_inheritances/2"
}
]
},
"totalItems": 2,
"itemsPerPage": 3,
"_embedded": {
"item": [
{
"_links": {
"self": {
"href": "/dummy_table_inheritance_children/1"
}
},
"nickname": "bar",
"id": 1,
"name": "foo"
},
{
"_links": {
"self": {
"href": "/dummy_table_inheritances/2"
}
},
"id": 2,
"name": "Foobarbaz inheritance"
}
]
}
}
"""


Scenario: Get related entity with multiple inherited children types
And I send a "POST" request to "/dummy_table_inheritance_relateds" with body:
"""
{
"children": [
"/dummy_table_inheritance_children/1",
"/dummy_table_inheritances/2"
]
}
"""
Then the response status code should be 201
And the response should be in JSON
And the header "Content-Type" should be equal to "application/hal+json; charset=utf-8"
And the JSON should be equal to:
"""
{
"_links": {
"self": {
"href": "/dummy_table_inheritance_relateds/1"
},
"children": [
{
"href": "/dummy_table_inheritance_children/1"
},
{
"href": "/dummy_table_inheritances/2"
}
]
},
"_embedded": {
"children": [
{
"_links": {
"self": {
"href": "/dummy_table_inheritance_children/1"
}
},
"nickname": "bar",
"id": 1,
"name": "foo"
},
{
"_links": {
"self": {
"href": "/dummy_table_inheritances/2"
}
},
"id": 2,
"name": "Foobarbaz inheritance"
}
]
},
"id": 1
}
"""
21 changes: 14 additions & 7 deletions src/Doctrine/Common/State/LinksHandlerTrait.php
Expand Up @@ -34,14 +34,20 @@ private function getLinks(string $resourceClass, Operation $operation, array $co
return $links;
}

$newLinks = [];
$newLink = null;
$linkProperty = $context['linkProperty'] ?? null;

foreach ($links as $link) {
if ($linkClass === $link->getFromClass()) {
$newLinks[] = $link;
if ($linkClass === $link->getFromClass() && $linkProperty === $link->getFromProperty()) {
$newLink = $link;
break;
}
}

if ($newLink) {
return [$newLink];
}

// Using GraphQL, it's possible that we won't find a GraphQL Operation of the same type (e.g. it is disabled).
try {
$resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($linkClass);
Expand All @@ -62,16 +68,17 @@ private function getLinks(string $resourceClass, Operation $operation, array $co
}

foreach ($this->getOperationLinks($linkedOperation ?? null) as $link) {
if ($resourceClass === $link->getToClass()) {
$newLinks[] = $link;
if ($resourceClass === $link->getToClass() && $linkProperty === $link->getFromProperty()) {
$newLink = $link;
break;
}
}

if (!$newLinks) {
if (!$newLink) {
throw new RuntimeException(sprintf('The class "%s" cannot be retrieved from "%s".', $resourceClass, $linkClass));
}

return $newLinks;
return [$newLink];
}

private function getIdentifierValue(array &$identifiers, string $name = null): mixed
Expand Down
1 change: 1 addition & 0 deletions src/GraphQl/Resolver/Stage/ReadStage.php
Expand Up @@ -83,6 +83,7 @@ public function __invoke(?string $resourceClass, ?string $rootClass, Operation $
if (isset($source[$info->fieldName], $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY], $source[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY])) {
$uriVariables = $source[ItemNormalizer::ITEM_IDENTIFIERS_KEY];
$normalizationContext['linkClass'] = $source[ItemNormalizer::ITEM_RESOURCE_CLASS_KEY];
$normalizationContext['linkProperty'] = $info->fieldName;
}

return $this->provider->provide($operation, $uriVariables, $normalizationContext);
Expand Down
Expand Up @@ -31,7 +31,7 @@ final class ExtractorResourceMetadataCollectionFactory implements ResourceMetada
{
use OperationDefaultsTrait;

public function __construct(private readonly ResourceExtractorInterface $extractor, private readonly ?ResourceMetadataCollectionFactoryInterface $decorated = null, array $defaults = [], LoggerInterface $logger = null)
public function __construct(private readonly ResourceExtractorInterface $extractor, private readonly ?ResourceMetadataCollectionFactoryInterface $decorated = null, array $defaults = [], LoggerInterface $logger = null, private readonly bool $graphQlEnabled = false)
{
$this->logger = $logger ?? new NullLogger();
$this->defaults = $defaults;
Expand Down Expand Up @@ -85,7 +85,9 @@ private function buildResources(array $nodes, string $resourceClass): array
}
}

$resource = $this->addGraphQlOperations($node['graphQlOperations'] ?? null, $resource);
if ($this->graphQlEnabled) {
$resource = $this->addGraphQlOperations($node['graphQlOperations'] ?? null, $resource);
}

$resources[] = $this->addOperations($node['operations'] ?? null, $resource);
}
Expand Down
Expand Up @@ -68,16 +68,16 @@ private function mergeLinks(array $links, array $toMergeLinks): array
{
$classLinks = [];
foreach ($links as $link) {
$classLinks[$link->getToClass()] = $link;
$classLinks[$link->getToClass().'#'.$link->getFromProperty()] = $link;
}

foreach ($toMergeLinks as $link) {
if (isset($classLinks[$link->getToClass()])) {
$classLinks[$link->getToClass()] = $classLinks[$link->getToClass()]->withLink($link);
if (null !== $prevLink = $classLinks[$link->getToClass().'#'.$link->getFromProperty()] ?? null) {
$classLinks[$link->getToClass().'#'.$link->getFromProperty()] = $prevLink->withLink($link);

continue;
}
$classLinks[$link->getToClass()] = $link;
$classLinks[$link->getToClass().'#'.$link->getFromProperty()] = $link;
}

return array_values($classLinks);
Expand Down
24 changes: 18 additions & 6 deletions src/Metadata/Resource/Factory/OperationDefaultsTrait.php
Expand Up @@ -187,16 +187,28 @@ private function getOperationWithDefaults(ApiResource $resource, Operation $oper
$operation = $operation->withName($operation->getRouteName());
}

$operationName = $operation->getName() ?? sprintf(
'_api_%s_%s%s',
$operation->getUriTemplate() ?: $operation->getShortName(),
strtolower($operation->getMethod() ?? HttpOperation::METHOD_GET),
$operation instanceof CollectionOperationInterface ? '_collection' : '',
);
$path = ($operation->getRoutePrefix() ?? '').($operation->getUriTemplate() ?? '');
$operationName = $operation->getName() ?? $this->getDefaultOperationName($operation, $resource->getClass());

return [
$operationName,
$operation,
];
}

private function getDefaultShortname(string $resourceClass): string
{
return (false !== $pos = strrpos($resourceClass, '\\')) ? substr($resourceClass, $pos + 1) : $resourceClass;
}

private function getDefaultOperationName(HttpOperation $operation, string $resourceClass): string
{
$path = ($operation->getRoutePrefix() ?? '').($operation->getUriTemplate() ?? '');

return sprintf(
'_api_%s_%s%s',
$path ?: ($operation->getShortName() ?? $this->getDefaultShortname($resourceClass)),
strtolower($operation->getMethod() ?? HttpOperation::METHOD_GET),
$operation instanceof CollectionOperationInterface ? '_collection' : '');
}
}
Expand Up @@ -13,8 +13,6 @@

namespace ApiPlatform\Metadata\Resource\Factory;

use ApiPlatform\Metadata\CollectionOperationInterface;
use ApiPlatform\Metadata\HttpOperation;
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;

/**
Expand All @@ -24,6 +22,8 @@
*/
final class OperationNameResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
{
use OperationDefaultsTrait;

public function __construct(private readonly ?ResourceMetadataCollectionFactoryInterface $decorated = null)
{
}
Expand Down Expand Up @@ -52,8 +52,7 @@ public function create(string $resourceClass): ResourceMetadataCollection
continue;
}

$path = ($operation->getRoutePrefix() ?? '').($operation->getUriTemplate() ?? '');
$newOperationName = sprintf('_api_%s_%s%s', $path ?: ($operation->getShortName() ?? $this->getDefaultShortname($resourceClass)), strtolower($operation->getMethod() ?? HttpOperation::METHOD_GET), $operation instanceof CollectionOperationInterface ? '_collection' : '');
$newOperationName = $this->getDefaultOperationName($operation, $resourceClass);
$operations->remove($operationName)->add($newOperationName, $operation->withName($newOperationName));
}

Expand All @@ -62,9 +61,4 @@ public function create(string $resourceClass): ResourceMetadataCollection

return $resourceMetadataCollection;
}

private function getDefaultShortname(string $resourceClass): string
{
return (false !== $pos = strrpos($resourceClass, '\\')) ? substr($resourceClass, $pos + 1) : $resourceClass;
}
}

0 comments on commit ac71153

Please sign in to comment.