Skip to content

SubresourceOperations customisation DX enhancement #3466

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

Closed
wants to merge 2 commits into from
Closed
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
3 changes: 2 additions & 1 deletion src/Bridge/Symfony/Routing/ApiLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,10 @@ public function load($data, $type = null): RouteCollection
'_controller' => $controller,
'_format' => null,
'_api_resource_class' => $operation['resource_class'],
'_api_subresource_operation_name' => $operation['route_name'],
'_api_subresource_operation_name' => $operation['operation_name'],
'_api_subresource_context' => [
'property' => $operation['property'],
'parent_resource_class' => $operation['parent_resource_class'] ?? '',
'identifiers' => $operation['identifiers'],
'collection' => $operation['collection'],
'operationId' => $operationId,
Expand Down
71 changes: 43 additions & 28 deletions src/Operation/Factory/SubresourceOperationFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ private function computeSubresourceOperations(string $resourceClass, array &$tre
if (null === $rootResourceClass) {
$rootResourceClass = $resourceClass;
}
$operationMethod = 'get';

foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $property) {
$propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $property);
Expand Down Expand Up @@ -99,66 +100,80 @@ private function computeSubresourceOperations(string $resourceClass, array &$tre
}

$rootResourceMetadata = $this->resourceMetadataFactory->create($rootResourceClass);
$operationName = 'get';
$operation = [
'property' => $property,
'collection' => $subresource->isCollection(),
'resource_class' => $subresourceClass,
'parent_resource_class' => $subresourceClass,
'shortNames' => [$subresourceMetadata->getShortName()],
];
$rootShortname = $rootResourceMetadata->getShortName();

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

$subresourceOperation = $rootResourceMetadata->getSubresourceOperations()[$operation['operation_name']] ?? [];
$operation['operation_name'] = RouteNameGenerator::inflector($operation['property'], $operation['collection'] ?? false).'_'.$operationMethod;

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

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

if (isset($subresourceOperation['operation_context']['groups'])) {
$operation['groups'] = $subresourceOperation['operation_context']['groups'];
}

$operation['parent_resource_class'] = $rootResourceClass;
$operation['identifiers'] = [['id', $rootResourceClass, true]];
$prefix = trim(trim($rootResourceMetadata->getAttribute('route_prefix', '')), '/');
if ('' !== $prefix) {
$prefix .= '/';
}

$operation['path'] = $subresourceOperation['path'] ?? sprintf(
'/%s%s/{id}/%s%s',
$prefix,
$this->pathSegmentNameGenerator->getSegmentName($rootShortname),
$this->pathSegmentNameGenerator->getSegmentName($operation['property'], $operation['collection']),
self::FORMAT_SUFFIX
);
$operation['path'] = $subresourceOperation['path']
?? sprintf(
'/%s%s/{id}/%s%s',
$prefix,
$this->pathSegmentNameGenerator->getSegmentName($rootShortname),
$this->pathSegmentNameGenerator->getSegmentName($operation['property'], $operation['collection']),
self::FORMAT_SUFFIX
);

if (!\in_array($rootShortname, $operation['shortNames'], true)) {
$operation['shortNames'][] = $rootShortname;
}
} else {
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
$operation['identifiers'] = $parentOperation['identifiers'];
$operation['identifiers'][] = [$parentOperation['property'], $resourceClass, $isLastItem ? true : $parentOperation['collection']];
if (false === strstr($parentOperation['operation_name'], $operationMethod)) {
continue;
}

$operation['operation_name'] = str_replace(
'get'.self::SUBRESOURCE_SUFFIX,
RouteNameGenerator::inflector($isLastItem ? 'item' : $property, $operation['collection']).'_get'.self::SUBRESOURCE_SUFFIX,
$operationMethod,
RouteNameGenerator::inflector($isLastItem ? 'item' : $operation['property'], $operation['collection'] ?? false).'_'.$operationMethod,
$parentOperation['operation_name']
);
$operation['route_name'] = str_replace($parentOperation['operation_name'], $operation['operation_name'], $parentOperation['route_name']);

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

$subresourceOperation = $rootResourceMetadata->getSubresourceOperations()[$operation['operation_name']] ?? [];
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
$operation['identifiers'] = $parentOperation['identifiers'];
$operation['parent_resource_class'] = $parentOperation['parent_resource_class'];
$operation['identifiers'][] = [$parentOperation['property'], $resourceClass, $isLastItem ? true : $parentOperation['collection']];

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

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

if (isset($subresourceOperation['path'])) {
$operation['path'] = $subresourceOperation['path'];
} else {
Expand Down
8 changes: 8 additions & 0 deletions src/Serializer/SerializerContextBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ public function createFromRequest(Request $request, bool $normalization, array $
$context['uri'] = $request->getUri();

if (isset($attributes['subresource_context'])) {
if ($parentResourceClass = $attributes['subresource_context']['parent_resource_class'] ?? '') {
$parentSubresources = $this->resourceMetadataFactory->create($parentResourceClass)->getSubresourceOperations();

if (isset($parentSubresources[$attributes[$operationKey]]['normalization_context']['groups'])) {
$context['groups'] = $parentSubresources[$attributes[$operationKey]]['normalization_context']['groups'];
}
}

$context['subresource_identifiers'] = [];

foreach ($attributes['subresource_context']['identifiers'] as $key => [$id, $resourceClass]) {
Expand Down
14 changes: 7 additions & 7 deletions tests/Bridge/Symfony/Routing/ApiLoaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public function testApiLoader()
);

$this->assertEquals(
$this->getSubresourceRoute('/dummies/{id}/subresources.{_format}', 'api_platform.action.get_subresource', RelatedDummyEntity::class, 'api_dummies_subresources_get_subresource', ['property' => 'subresource', 'identifiers' => [['id', DummyEntity::class, true]], 'collection' => true, 'operationId' => 'api_dummies_subresources_get_subresource']),
$this->getSubresourceRoute('/dummies/{id}/subresources.{_format}', 'api_platform.action.get_subresource', RelatedDummyEntity::class, 'subresources_get', ['property' => 'subresource', 'identifiers' => [['id', DummyEntity::class, true]], 'collection' => true, 'operationId' => 'api_dummies_subresources_get_subresource', 'parent_resource_class' => 'ApiPlatform\Core\Tests\Fixtures\DummyEntity']),
$routeCollection->get('api_dummies_subresources_get_subresource')
);
}
Expand Down Expand Up @@ -188,32 +188,32 @@ public function testRecursiveSubresource()
$routeCollection = $this->getApiLoaderWithResourceMetadata($resourceMetadata, true)->load(null);

$this->assertEquals(
$this->getSubresourceRoute('/dummies/{id}/subresources.{_format}', 'api_platform.action.get_subresource', RelatedDummyEntity::class, 'api_dummies_subresources_get_subresource', ['property' => 'subresource', 'identifiers' => [['id', DummyEntity::class, true]], 'collection' => true, 'operationId' => 'api_dummies_subresources_get_subresource']),
$this->getSubresourceRoute('/dummies/{id}/subresources.{_format}', 'api_platform.action.get_subresource', RelatedDummyEntity::class, 'subresources_get', ['property' => 'subresource', 'identifiers' => [['id', DummyEntity::class, true]], 'collection' => true, 'operationId' => 'api_dummies_subresources_get_subresource', 'parent_resource_class' => 'ApiPlatform\Core\Tests\Fixtures\DummyEntity']),
$routeCollection->get('api_dummies_subresources_get_subresource')
);

$this->assertEquals(
$this->getSubresourceRoute('/related_dummies/{id}/recursivesubresource/subresources.{_format}', 'api_platform.action.get_subresource', RelatedDummyEntity::class, 'api_related_dummies_recursivesubresource_subresources_get_subresource', ['property' => 'subresource', 'identifiers' => [['id', RelatedDummyEntity::class, true], ['recursivesubresource', DummyEntity::class, false]], 'collection' => true, 'operationId' => 'api_related_dummies_recursivesubresource_subresources_get_subresource']),
$this->getSubresourceRoute('/related_dummies/{id}/recursivesubresource/subresources.{_format}', 'api_platform.action.get_subresource', RelatedDummyEntity::class, 'recursivesubresource_subresources_get', ['property' => 'subresource', 'identifiers' => [['id', RelatedDummyEntity::class, true], ['recursivesubresource', DummyEntity::class, false]], 'collection' => true, 'operationId' => 'api_related_dummies_recursivesubresource_subresources_get_subresource', 'parent_resource_class' => 'ApiPlatform\Core\Tests\Fixtures\RelatedDummyEntity']),
$routeCollection->get('api_related_dummies_recursivesubresource_subresources_get_subresource')
);

$this->assertEquals(
$this->getSubresourceRoute('/related_dummies/{id}/recursivesubresource.{_format}', 'dummy_controller', DummyEntity::class, 'api_related_dummies_recursivesubresource_get_subresource', ['property' => 'recursivesubresource', 'identifiers' => [['id', RelatedDummyEntity::class, true]], 'collection' => false, 'operationId' => 'api_related_dummies_recursivesubresource_get_subresource']),
$this->getSubresourceRoute('/related_dummies/{id}/recursivesubresource.{_format}', 'api_platform.action.get_subresource', DummyEntity::class, 'recursivesubresource_get', ['property' => 'recursivesubresource', 'identifiers' => [['id', RelatedDummyEntity::class, true]], 'collection' => false, 'operationId' => 'api_related_dummies_recursivesubresource_get_subresource', 'parent_resource_class' => 'ApiPlatform\Core\Tests\Fixtures\RelatedDummyEntity']),
$routeCollection->get('api_related_dummies_recursivesubresource_get_subresource')
);

$this->assertEquals(
$this->getSubresourceRoute('/dummies/{id}/subresources/{subresource}/recursivesubresource.{_format}', 'api_platform.action.get_subresource', DummyEntity::class, 'api_dummies_subresources_recursivesubresource_get_subresource', ['property' => 'recursivesubresource', 'identifiers' => [['id', DummyEntity::class, true], ['subresource', RelatedDummyEntity::class, true]], 'collection' => false, 'operationId' => 'api_dummies_subresources_recursivesubresource_get_subresource']),
$this->getSubresourceRoute('/dummies/{id}/subresources/{subresource}/recursivesubresource.{_format}', 'api_platform.action.get_subresource', DummyEntity::class, 'subresources_recursivesubresource_get', ['property' => 'recursivesubresource', 'identifiers' => [['id', DummyEntity::class, true], ['subresource', RelatedDummyEntity::class, true]], 'collection' => false, 'operationId' => 'api_dummies_subresources_recursivesubresource_get_subresource', 'parent_resource_class' => 'ApiPlatform\Core\Tests\Fixtures\DummyEntity']),
$routeCollection->get('api_dummies_subresources_recursivesubresource_get_subresource')
);

$this->assertEquals(
$this->getSubresourceRoute('/related_dummies/{id}/secondrecursivesubresource/subresources.{_format}', 'api_platform.action.get_subresource', RelatedDummyEntity::class, 'api_related_dummies_secondrecursivesubresource_subresources_get_subresource', ['property' => 'subresource', 'identifiers' => [['id', RelatedDummyEntity::class, true], ['secondrecursivesubresource', DummyEntity::class, false]], 'collection' => true, 'operationId' => 'api_related_dummies_secondrecursivesubresource_subresources_get_subresource']),
$this->getSubresourceRoute('/related_dummies/{id}/secondrecursivesubresource/subresources.{_format}', 'api_platform.action.get_subresource', RelatedDummyEntity::class, 'secondrecursivesubresource_subresources_get', ['property' => 'subresource', 'identifiers' => [['id', RelatedDummyEntity::class, true], ['secondrecursivesubresource', DummyEntity::class, false]], 'collection' => true, 'operationId' => 'api_related_dummies_secondrecursivesubresource_subresources_get_subresource', 'parent_resource_class' => 'ApiPlatform\Core\Tests\Fixtures\RelatedDummyEntity']),
$routeCollection->get('api_related_dummies_secondrecursivesubresource_subresources_get_subresource')
);

$this->assertEquals(
$this->getSubresourceRoute('/related_dummies/{id}/secondrecursivesubresource.{_format}', 'api_platform.action.get_subresource', DummyEntity::class, 'api_related_dummies_secondrecursivesubresource_get_subresource', ['property' => 'secondrecursivesubresource', 'identifiers' => [['id', RelatedDummyEntity::class, true]], 'collection' => false, 'operationId' => 'api_related_dummies_secondrecursivesubresource_get_subresource']),
$this->getSubresourceRoute('/related_dummies/{id}/secondrecursivesubresource.{_format}', 'api_platform.action.get_subresource', DummyEntity::class, 'secondrecursivesubresource_get', ['property' => 'secondrecursivesubresource', 'identifiers' => [['id', RelatedDummyEntity::class, true]], 'collection' => false, 'operationId' => 'api_related_dummies_secondrecursivesubresource_get_subresource', 'parent_resource_class' => 'ApiPlatform\Core\Tests\Fixtures\RelatedDummyEntity']),
$routeCollection->get('api_related_dummies_secondrecursivesubresource_get_subresource')
);
}
Expand Down
Loading