Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@

declare(strict_types=1);

use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Answer;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeItem;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeLabel;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeRelation;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Container;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCarColor;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyFriend;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Node;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Question;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedToDummyFriend;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelationEmbedder;
Expand Down Expand Up @@ -418,6 +422,42 @@ public function thereIsARelatedDummyWithFriends($nb)
$relatedDummy2 = new RelatedDummy();
$relatedDummy2->setName('RelatedDummy without friends');
$this->manager->persist($relatedDummy2);
$this->manager->flush();
}

/**
* @Given there is an answer :answer to the question :question
*/
public function thereIsAnAnswerToTheQuestion($a, $q)
{
$answer = new Answer();
$answer->setContent($a);

$question = new Question();
$question->setContent($q);

$question->setAnswer($answer);

$this->manager->persist($answer);
$this->manager->persist($question);
$this->manager->flush();
}

/**
* @Given there are :nb nodes in a container :uuid
*/
public function thereAreNodesInAContainer($nb, $uuid)
{
$container = new Container();
$container->setId($uuid);
$this->manager->persist($container);

for ($i = 0; $i < $nb; ++$i) {
$node = new Node();
$node->setContainer($container);
$node->setSerial($i);
$this->manager->persist($node);
}

$this->manager->flush();
}
Expand Down
224 changes: 224 additions & 0 deletions features/main/subresource.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
Feature: Subresource support
In order to use a hypermedia API
As a client software developer
I need to be able to retrieve embedded resources only as Subresources

@createSchema
Scenario: Get subresource one to one relation
Given there is an answer "42" to the question "What's the answer to the Ultimate Question of Life, the Universe and Everything?"
When I send a "GET" request to "/questions/1/answer"
And the response status code should be 200
And the response should be in JSON
And the JSON should be equal to:
"""
{
"@context": "/contexts/Answer",
"@id": "/answers/1",
"@type": "Answer",
"id": 1,
"content": "42",
"question": "/questions/1"
}
"""

Scenario: Create a third level
When I add "Content-Type" header equal to "application/ld+json"
And I send a "POST" request to "/third_levels" with body:
"""
{"level": 3}
"""
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/ld+json; charset=utf-8"
And the JSON should be equal to:
"""
{
"@context": "/contexts/ThirdLevel",
"@id": "/third_levels/1",
"@type": "ThirdLevel",
"id": 1,
"level": 3,
"test": true
}
"""

Scenario: Create a named related dummy
When I add "Content-Type" header equal to "application/ld+json"
And I send a "POST" request to "/related_dummies" with body:
"""
{"name": "Hello", "thirdLevel": "/third_levels/1"}
"""
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/ld+json; charset=utf-8"

Scenario: Create an unnamed related dummy
When I add "Content-Type" header equal to "application/ld+json"
And I send a "POST" request to "/related_dummies" with body:
"""
{"thirdLevel": "/third_levels/1"}
"""
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/ld+json; charset=utf-8"

Scenario: Create a dummy with relations
When I add "Content-Type" header equal to "application/ld+json"
And I send a "POST" request to "/dummies" with body:
"""
{
"name": "Dummy with relations",
"relatedDummy": "http://example.com/related_dummies/1",
"relatedDummies": [
"/related_dummies/1",
"/related_dummies/2"
],
"name_converted": null
}
"""
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/ld+json; charset=utf-8"

Scenario: Get the subresource relation collection
When I send a "GET" request to "/dummies/1/related_dummies"
And the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the JSON should be equal to:
"""
{
"@context": "/contexts/RelatedDummy",
"@id": "/dummies/1/related_dummies",
"@type": "hydra:Collection",
"hydra:member": [
{
"@id": "/related_dummies/1",
"@type": "https://schema.org/Product",
"id": 1,
"name": "Hello",
"symfony": "symfony",
"dummyDate": null,
"thirdLevel": "/third_levels/1",
"relatedToDummyFriend": [],
"dummyBoolean": null,
"age": null
},
{
"@id": "/related_dummies/2",
"@type": "https://schema.org/Product",
"id": 2,
"name": null,
"symfony": "symfony",
"dummyDate": null,
"thirdLevel": "/third_levels/1",
"relatedToDummyFriend": [],
"dummyBoolean": null,
"age": null
}
],
"hydra:totalItems": 2,
"hydra:search": {
"@type": "hydra:IriTemplate",
"hydra:template": "/dummies/1/related_dummies{?relatedToDummyFriend.dummyFriend,relatedToDummyFriend.dummyFriend[],name}",
"hydra:variableRepresentation": "BasicRepresentation",
"hydra:mapping": [
{
"@type": "IriTemplateMapping",
"variable": "relatedToDummyFriend.dummyFriend",
"property": "relatedToDummyFriend.dummyFriend",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "relatedToDummyFriend.dummyFriend[]",
"property": "relatedToDummyFriend.dummyFriend",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "name",
"property": "name",
"required": false
}
]
}
}
"""

