Skip to content

fix #1479 allow to override SubresourceOperations through metadata #1483

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
5 changes: 5 additions & 0 deletions src/Annotation/ApiResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ final class ApiResource
*/
public $collectionOperations;

/**
* @var array
*/
public $subresourceOperations;

/**
* @var array
*/
Expand Down
11 changes: 6 additions & 5 deletions src/Bridge/Symfony/Routing/ApiLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,13 @@ public function load($data, $type = null): RouteCollection
'collection' => $operation['collection'],
'operationId' => $operationId,
],
],
] + $operation['defaults'] ?? [],
$operation['requirements'] ?? [],
[],
'',
[],
['GET']
$operation['options'] ?? [],
$operation['host'] ?? '',
$operation['schemes'] ?? [],
['GET'],
$operation['condition'] ?? ''
));
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Metadata/Extractor/XmlExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ protected function extractPath(string $path)
'iri' => $this->phpize($resource, 'iri', 'string'),
'itemOperations' => $this->getOperations($resource, 'itemOperation'),
'collectionOperations' => $this->getOperations($resource, 'collectionOperation'),
'subresourceOperations' => $this->getOperations($resource, 'subresourceOperation'),
'attributes' => $this->getAttributes($resource, 'attribute') ?: null,
'properties' => $this->getProperties($resource) ?: null,
];
Expand Down
1 change: 1 addition & 0 deletions src/Metadata/Extractor/YamlExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ private function extractResources(array $resourcesYaml, string $path)
'iri' => $this->phpize($resourceYaml, 'iri', 'string'),
'itemOperations' => $resourceYaml['itemOperations'] ?? null,
'collectionOperations' => $resourceYaml['collectionOperations'] ?? null,
'subresourceOperations' => $resourceYaml['subresourceOperations'] ?? null,
'attributes' => $resourceYaml['attributes'] ?? null,
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,13 @@ private function createMetadata(ApiResource $annotation, ResourceMetadata $paren
$annotation->iri,
$annotation->itemOperations,
$annotation->collectionOperations,
$annotation->attributes
$annotation->attributes,
$annotation->subresourceOperations
);
}

