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
14 changes: 14 additions & 0 deletions features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelationEmbedder;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\UuidIdentifierDummy;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Doctrine\Common\Persistence\ManagerRegistry;
Expand Down Expand Up @@ -251,6 +252,19 @@ public function thereIsARelationEmbedderObject()
$this->manager->flush();
}

/**
* @Given there is a Dummy Object mapped by UUID
*/
public function thereIsADummyObjectMappedByUUID()
{
$dummy = new UuidIdentifierDummy();
$dummy->setName('My Dummy');
$dummy->setUuid('41B29566-144B-11E6-A148-3E1D05DEFE78');

$this->manager->persist($dummy);
$this->manager->flush();
}

/**
* @Given there are Composite identifier objects
*/
Expand Down
77 changes: 46 additions & 31 deletions features/composite.feature
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Feature: Retrieve data with Composite identifiers

@createSchema
@dropSchema
Scenario: Get collection with composite identifiers
Scenario: Get a collection with composite identifiers
Given there are Composite identifier objects
When I send a "GET" request to "/composite_items"
Then the response status code should be 200
Expand All @@ -14,23 +14,23 @@ Feature: Retrieve data with Composite identifiers
And the JSON should be equal to:
"""
{
"@context": "/contexts/CompositeItem",
"@id": "/composite_items",
"@type": "hydra:Collection",
"hydra:member": [
{
"@id": "/composite_items/1",
"@type": "CompositeItem",
"field1": "foobar",
"compositeValues": [
"/composite_relations/1-1",
"/composite_relations/1-2",
"/composite_relations/1-3",
"/composite_relations/1-4"
]
}
],
"hydra:totalItems": 1
"@context": "/contexts/CompositeItem",
"@id": "/composite_items",
"@type": "hydra:Collection",
"hydra:member": [
{
"@id": "/composite_items/1",
"@type": "CompositeItem",
"field1": "foobar",
"compositeValues": [
"/composite_relations/compositeItem=1;compositeLabel=1",
"/composite_relations/compositeItem=1;compositeLabel=2",
"/composite_relations/compositeItem=1;compositeLabel=3",
"/composite_relations/compositeItem=1;compositeLabel=4"
]
}
],
"hydra:totalItems": 1
}
"""

Expand All @@ -45,36 +45,51 @@ Feature: Retrieve data with Composite identifiers
And the JSON should be equal to:
"""
{
"@context": "\/contexts\/CompositeRelation",
"@id": "\/composite_relations",
"@context": "/contexts/CompositeRelation",
"@id": "/composite_relations",
"@type": "hydra:Collection",
"hydra:member": [
{
"@id": "\/composite_relations\/1-1",
"@id": "/composite_relations/compositeItem=1;compositeLabel=1",
"@type": "CompositeRelation",
"id": "1-1",
"value": "somefoobardummy"
},
{
"@id": "\/composite_relations\/1-2",
"@id": "/composite_relations/compositeItem=1;compositeLabel=2",
"@type": "CompositeRelation",
"id": "1-2",
"value": "somefoobardummy"
},
{
"@id": "\/composite_relations\/1-3",
"@id": "/composite_relations/compositeItem=1;compositeLabel=3",
"@type": "CompositeRelation",
"id": "1-3",
"value": "somefoobardummy"
}
],
"hydra:totalItems": 4,
"hydra:view": {
"@id": "\/composite_relations?page=1",
"@id": "/composite_relations?page=1",
"@type": "hydra:PartialCollectionView",
"hydra:first": "\/composite_relations?page=1",
"hydra:last": "\/composite_relations?page=2",
"hydra:next": "\/composite_relations?page=2"
"hydra:first": "/composite_relations?page=1",
"hydra:last": "/composite_relations?page=2",
"hydra:next": "/composite_relations?page=2"
}
}
}
"""

@createSchema
@dropSchema
Scenario: Get the first composite relation
Given there are Composite identifier objects
When I send a "GET" request to "/composite_relations/compositeItem=1;compositeLabel=1"
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/ld+json"

@createSchema
@dropSchema
Scenario: Get first composite item
Given there are Composite identifier objects
When I send a "GET" request to "/composite_items/1"
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/ld+json"
81 changes: 81 additions & 0 deletions features/uuid.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
Feature: Using uuid identifier on resource
In order to use an hypermedia API
As a client software developer
I need to be able to user other identifier than id in resource and set it via API call on POST / PUT.

@createSchema
Scenario: Create a resource
When I send a "POST" request to "/uuid_identifier_dummies" with body:
"""
{
"name": "My Dummy",
"uuid": "41B29566-144B-11E6-A148-3E1D05DEFE78"
}
"""
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"

Scenario: Get a resource
When I send a "GET" request to "/uuid_identifier_dummies/41B29566-144B-11E6-A148-3E1D05DEFE78"
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/ld+json"
And the JSON should be equal to:
"""
{
"@context": "/contexts/UuidIdentifierDummy",
"@id": "/uuid_identifier_dummies/41B29566-144B-11E6-A148-3E1D05DEFE78",
"@type": "UuidIdentifierDummy",
"name": "My Dummy"
}
"""