Scenario: Get filtered embedded relation collection
When I send a "GET" request to "/dummies/1/related_dummies?name=Hello"
And the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the JSON should be equal to:
"""
{
"@context": "/contexts/RelatedDummy",
"@id": "/dummies/1/related_dummies",
"@type": "hydra:Collection",
"hydra:member": [
{
"@id": "/related_dummies/1",
"@type": "https://schema.org/Product",
"id": 1,
"name": "Hello",
"symfony": "symfony",
"dummyDate": null,
"thirdLevel": "/third_levels/1",
"relatedToDummyFriend": [],
"dummyBoolean": null,
"age": null
}
],
"hydra:totalItems": 1,
"hydra:view": {
"@id": "/dummies/1/related_dummies?name=Hello",
"@type": "hydra:PartialCollectionView"
},
"hydra:search": {
"@type": "hydra:IriTemplate",
"hydra:template": "/dummies/1/related_dummies{?relatedToDummyFriend.dummyFriend,relatedToDummyFriend.dummyFriend[],name}",
"hydra:variableRepresentation": "BasicRepresentation",
"hydra:mapping": [
{
"@type": "IriTemplateMapping",
"variable": "relatedToDummyFriend.dummyFriend",
"property": "relatedToDummyFriend.dummyFriend",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "relatedToDummyFriend.dummyFriend[]",
"property": "relatedToDummyFriend.dummyFriend",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "name",
"property": "name",
"required": false
}
]
}
}
"""

@dropSchema
Scenario: Get the embedded relation collection
When I send a "GET" request to "/dummies/1/related_dummies/1/third_level"
And the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the JSON should be equal to:
"""
{
"@context": "/contexts/ThirdLevel",
"@id": "/third_levels/1",
"@type": "ThirdLevel",
"id": 1,
"level": 3,
"test": true
}
"""

1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ parameters:
- '#Parameter \#1 \$rootNode of method ApiPlatform\\Core\\Bridge\\Symfony\\Bundle\\DependencyInjection\\Configuration::[a-zA-Z0-9]+\(\) expects Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition, Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition|Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition given#'
- '#Parameter \#1 \$source of static method ApiPlatform\\Core\\Util\\RequestParser::parseRequestParams\(\) expects string, string\|resource given#'
- '#Call to an undefined method Doctrine\\Common\\Persistence\\ObjectManager::getConnection\(\)#'
- '#Method ApiPlatform\\Core\\Api\\OperationTypeDeprecationHelper::getOperationType\(\) should return string but returns string|bool|null#'
5 changes: 5 additions & 0 deletions src/Annotation/ApiProperty.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,9 @@ final class ApiProperty
* @var array
*/
public $attributes = [];

/**
* @var bool
*/
public $subresource;
}
26 changes: 26 additions & 0 deletions src/Api/IriConverterInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,30 @@ public function getIriFromItem($item, int $referenceType = UrlGeneratorInterface
* @return string
*/
public function getIriFromResourceClass(string $resourceClass, int $referenceType = UrlGeneratorInterface::ABS_PATH): string;

/**
* Gets the item IRI associated with the given resource.
*
* @param string $resourceClass
* @param array $identifiers
* @param int $referenceType
*
* @throws InvalidArgumentException
*
* @return string
*/
public function getItemIriFromResourceClass(string $resourceClass, array $identifiers, int $referenceType = UrlGeneratorInterface::ABS_PATH): string;

/**
* Gets the IRI associated with the given resource subresource.
*
* @param string $resourceClass
* @param array $identifiers
* @param int $referenceType
*
* @throws InvalidArgumentException
*
* @return string
*/
public function getSubresourceIriFromResourceClass(string $resourceClass, array $identifiers, int $referenceType = UrlGeneratorInterface::ABS_PATH): string;
}
21 changes: 21 additions & 0 deletions src/Api/OperationType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);
/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace ApiPlatform\Core\Api;

final class OperationType
{
const ITEM = 'item';
const COLLECTION = 'collection';
const SUBRESOURCE = 'subresource';
const TYPES = [self::ITEM, self::COLLECTION, self::SUBRESOURCE];
}
Loading