$resourceMetadata = $parentResourceMetadata;
foreach (['shortName', 'description', 'iri', 'itemOperations', 'collectionOperations', 'attributes'] as $property) {
foreach (['shortName', 'description', 'iri', 'itemOperations', 'collectionOperations', 'subresourceOperations', 'attributes'] as $property) {
$resourceMetadata = $this->createWith($resourceMetadata, $property, $annotation->$property);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ private function handleNotFound(ResourceMetadata $parentPropertyMetadata = null,
*/
private function update(ResourceMetadata $resourceMetadata, array $metadata): ResourceMetadata
{
foreach (['shortName', 'description', 'iri', 'itemOperations', 'collectionOperations', 'attributes'] as $property) {
foreach (['shortName', 'description', 'iri', 'itemOperations', 'collectionOperations', 'subresourceOperations', 'attributes'] as $property) {
if (null === $metadata[$property] || null !== $resourceMetadata->{'get'.ucfirst($property)}()) {
continue;
}
Expand Down
80 changes: 40 additions & 40 deletions src/Metadata/Resource/ResourceMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,17 @@ final class ResourceMetadata
private $iri;
private $itemOperations;
private $collectionOperations;
private $subresourceOperations;
private $attributes;

public function __construct(string $shortName = null, string $description = null, string $iri = null, array $itemOperations = null, array $collectionOperations = null, array $attributes = null)
public function __construct(string $shortName = null, string $description = null, string $iri = null, array $itemOperations = null, array $collectionOperations = null, array $attributes = null, array $subresourceOperations = null)
{
$this->shortName = $shortName;
$this->description = $description;
$this->iri = $iri;
$this->itemOperations = $itemOperations;
$this->collectionOperations = $collectionOperations;
$this->subresourceOperations = $subresourceOperations;
$this->attributes = $attributes;
}

Expand All @@ -49,10 +51,6 @@ public function getShortName()

/**
* Returns a new instance with the given short name.
*
* @param string $shortName
*
* @return self
*/
public function withShortName(string $shortName): self
{
Expand All @@ -74,10 +72,6 @@ public function getDescription()

/**
* Returns a new instance with the given description.
*
* @param string $description
*
* @return self
*/
public function withDescription(string $description): self
{
Expand All @@ -99,10 +93,6 @@ public function getIri()

/**
* Returns a new instance with the given IRI.
*
* @param string $iri
*
* @return self
*/
public function withIri(string $iri): self
{
Expand All @@ -124,10 +114,6 @@ public function getItemOperations()

/**
* Returns a new instance with the given item operations.
*
* @param array $itemOperations
*
* @return self
*/
public function withItemOperations(array $itemOperations): self
{
Expand All @@ -149,10 +135,6 @@ public function getCollectionOperations()

/**
* Returns a new instance with the given collection operations.
*
* @param array $collectionOperations
*
* @return self
*/
public function withCollectionOperations(array $collectionOperations): self
{
Expand All @@ -162,13 +144,31 @@ public function withCollectionOperations(array $collectionOperations): self
return $metadata;
}

/**
* Gets subresource operations.
*
* @return array|null
*/
public function getSubresourceOperations()
{
return $this->subresourceOperations;
}

/**
* Returns a new instance with the given subresource operations.
*/
public function withSubresourceOperations(array $subresourceOperations): self
{
$metadata = clone $this;
$metadata->subresourceOperations = $subresourceOperations;

return $metadata;
}

/**
* Gets a collection operation attribute, optionally fallback to a resource attribute.
*
* @param string|null $operationName
* @param string $key
* @param mixed $defaultValue
* @param bool $resourceFallback
* @param mixed $defaultValue
*
* @return mixed
*/
Expand All @@ -180,10 +180,7 @@ public function getCollectionOperationAttribute(string $operationName = null, st
/**
* Gets an item operation attribute, optionally fallback to a resource attribute.
*
* @param string|null $operationName
* @param string $key
* @param mixed $defaultValue
* @param bool $resourceFallback
* @param mixed $defaultValue
*
* @return mixed
*/
Expand All @@ -192,14 +189,22 @@ public function getItemOperationAttribute(string $operationName = null, string $
return $this->getOperationAttribute($this->itemOperations, $operationName, $key, $defaultValue, $resourceFallback);
}

/**
* Gets a subresource operation attribute, optionally fallback to a resource attribute.
*
* @param mixed $defaultValue
*
* @return mixed
*/
public function getSubresourceOperationAttribute(string $operationName = null, string $key, $defaultValue = null, bool $resourceFallback = false)
{
return $this->getOperationAttribute($this->subresourceOperations, $operationName, $key, $defaultValue, $resourceFallback);
}

/**
* Gets an operation attribute, optionally fallback to a resource attribute.
*
* @param array|null $operations
* @param string|null $operationName
* @param string $key
* @param mixed $defaultValue
* @param bool $resourceFallback
* @param mixed $defaultValue
*
* @return mixed
*/
Expand Down Expand Up @@ -229,8 +234,7 @@ public function getAttributes()
/**
* Gets an attribute.
*
* @param string $key
* @param mixed $defaultValue
* @param mixed $defaultValue
*
* @return mixed
*/
Expand All @@ -245,10 +249,6 @@ public function getAttribute(string $key, $defaultValue = null)

/**
* Returns a new instance with the given attribute.
*
* @param array $attributes
*
* @return self
*/
public function withAttributes(array $attributes): self
{
Expand Down
14 changes: 14 additions & 0 deletions src/Metadata/schema/metadata.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<xsd:element name="property" minOccurs="0" maxOccurs="unbounded" type="property"/>
<xsd:element name="itemOperations" minOccurs="0" maxOccurs="unbounded" type="itemOperations"/>
<xsd:element name="collectionOperations" minOccurs="0" maxOccurs="unbounded" type="collectionOperations"/>
<xsd:element name="subresourceOperations" minOccurs="0" maxOccurs="unbounded" type="subresourceOperations"/>
</xsd:sequence>

<xsd:attribute type="xsd:string" name="class" use="required"/>
Expand Down Expand Up @@ -56,6 +57,19 @@
<xsd:attribute type="xsd:string" name="name"/>
</xsd:complexType>

<xsd:complexType name="subresourceOperations">
<xsd:sequence minOccurs="0" maxOccurs="unbounded">
<xsd:element name="subresourceOperation" minOccurs="0" maxOccurs="unbounded" type="subresourceOperation"/>
</xsd:sequence>
</xsd:complexType>

<xsd:complexType name="subresourceOperation">
<xsd:sequence minOccurs="1" maxOccurs="unbounded">
<xsd:element name="attribute" minOccurs="1" maxOccurs="unbounded" type="attribute"/>
</xsd:sequence>
<xsd:attribute type="xsd:string" name="name"/>
</xsd:complexType>

<xsd:complexType name="attribute" mixed="true">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="attribute" type="attribute" minOccurs="0" maxOccurs="unbounded" />
Expand Down
45 changes: 33 additions & 12 deletions src/Operation/Factory/SubresourceOperationFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ final class SubresourceOperationFactory implements SubresourceOperationFactoryIn
{
const SUBRESOURCE_SUFFIX = '_subresource';
const FORMAT_SUFFIX = '.{_format}';
const ROUTE_OPTIONS = ['defaults' => [], 'requirements' => [], 'options' => [], 'host' => '', 'schemes' => [], 'condition' => ''];

private $resourceMetadataFactory;
private $propertyNameCollectionFactory;
Expand Down Expand Up @@ -82,6 +83,7 @@ private function computeSubresourceOperations(string $resourceClass, array &$tre
continue;
}

$rootResourceMetadata = $this->resourceMetadataFactory->create($rootResourceClass);
$operationName = 'get';
$operation = [
'property' => $property,
Expand All @@ -91,19 +93,25 @@ private function computeSubresourceOperations(string $resourceClass, array &$tre
];

if (null === $parentOperation) {
$rootResourceMetadata = $this->resourceMetadataFactory->create($rootResourceClass);
$rootShortname = $rootResourceMetadata->getShortName();
$operation['identifiers'] = [['id', $rootResourceClass, true]];
$operation['route_name'] = sprintf(
'%s%s_%s_%s%s',
RouteNameGenerator::ROUTE_NAME_PREFIX,
RouteNameGenerator::inflector($rootShortname),
$operation['operation_name'] = sprintf(
'%s_%s%s',
RouteNameGenerator::inflector($operation['property'], $operation['collection'] ?? false),
$operationName,
self::SUBRESOURCE_SUFFIX
);

$operation['path'] = sprintf(
$subresourceOperation = $rootResourceMetadata->getSubresourceOperations()[$operation['operation_name']] ?? [];

$operation['route_name'] = sprintf(
'%s%s_%s',
RouteNameGenerator::ROUTE_NAME_PREFIX,
RouteNameGenerator::inflector($rootShortname),
$operation['operation_name']
);

$operation['path'] = $subresourceOperation['path'] ?? sprintf(
'/%s/{id}/%s%s',
$this->pathSegmentNameGenerator->getSegmentName($rootShortname, true),
$this->pathSegmentNameGenerator->getSegmentName($operation['property'], $operation['collection']),
Expand All @@ -117,18 +125,31 @@ private function computeSubresourceOperations(string $resourceClass, array &$tre
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
$operation['identifiers'] = $parentOperation['identifiers'];
$operation['identifiers'][] = [$parentOperation['property'], $resourceClass, $parentOperation['collection']];
$operation['route_name'] = str_replace('get'.self::SUBRESOURCE_SUFFIX, RouteNameGenerator::inflector($property, $operation['collection']).'_get'.self::SUBRESOURCE_SUFFIX, $parentOperation['route_name']);

$operation['operation_name'] = str_replace('get'.self::SUBRESOURCE_SUFFIX, RouteNameGenerator::inflector($property, $operation['collection']).'_get'.self::SUBRESOURCE_SUFFIX, $parentOperation['operation_name']);

$operation['route_name'] = str_replace($parentOperation['operation_name'], $operation['operation_name'], $parentOperation['route_name']);

if (!in_array($resourceMetadata->getShortName(), $operation['shortNames'], true)) {
$operation['shortNames'][] = $resourceMetadata->getShortName();
}

$operation['path'] = str_replace(self::FORMAT_SUFFIX, '', $parentOperation['path']);
if ($parentOperation['collection']) {
list($key) = end($operation['identifiers']);
$operation['path'] .= sprintf('/{%s}', $key);
$subresourceOperation = $rootResourceMetadata->getSubresourceOperations()[$operation['operation_name']] ?? [];

if (isset($subresourceOperation['path'])) {
$operation['path'] = $subresourceOperation['path'];
} else {
$operation['path'] = str_replace(self::FORMAT_SUFFIX, '', $parentOperation['path']);
if ($parentOperation['collection']) {
list($key) = end($operation['identifiers']);
$operation['path'] .= sprintf('/{%s}', $key);
}
$operation['path'] .= sprintf('/%s%s', $this->pathSegmentNameGenerator->getSegmentName($property, $operation['collection']), self::FORMAT_SUFFIX);
}
$operation['path'] .= sprintf('/%s%s', $this->pathSegmentNameGenerator->getSegmentName($property, $operation['collection']), self::FORMAT_SUFFIX);
}

foreach (self::ROUTE_OPTIONS as $routeOption => $defaultValue) {
$operation[$routeOption] = $subresourceOperation[$routeOption] ?? $defaultValue;
}

$tree[$operation['route_name']] = $operation;
Expand Down
5 changes: 5 additions & 0 deletions tests/Fixtures/FileConfigurations/resources.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
<attribute name="path">the/collection/path</attribute>
</collectionOperation>
</collectionOperations>
<subresourceOperations>
<subresourceOperation name="my_collection_subresource">
<attribute name="path">the/subresource/path</attribute>
</subresourceOperation>
</subresourceOperations>
<attribute name="normalization_context">
<attribute name="groups">
<attribute>default</attribute>
Expand Down
3 changes: 3 additions & 0 deletions tests/Fixtures/FileConfigurations/resources.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ resources:
my_collection_op:
method: 'POST'
path: 'the/collection/path'
subresourceOperations:
my_collection_subresource:
path: 'the/subresource/path'
attributes:
normalization_context:
groups: ['default']
Expand Down
3 changes: 3 additions & 0 deletions tests/Fixtures/FileConfigurations/single_resource.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
my_collection_op:
method: 'POST'
path: 'the/collection/path'
subresourceOperations:
my_collection_subresource:
path: 'the/subresource/path'
attributes:
normalization_context:
groups: ['default']
Expand Down
Loading