Scenario: Get a collection
When I send a "GET" request to "/uuid_identifier_dummies"
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/ld+json"
And the JSON should be equal to:
"""
{
"@context": "/contexts/UuidIdentifierDummy",
"@id": "/uuid_identifier_dummies",
"@type": "hydra:Collection",
"hydra:member": [
{
"@id": "/uuid_identifier_dummies/41B29566-144B-11E6-A148-3E1D05DEFE78",
"@type": "UuidIdentifierDummy",
"name": "My Dummy"
}
],
"hydra:totalItems": 1
}
"""

Scenario: Update a resource
When I send a "PUT" request to "/uuid_identifier_dummies/41B29566-144B-11E6-A148-3E1D05DEFE78" with body:
"""
{
"name": "My Dummy modified"
}
"""
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/ld+json"
And the JSON should be equal to:
"""
{
"@context": "/contexts/UuidIdentifierDummy",
"@id": "/uuid_identifier_dummies/41B29566-144B-11E6-A148-3E1D05DEFE78",
"@type": "UuidIdentifierDummy",
"name": "My Dummy modified"
}
"""


@dropSchema
Scenario: Delete a resource
When I send a "DELETE" request to "/uuid_identifier_dummies/41B29566-144B-11E6-A148-3E1D05DEFE78"
Then the response status code should be 204
And the response should be empty
92 changes: 71 additions & 21 deletions src/Bridge/Doctrine/Orm/ItemDataProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;

/**
* Item data provider for the Doctrine ORM.
Expand Down Expand Up @@ -68,7 +69,75 @@ public function getItem(string $resourceClass, $id, string $operationName = null
throw new ResourceClassNotSupportedException();
}

$identifierValues = explode('-', $id);
$identifiers = $this->normalizeIdentifiers($id, $manager, $resourceClass);

if (!$fetchData && $manager instanceof EntityManagerInterface) {
return $manager->getReference($resourceClass, $identifiers);
}

$repository = $manager->getRepository($resourceClass);
$queryBuilder = $repository->createQueryBuilder('o');

$this->addWhereForIdentifiers($identifiers, $queryBuilder);

foreach ($this->itemExtensions as $extension) {
$extension->applyToItem($queryBuilder, $resourceClass, $identifiers, $operationName);
}

return $queryBuilder->getQuery()->getOneOrNullResult();
}

/**
* Add WHERE conditions to the query for one or more identifiers (simple or composite).
*
* @param array $identifiers
* @param QueryBuilder $queryBuilder
*/
private function addWhereForIdentifiers(array $identifiers, QueryBuilder $queryBuilder)
{
if (empty($identifiers)) {
return;
}

foreach ($identifiers as $identifier => $value) {
$placeholder = ':id_'.$identifier;
$expression = $queryBuilder->expr()->eq(
'o.'.$identifier,
$placeholder
);

$queryBuilder->andWhere($expression);

$queryBuilder->setParameter($placeholder, $value);
}
}

/**
* Transform and check the identifier, composite or not.
*
* @param $id
* @param $manager
* @param $resourceClass
*
* @return array
*/
private function normalizeIdentifiers($id, $manager, $resourceClass) : array
{
$doctrineMetadataIdentifier = $manager->getClassMetadata($resourceClass)->getIdentifier();
$identifierValues = [$id];
$identifierValuesArray = [];

if (count($doctrineMetadataIdentifier) >= 2) {
$identifierValues = explode(';', $id);
foreach ($identifierValues as $key => $value) {
$identifierValueArray = explode('=', $value);
if ($doctrineMetadataIdentifier[$key] === $identifierValueArray[0]) {
$identifierValuesArray[] = $identifierValueArray[1];
}
}
$identifierValues = $identifierValuesArray;
}

$identifiers = [];
$i = 0;

Expand All @@ -88,25 +157,6 @@ public function getItem(string $resourceClass, $id, string $operationName = null
++$i;
}

if (!$fetchData && $manager instanceof EntityManagerInterface) {
return $manager->getReference($resourceClass, $identifiers);
}

$repository = $manager->getRepository($resourceClass);
$queryBuilder = $repository->createQueryBuilder('o');

foreach ($identifiers as $propertyName => $value) {
$placeholder = 'id_'.$propertyName;

$queryBuilder
->where($queryBuilder->expr()->eq('o.'.$propertyName, ':'.$placeholder))
->setParameter($placeholder, $value);
}

foreach ($this->itemExtensions as $extension) {
$extension->applyToItem($queryBuilder, $resourceClass, $identifiers, $operationName);
}

return $queryBuilder->getQuery()->getOneOrNullResult();
return $identifiers;
}
}
15 changes: 12 additions & 3 deletions src/Bridge/Symfony/Routing/IriConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,25 @@ public function getIriFromItem($item, int $referenceType = UrlGeneratorInterface
$resourceClass = $this->getObjectClass($item);
$routeName = $this->getRouteName($resourceClass, false);

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

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

return $this->router->generate($routeName, ['id' => implode('-', $identifierValues)], $referenceType);
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);
}

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

/**
Expand Down
10 changes: 0 additions & 10 deletions tests/Fixtures/TestBundle/Entity/CompositeRelation.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,6 @@ class CompositeRelation
*/
private $compositeLabel;

/**
* Get composite id.
*
* @return string
*/
public function getId()
{
return sprintf('%s-%s', $this->compositeItem->getId(), $this->compositeLabel->getId());
}

/**
* Get value.
*
Expand Down
Loading