diff --git a/src/Laravel/Routing/IriConverter.php b/src/Laravel/Routing/IriConverter.php
index 82afad6be8a..4ee8cc128f9 100644
--- a/src/Laravel/Routing/IriConverter.php
+++ b/src/Laravel/Routing/IriConverter.php
@@ -135,6 +135,13 @@ public function getIriFromResource(object|string $resource, int $referenceType =
}
$identifiersExtractorOperation = $operation;
+
+ // The IRI of an item operation with a custom URI template points to the canonical operation declared with "itemUriTemplate"
+ if (!isset($context['item_uri_template']) && $operation instanceof HttpOperation && !$operation instanceof CollectionOperationInterface && null !== ($itemUriTemplate = $operation->getItemUriTemplate())) {
+ $operation = $this->operationMetadataFactory->create($itemUriTemplate);
+ $identifiersExtractorOperation = $operation;
+ }
+
// In symfony the operation name is the route name, try to find one if none provided
if (
!$operation->getName()
diff --git a/src/Metadata/ApiResource.php b/src/Metadata/ApiResource.php
index 5d136533d83..bf62bfd0486 100644
--- a/src/Metadata/ApiResource.php
+++ b/src/Metadata/ApiResource.php
@@ -982,6 +982,7 @@ public function __construct(
protected array $extraProperties = [],
?bool $map = null,
protected ?array $mcp = null,
+ protected ?string $itemUriTemplate = null,
) {
parent::__construct(
shortName: $shortName,
@@ -1093,6 +1094,19 @@ public function withUriTemplate(string $uriTemplate): static
return $self;
}
+ public function getItemUriTemplate(): ?string
+ {
+ return $this->itemUriTemplate;
+ }
+
+ public function withItemUriTemplate(?string $itemUriTemplate = null): static
+ {
+ $self = clone $this;
+ $self->itemUriTemplate = $itemUriTemplate;
+
+ return $self;
+ }
+
public function getTypes(): ?array
{
return $this->types;
diff --git a/src/Metadata/Extractor/XmlResourceExtractor.php b/src/Metadata/Extractor/XmlResourceExtractor.php
index d439c1e9815..128ce96b7c5 100644
--- a/src/Metadata/Extractor/XmlResourceExtractor.php
+++ b/src/Metadata/Extractor/XmlResourceExtractor.php
@@ -17,9 +17,12 @@
use ApiPlatform\Doctrine\Orm\State\Options as OrmOptions;
use ApiPlatform\Elasticsearch\State\Options as ElasticsearchOptions;
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
+use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\HeaderParameter;
+use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
+use ApiPlatform\Metadata\Put;
use ApiPlatform\Metadata\QueryParameter;
use ApiPlatform\OpenApi\Model\ExternalDocumentation;
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
@@ -64,6 +67,7 @@ protected function extractPath(string $path): void
foreach ($xml->resource as $resource) {
$base = $this->buildExtendedBase($resource);
$this->resources[$this->resolve((string) $resource['class'])][] = array_merge($base, [
+ 'itemUriTemplate' => $this->phpize($resource, 'itemUriTemplate', 'string'),
'operations' => $this->buildOperations($resource, $base),
'graphQlOperations' => $this->buildGraphQlOperations($resource, $base),
]);
@@ -411,7 +415,7 @@ private function buildOperations(\SimpleXMLElement $resource, array $root): ?arr
}
}
- if (\in_array((string) $operation['class'], [GetCollection::class, Post::class], true)) {
+ if (\in_array((string) $operation['class'], [GetCollection::class, Post::class, Get::class, Patch::class, Put::class], true)) {
$datum['itemUriTemplate'] = $this->phpize($operation, 'itemUriTemplate', 'string');
} elseif (isset($operation['itemUriTemplate'])) {
throw new InvalidArgumentException(\sprintf('"itemUriTemplate" option is not allowed on a %s operation.', $operation['class']));
diff --git a/src/Metadata/Extractor/YamlResourceExtractor.php b/src/Metadata/Extractor/YamlResourceExtractor.php
index fe21862d065..a7f2bf436b9 100644
--- a/src/Metadata/Extractor/YamlResourceExtractor.php
+++ b/src/Metadata/Extractor/YamlResourceExtractor.php
@@ -17,9 +17,12 @@
use ApiPlatform\Doctrine\Orm\State\Options as OrmOptions;
use ApiPlatform\Elasticsearch\State\Options as ElasticsearchOptions;
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
+use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\HeaderParameter;
+use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
+use ApiPlatform\Metadata\Put;
use ApiPlatform\Metadata\QueryParameter;
use ApiPlatform\OpenApi\Model\ExternalDocumentation;
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
@@ -89,6 +92,7 @@ private function buildResources(array $resourcesYaml, string $path): void
try {
$base = $this->buildExtendedBase($resourceYamlDatum);
$this->resources[$resourceName][$resourcesCount + $key] = array_merge($base, [
+ 'itemUriTemplate' => $this->phpize($resourceYamlDatum, 'itemUriTemplate', 'string'),
'operations' => $this->buildOperations($resourceYamlDatum, $base),
'graphQlOperations' => $this->buildGraphQlOperations($resourceYamlDatum, $base),
]);
@@ -349,7 +353,7 @@ private function buildOperations(array $resource, array $root): ?array
}
}
- if (\in_array((string) $class, [GetCollection::class, Post::class], true)) {
+ if (\in_array((string) $class, [GetCollection::class, Post::class, Get::class, Patch::class, Put::class], true)) {
$datum['itemUriTemplate'] = $this->phpize($operation, 'itemUriTemplate', 'string');
} elseif (isset($operation['itemUriTemplate'])) {
throw new InvalidArgumentException(\sprintf('"itemUriTemplate" option is not allowed on a %s operation.', $class));
diff --git a/src/Metadata/Extractor/schema/resources.xsd b/src/Metadata/Extractor/schema/resources.xsd
index 6019722d6bb..37b44e58dbc 100644
--- a/src/Metadata/Extractor/schema/resources.xsd
+++ b/src/Metadata/Extractor/schema/resources.xsd
@@ -22,6 +22,7 @@
+
diff --git a/src/Metadata/Get.php b/src/Metadata/Get.php
index 82a01f83dc8..28aa13e33df 100644
--- a/src/Metadata/Get.php
+++ b/src/Metadata/Get.php
@@ -105,6 +105,7 @@ public function __construct(
?bool $throwOnNotFound = null,
array $extraProperties = [],
?bool $map = null,
+ ?string $itemUriTemplate = null,
) {
parent::__construct(
uriTemplate: $uriTemplate,
@@ -189,7 +190,8 @@ class: $class,
jsonStream: $jsonStream,
throwOnNotFound: $throwOnNotFound,
extraProperties: $extraProperties,
- map: $map
+ map: $map,
+ itemUriTemplate: $itemUriTemplate
);
}
}
diff --git a/src/Metadata/GetCollection.php b/src/Metadata/GetCollection.php
index a94ae240999..afbea941717 100644
--- a/src/Metadata/GetCollection.php
+++ b/src/Metadata/GetCollection.php
@@ -104,7 +104,7 @@ public function __construct(
?bool $jsonStream = null,
array $extraProperties = [],
?bool $throwOnNotFound = null,
- private ?string $itemUriTemplate = null,
+ ?string $itemUriTemplate = null,
?bool $map = null,
) {
parent::__construct(
@@ -190,20 +190,8 @@ class: $class,
strictQueryParameterValidation: $strictQueryParameterValidation,
hideHydraOperation: $hideHydraOperation,
stateOptions: $stateOptions,
- map: $map
+ map: $map,
+ itemUriTemplate: $itemUriTemplate
);
}
-
- public function getItemUriTemplate(): ?string
- {
- return $this->itemUriTemplate;
- }
-
- public function withItemUriTemplate(string $itemUriTemplate): self
- {
- $self = clone $this;
- $self->itemUriTemplate = $itemUriTemplate;
-
- return $self;
- }
}
diff --git a/src/Metadata/HttpOperation.php b/src/Metadata/HttpOperation.php
index a8f28f22d83..402379526c1 100644
--- a/src/Metadata/HttpOperation.php
+++ b/src/Metadata/HttpOperation.php
@@ -225,6 +225,7 @@ public function __construct(
?bool $throwOnNotFound = null,
array $extraProperties = [],
?bool $map = null,
+ protected ?string $itemUriTemplate = null,
) {
$this->formats = (null === $formats || \is_array($formats)) ? $formats : [$formats];
$this->inputFormats = (null === $inputFormats || \is_array($inputFormats)) ? $inputFormats : [$inputFormats];
@@ -316,6 +317,19 @@ public function withUriTemplate(?string $uriTemplate = null): static
return $self;
}
+ public function getItemUriTemplate(): ?string
+ {
+ return $this->itemUriTemplate;
+ }
+
+ public function withItemUriTemplate(?string $itemUriTemplate = null): static
+ {
+ $self = clone $this;
+ $self->itemUriTemplate = $itemUriTemplate;
+
+ return $self;
+ }
+
public function getTypes(): ?array
{
return $this->types;
diff --git a/src/Metadata/Patch.php b/src/Metadata/Patch.php
index 100ac370e7a..356ca090a7a 100644
--- a/src/Metadata/Patch.php
+++ b/src/Metadata/Patch.php
@@ -105,6 +105,7 @@ public function __construct(
?bool $throwOnNotFound = null,
array $extraProperties = [],
?bool $map = null,
+ ?string $itemUriTemplate = null,
) {
parent::__construct(
method: 'PATCH',
@@ -190,7 +191,8 @@ class: $class,
jsonStream: $jsonStream,
throwOnNotFound: $throwOnNotFound,
extraProperties: $extraProperties,
- map: $map
+ map: $map,
+ itemUriTemplate: $itemUriTemplate
);
}
}
diff --git a/src/Metadata/Post.php b/src/Metadata/Post.php
index 61a4a059c7c..0dd3ccee3fd 100644
--- a/src/Metadata/Post.php
+++ b/src/Metadata/Post.php
@@ -102,7 +102,7 @@ public function __construct(
?bool $jsonStream = null,
?bool $throwOnNotFound = null,
array $extraProperties = [],
- private ?string $itemUriTemplate = null,
+ ?string $itemUriTemplate = null,
?bool $strictQueryParameterValidation = null,
?bool $hideHydraOperation = null,
?bool $map = null,
@@ -191,20 +191,8 @@ class: $class,
jsonStream: $jsonStream,
throwOnNotFound: $throwOnNotFound,
extraProperties: $extraProperties,
- map: $map
+ map: $map,
+ itemUriTemplate: $itemUriTemplate
);
}
-
- public function getItemUriTemplate(): ?string
- {
- return $this->itemUriTemplate;
- }
-
- public function withItemUriTemplate(string $itemUriTemplate): self
- {
- $self = clone $this;
- $self->itemUriTemplate = $itemUriTemplate;
-
- return $self;
- }
}
diff --git a/src/Metadata/Put.php b/src/Metadata/Put.php
index 87529e95879..ff1dbab7f25 100644
--- a/src/Metadata/Put.php
+++ b/src/Metadata/Put.php
@@ -106,6 +106,7 @@ public function __construct(
?bool $hideHydraOperation = null,
private ?bool $allowCreate = null,
?bool $map = null,
+ ?string $itemUriTemplate = null,
) {
parent::__construct(
method: 'PUT',
@@ -191,7 +192,8 @@ class: $class,
jsonStream: $jsonStream,
throwOnNotFound: $throwOnNotFound,
extraProperties: $extraProperties,
- map: $map
+ map: $map,
+ itemUriTemplate: $itemUriTemplate
);
}
diff --git a/src/Metadata/Tests/Extractor/Adapter/XmlResourceAdapter.php b/src/Metadata/Tests/Extractor/Adapter/XmlResourceAdapter.php
index 4bebfa435e7..2c131457231 100644
--- a/src/Metadata/Tests/Extractor/Adapter/XmlResourceAdapter.php
+++ b/src/Metadata/Tests/Extractor/Adapter/XmlResourceAdapter.php
@@ -24,6 +24,7 @@ final class XmlResourceAdapter implements ResourceAdapterInterface
{
private const ATTRIBUTES = [
'uriTemplate',
+ 'itemUriTemplate',
'shortName',
'description',
'routePrefix',
diff --git a/src/Metadata/Tests/Extractor/Adapter/resources.xml b/src/Metadata/Tests/Extractor/Adapter/resources.xml
index 15883953196..c4ca0d619c6 100644
--- a/src/Metadata/Tests/Extractor/Adapter/resources.xml
+++ b/src/Metadata/Tests/Extractor/Adapter/resources.xml
@@ -1,3 +1,3 @@
-
+
diff --git a/src/Metadata/Tests/Extractor/Adapter/resources.yaml b/src/Metadata/Tests/Extractor/Adapter/resources.yaml
index fe1595bf154..2a4a3eb16f4 100644
--- a/src/Metadata/Tests/Extractor/Adapter/resources.yaml
+++ b/src/Metadata/Tests/Extractor/Adapter/resources.yaml
@@ -350,3 +350,4 @@ resources:
'Lorem ipsum': 'Dolor sit amet'
map: null
mcp: null
+ itemUriTemplate: '/users/{userId}/comments/{commentId}{._format}'
diff --git a/src/Metadata/Tests/Extractor/ResourceMetadataCompatibilityTest.php b/src/Metadata/Tests/Extractor/ResourceMetadataCompatibilityTest.php
index 0accd28d7ca..88449583993 100644
--- a/src/Metadata/Tests/Extractor/ResourceMetadataCompatibilityTest.php
+++ b/src/Metadata/Tests/Extractor/ResourceMetadataCompatibilityTest.php
@@ -62,6 +62,7 @@ final class ResourceMetadataCompatibilityTest extends TestCase
null,
[
'uriTemplate' => '/users/{userId}/comments',
+ 'itemUriTemplate' => '/users/{userId}/comments/{commentId}{._format}',
'shortName' => self::SHORT_NAME,
'description' => 'A list of Comments from User',
'routePrefix' => '/api',
diff --git a/src/Metadata/Tests/Extractor/XmlExtractorTest.php b/src/Metadata/Tests/Extractor/XmlExtractorTest.php
index 7b3f496d04e..2ca552404eb 100644
--- a/src/Metadata/Tests/Extractor/XmlExtractorTest.php
+++ b/src/Metadata/Tests/Extractor/XmlExtractorTest.php
@@ -111,6 +111,7 @@ public function testValidXML(): void
'jsonldContext' => null,
'throwOnNotFound' => null,
+ 'itemUriTemplate' => null,
],
[
'uriTemplate' => '/users/{author}/comments{._format}',
@@ -291,6 +292,7 @@ public function testValidXML(): void
'jsonldContext' => null,
'throwOnNotFound' => null,
+ 'itemUriTemplate' => null,
],
[
'name' => null,
@@ -408,6 +410,7 @@ public function testValidXML(): void
'jsonldContext' => null,
'throwOnNotFound' => null,
+ 'itemUriTemplate' => null,
],
],
'graphQlOperations' => null,
@@ -424,6 +427,7 @@ public function testValidXML(): void
'jsonldContext' => null,
'throwOnNotFound' => null,
+ 'itemUriTemplate' => null,
],
],
], $extractor->getResources());
diff --git a/src/Metadata/Tests/Extractor/YamlExtractorTest.php b/src/Metadata/Tests/Extractor/YamlExtractorTest.php
index 3ff86c07f4b..a88bf0c43a3 100644
--- a/src/Metadata/Tests/Extractor/YamlExtractorTest.php
+++ b/src/Metadata/Tests/Extractor/YamlExtractorTest.php
@@ -108,6 +108,7 @@ public function testValidYaml(): void
'jsonldContext' => null,
'throwOnNotFound' => null,
+ 'itemUriTemplate' => null,
],
],
Program::class => [
@@ -184,6 +185,7 @@ public function testValidYaml(): void
'jsonldContext' => null,
'throwOnNotFound' => null,
+ 'itemUriTemplate' => null,
],
[
'uriTemplate' => '/users/{author}/programs{._format}',
@@ -331,6 +333,7 @@ public function testValidYaml(): void
'jsonldContext' => null,
'throwOnNotFound' => null,
+ 'itemUriTemplate' => null,
],
[
'name' => null,
@@ -421,6 +424,7 @@ public function testValidYaml(): void
'jsonldContext' => null,
'throwOnNotFound' => null,
+ 'itemUriTemplate' => null,
],
],
'graphQlOperations' => null,
@@ -437,6 +441,7 @@ public function testValidYaml(): void
'jsonldContext' => null,
'throwOnNotFound' => null,
+ 'itemUriTemplate' => null,
],
],
SingleFileConfigDummy::class => [
@@ -513,6 +518,7 @@ public function testValidYaml(): void
'jsonldContext' => null,
'throwOnNotFound' => null,
+ 'itemUriTemplate' => null,
],
],
], $extractor->getResources());
diff --git a/src/Symfony/Routing/IriConverter.php b/src/Symfony/Routing/IriConverter.php
index eb5ce19576f..ad772184c2c 100644
--- a/src/Symfony/Routing/IriConverter.php
+++ b/src/Symfony/Routing/IriConverter.php
@@ -156,6 +156,13 @@ public function getIriFromResource(object|string $resource, int $referenceType =
}
$identifiersExtractorOperation = $operation;
+
+ // The IRI of an item operation with a custom URI template points to the canonical operation declared with "itemUriTemplate"
+ if (!isset($context['item_uri_template']) && $this->operationMetadataFactory && $operation instanceof HttpOperation && !$operation instanceof CollectionOperationInterface && null !== ($itemUriTemplate = $operation->getItemUriTemplate())) {
+ $operation = $this->operationMetadataFactory->create($itemUriTemplate);
+ $identifiersExtractorOperation = $operation;
+ }
+
// In symfony the operation name is the route name, try to find one if none provided
if (
!$operation->getName()
diff --git a/tests/Fixtures/TestBundle/Entity/CanonicalIriEntity.php b/tests/Fixtures/TestBundle/Entity/CanonicalIriEntity.php
new file mode 100644
index 00000000000..1b1d09d6552
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Entity/CanonicalIriEntity.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
+
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\Patch;
+use Doctrine\ORM\Mapping\Column;
+use Doctrine\ORM\Mapping\Entity;
+use Doctrine\ORM\Mapping\Id;
+
+#[ApiResource(
+ itemUriTemplate: '/canonical_iri_entities/{id}{._format}',
+ operations: [
+ new Get(uriTemplate: '/canonical_iri_entities/{id}{._format}'),
+ new Patch(uriTemplate: '/canonical_iri_entities/{id}/rename{._format}', read: true),
+ // the operation-level template has precedence over the resource-level one
+ new Patch(
+ uriTemplate: '/canonical_iri_entities/{id}/override{._format}',
+ itemUriTemplate: '/canonical_iri_entities/{id}/rename{._format}',
+ read: true,
+ name: 'patch_override',
+ ),
+ ],
+)]
+#[Entity]
+class CanonicalIriEntity
+{
+ #[Id]
+ #[Column]
+ public ?int $id = null;
+
+ #[Column]
+ public string $name = '';
+}
diff --git a/tests/Functional/CanonicalIriTest.php b/tests/Functional/CanonicalIriTest.php
new file mode 100644
index 00000000000..9a0d7e08bda
--- /dev/null
+++ b/tests/Functional/CanonicalIriTest.php
@@ -0,0 +1,87 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Tests\Functional;
+
+use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
+use ApiPlatform\Tests\Fixtures\TestBundle\Entity\CanonicalIriEntity;
+use ApiPlatform\Tests\RecreateSchemaTrait;
+use ApiPlatform\Tests\SetupClassResourcesTrait;
+
+final class CanonicalIriTest extends ApiTestCase
+{
+ use RecreateSchemaTrait;
+ use SetupClassResourcesTrait;
+
+ protected static ?bool $alwaysBootKernel = false;
+
+ /**
+ * @return class-string[]
+ */
+ public static function getResources(): array
+ {
+ return [CanonicalIriEntity::class];
+ }
+
+ public function testItemOperationWithCustomUriTemplateUsesTheResourceItemUriTemplate(): void
+ {
+ if ($this->isMongoDB()) {
+ $this->markTestSkipped();
+ }
+
+ $this->recreateSchema([CanonicalIriEntity::class]);
+ $this->createEntity();
+
+ self::createClient()->request('PATCH', '/canonical_iri_entities/1/rename', [
+ 'headers' => ['Content-Type' => 'application/merge-patch+json'],
+ 'json' => ['name' => 'renamed'],
+ ]);
+
+ $this->assertResponseStatusCodeSame(200);
+ $this->assertJsonContains([
+ '@id' => '/canonical_iri_entities/1',
+ 'name' => 'renamed',
+ ]);
+ }
+
+ public function testOperationItemUriTemplateHasPrecedenceOverTheResourceOne(): void
+ {
+ if ($this->isMongoDB()) {
+ $this->markTestSkipped();
+ }
+
+ $this->recreateSchema([CanonicalIriEntity::class]);
+ $this->createEntity();
+
+ self::createClient()->request('PATCH', '/canonical_iri_entities/1/override', [
+ 'headers' => ['Content-Type' => 'application/merge-patch+json'],
+ 'json' => ['name' => 'renamed again'],
+ ]);
+
+ $this->assertResponseStatusCodeSame(200);
+ $this->assertJsonContains([
+ '@id' => '/canonical_iri_entities/1/rename',
+ 'name' => 'renamed again',
+ ]);
+ }
+
+ private function createEntity(): void
+ {
+ $manager = $this->getManager();
+ $entity = new CanonicalIriEntity();
+ $entity->id = 1;
+ $entity->name = 'initial';
+ $manager->persist($entity);
+ $manager->flush();
+ }
+}
diff --git a/tests/Symfony/Routing/IriConverterTest.php b/tests/Symfony/Routing/IriConverterTest.php
index 0e05743a303..a5eecf4280b 100644
--- a/tests/Symfony/Routing/IriConverterTest.php
+++ b/tests/Symfony/Routing/IriConverterTest.php
@@ -323,6 +323,28 @@ private function getResourceClassResolver()
return $resourceClassResolver->reveal();
}
+ public function testGetIriFromItemOperationWithItemUriTemplate(): void
+ {
+ $item = new Dummy();
+ $item->setId(1);
+
+ // e.g. a PATCH operation with a custom URI template, whose IRI must point to the canonical operation
+ $operation = (new \ApiPlatform\Metadata\Patch())->withName('patch_custom')->withItemUriTemplate('/dummies/{id}{._format}');
+ $canonicalOperation = (new Get())->withName('canonical_get')->withUriTemplate('/dummies/{id}{._format}');
+
+ $routerProphecy = $this->prophesize(RouterInterface::class);
+ $routerProphecy->generate('canonical_get', ['id' => 1], UrlGeneratorInterface::ABS_PATH)->shouldBeCalled()->willReturn('/dummies/1');
+
+ $identifiersExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class);
+ $identifiersExtractorProphecy->getIdentifiersFromItem($item, $canonicalOperation, Argument::any())->shouldBeCalled()->willReturn(['id' => 1]);
+
+ $operationMetadataFactoryProphecy = $this->prophesize(OperationMetadataFactoryInterface::class);
+ $operationMetadataFactoryProphecy->create('/dummies/{id}{._format}')->shouldBeCalled()->willReturn($canonicalOperation);
+
+ $iriConverter = $this->getIriConverter(null, $routerProphecy, $identifiersExtractorProphecy, null, null, null, $operationMetadataFactoryProphecy);
+ $this->assertSame('/dummies/1', $iriConverter->getIriFromResource($item, UrlGeneratorInterface::ABS_PATH, $operation));
+ }
+
private function getIriConverter(?ObjectProphecy $stateProviderProphecy = null, ?ObjectProphecy $routerProphecy = null, ?ObjectProphecy $identifiersExtractorProphecy = null, $resourceMetadataCollectionFactoryProphecy = null, $uriVariablesConverter = null, $decorated = null, ?ObjectProphecy $operationMetadataFactory = null): IriConverter
{
if (!$stateProviderProphecy) {