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
67 changes: 67 additions & 0 deletions features/relation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,24 @@ Feature: Relations support
}
"""

Scenario: Create a dummy friend
When I send a "POST" request to "/dummy_friends" with body:
"""
{"name": "Zoidberg"}
"""
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"
And the JSON should be equal to:
"""
{
"@context": "/contexts/DummyFriend",
"@id": "/dummy_friends/1",
"@type": "DummyFriend",
"name": "Zoidberg"
}
"""

Scenario: Create a related dummy
When I send a "POST" request to "/related_dummies" with body:
"""
Expand All @@ -42,12 +60,61 @@ Feature: Relations support
"name": null,
"dummyDate": null,
"thirdLevel": "/third_levels/1",
"relatedToDummyFriend": null,
"dummyBoolean": null,
"symfony": "symfony",
"age": null
}
"""

Scenario: Create a friend relationship
When I send a "POST" request to "/related_to_dummy_friends" with body:
"""
{
"name": "Friends relation",
"dummyFriend": "/dummy_friends/1",
"relatedDummy": "/related_dummies/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"
And the JSON should be equal to:
"""
{
"@context": "/contexts/RelatedToDummyFriend",
"@id": "/related_to_dummy_friends/dummyFriend=1;relatedDummy=1",
"@type": "RelatedToDummyFriend",
"name": "Friends relation",
"dummyFriend": {
"@id": "/dummy_friends/1",
"@type": "DummyFriend",
"name": "Zoidberg"
}
}
"""

Scenario: Get the relationship
When I send a "GET" request to "/related_to_dummy_friends/dummyFriend=1;relatedDummy=1"
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"
And the JSON should be equal to:
"""
{
"@context": "/contexts/RelatedToDummyFriend",
"@id": "/related_to_dummy_friends/dummyFriend=1;relatedDummy=1",
"@type": "RelatedToDummyFriend",
"name": "Friends relation",
"dummyFriend": {
"@id": "/dummy_friends/1",
"@type": "DummyFriend",
"name": "Zoidberg"
}
}
"""


Scenario: Create a dummy with relations
When I send a "POST" request to "/dummies" with body:
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ public function create(string $resourceClass, string $property, array $options =
foreach ($identifiers as $identifier) {
if ($identifier === $property) {
$propertyMetadata = $propertyMetadata->withIdentifier(true);
$propertyMetadata = $propertyMetadata->withReadable(false);
$propertyMetadata = $propertyMetadata->withWritable($doctrineClassMetadata->isIdentifierNatural());

break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@

<!-- Metadata loader -->

<service id="api_platform.doctrine.orm.metadata.property.metadata_factory" class="ApiPlatform\Core\Bridge\Doctrine\Orm\Metadata\Property\DoctrineOrmPropertyMetadataFactory" decorates="api_platform.metadata.property.metadata_factory" decoration-priority="10" public="false">
<service id="api_platform.doctrine.orm.metadata.property.metadata_factory" class="ApiPlatform\Core\Bridge\Doctrine\Orm\Metadata\Property\DoctrineOrmPropertyMetadataFactory" decorates="api_platform.metadata.property.metadata_factory" decoration-priority="40" public="false">
<argument type="service" id="doctrine" />
<argument type="service" id="api_platform.doctrine.orm.metadata.property.metadata_factory.inner" />
</service>
Expand Down
74 changes: 62 additions & 12 deletions src/Bridge/Symfony/Routing/IriConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,25 +77,75 @@ public function getIriFromItem($item, int $referenceType = UrlGeneratorInterface
$resourceClass = $this->getObjectClass($item);
$routeName = $this->getRouteName($resourceClass, false);

$identifiers = $this->generateIdentifiersUrl($this->getIdentifiersFromItem($item));

return $this->router->generate($routeName, ['id' => implode(';', $identifiers)], $referenceType);
}

/**
* Generate the identifier url.
*
* @param array $identifiers
*
* @return array
*/
public function generateIdentifiersUrl(array $identifiers) : array
{
if (1 === count($identifiers)) {
return [rawurlencode(array_values($identifiers)[0])];
}

foreach ($identifiers as $name => $value) {
$identifiers[$name] = sprintf('%s=%s', $name, $value);
}

return $identifiers;
}

/**
* Find identifiers from an Item (Object).
*
* @param object $item
*
* @throws RuntimeException
*
* @return array
*/
private function getIdentifiersFromItem($item) : array
{
$identifiers = [];
$resourceClass = $this->getObjectClass($item);

foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) {
$propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName);

if ($propertyMetadata->isIdentifier()) {
$identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName);
if (!$propertyMetadata->isIdentifier()) {
continue;
}
}

if (1 === count($identifiers)) {
$identifiers = array_map(function ($identifierValue) {
return rawurlencode($identifierValue);
}, $identifiers);
} else {
$identifiers = array_map(function ($identifierName, $identifierValue) {
return sprintf('%s=%s', $identifierName, rawurlencode($identifierValue));
}, array_keys($identifiers), $identifiers);
$identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName);

if (!is_object($identifiers[$propertyName])) {
continue;
}

$relatedResourceClass = $this->getObjectClass($identifiers[$propertyName]);
$relatedItem = $identifiers[$propertyName];

foreach ($this->propertyNameCollectionFactory->create($relatedResourceClass) as $relatedPropertyName) {
$propertyMetadata = $this->propertyMetadataFactory->create($relatedResourceClass, $relatedPropertyName);

if ($propertyMetadata->isIdentifier()) {
$identifiers[$propertyName] = $this->propertyAccessor->getValue($relatedItem, $relatedPropertyName);
}
}

if (empty($identifiers[$propertyName])) {
throw new \RuntimeException(sprintf('%s identifiers can not be found', $resourceClass));
}
}

return $this->router->generate($routeName, ['id' => implode(';', $identifiers)], $referenceType);
return $identifiers;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/JsonLd/Serializer/ItemNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ protected function getAllowedAttributes($classOrObject, array $context, $attribu
$propertyMetadata = $this->propertyMetadataFactory->create($context['resource_class'], $propertyName, $options);

if (
(isset($context['jsonld_normalize']) && !$propertyMetadata->isIdentifier() && $propertyMetadata->isReadable()) ||
(isset($context['jsonld_normalize']) && $propertyMetadata->isReadable()) ||
(isset($context['jsonld_denormalize']) && $propertyMetadata->isWritable())
) {
$allowedAttributes[] = $propertyName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ private function transformReadWrite(PropertyMetadata $propertyMetadata, string $
{
$groups = $this->getPropertySerializerGroups($resourceClass, $property);

if (false !== $propertyMetadata->isReadable()) {
if ($propertyMetadata->isIdentifier()) {
$propertyMetadata = $propertyMetadata->withReadable(null !== $normalizationGroups && !empty(array_intersect($normalizationGroups, $groups)));
} elseif (false !== $propertyMetadata->isReadable()) {
$propertyMetadata = $propertyMetadata->withReadable(null === $normalizationGroups || !empty(array_intersect($normalizationGroups, $groups)));
}
if (false !== $propertyMetadata->isWritable()) {
Expand Down
88 changes: 88 additions & 0 deletions tests/Fixtures/TestBundle/Entity/DummyFriend.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

/*
* 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\Tests\Fixtures\TestBundle\Entity;

use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;

/**
* DummyFriend.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @ApiResource()
* @ORM\Entity
*/
class DummyFriend
{
/**
* @var int The id.
*
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;

/**
* @var string The dummy name.
*
* @ORM\Column
* @Assert\NotBlank
* @ApiProperty(iri="http://schema.org/name")
* @Groups({"fakemanytomany"})
*/
private $name;

/**
* Get id.
*
* @return id.
*/
public function getId()
{
return $this->id;
}

/**
* Set id.
*
* @param id the value to set.
*/
public function setId($id)
{
$this->id = $id;
}

/**
* Get name.
*
* @return name.
*/
public function getName()
{
return $this->name;
}

/**
* Set name.
*
* @param name the value to set.
*/
public function setName($name)
{
$this->name = $name;
}
}
26 changes: 26 additions & 0 deletions tests/Fixtures/TestBundle/Entity/RelatedDummy.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ class RelatedDummy extends ParentDummy
*/
public $thirdLevel;

/**
* @ORM\OneToMany(targetEntity="RelatedToDummyFriend", cascade={"persist"}, fetch="EAGER", mappedBy="relatedDummy")
* @Groups({"fakemanytomany"})
*/
public $relatedToDummyFriend;

/**
* @var bool A dummy bool.
*
Expand Down Expand Up @@ -117,4 +123,24 @@ public function setDummyBoolean($dummyBoolean)
{
$this->dummyBoolean = $dummyBoolean;
}

/**
* Get relatedToDummyFriend.
*
* @return relatedToDummyFriend.
*/
public function getRelatedToDummyFriend()
{
return $this->relatedToDummyFriend;
}

/**
* Set relatedToDummyFriend.
*
* @param relatedToDummyFriend the value to set.
*/
public function setRelatedToDummyFriend(RelatedToDummyFriend $relatedToDummyFriend)
{
$this->relatedToDummyFriend = $relatedToDummyFriend;
}
}
Loading