API Platform version(s) affected: 3.0.2
Description
I'm migrating from v2.6.5 to v3.0.2. In v2 I used AutoGroupResourceMetadataFactory proposed in this Symfony Cast with my minor modifications.
AutoGroupResourceMetadataFactory.php
<?php
declare(strict_types=1);
namespace App\ApiPlatform;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
/**
* This class is responsible for auto-creation of normalization/denormalization groups
* to allow flexible transformations/settings on a per-entity/per-property basis.
*
* @see https://symfonycasts.com/screencast/api-platform-security/resource-metadata-factory
*/
class AutoGroupResourceMetadataFactory implements ResourceMetadataFactoryInterface
{
public function __construct(private ResourceMetadataFactoryInterface $decorated)
{
}
public function create(string $resourceClass): ResourceMetadata
{
$resourceMetadata = $this->decorated->create($resourceClass);
$itemOperations = $resourceMetadata->getItemOperations();
$resourceMetadata = $resourceMetadata->withItemOperations(
$this->updateContextOnOperations((array) $itemOperations, $resourceMetadata->getShortName() ?? '', true)
);
$collectionOperations = $resourceMetadata->getCollectionOperations();
$resourceMetadata = $resourceMetadata->withCollectionOperations(
$this->updateContextOnOperations((array) $collectionOperations, $resourceMetadata->getShortName() ?? '', false)
);
return $resourceMetadata;
}
private function updateContextOnOperations(array $operations, string $shortName, bool $isItem): array
{
foreach ($operations as $operationName => $operationOptions) {
$operationOptions['normalization_context'] = $operationOptions['normalization_context'] ?? [];
$operationOptions['normalization_context']['groups'] = $operationOptions['normalization_context']['groups'] ?? [];
$operationOptions['normalization_context']['groups'] = array_unique(array_merge(
$operationOptions['normalization_context']['groups'],
$this->getDefaultGroups($shortName, true, $isItem, $operationName)
));
$operationOptions['denormalization_context'] = $operationOptions['denormalization_context'] ?? [];
$operationOptions['denormalization_context']['groups'] = $operationOptions['denormalization_context']['groups'] ?? [];
$operationOptions['denormalization_context']['groups'] = array_unique(array_merge(
$operationOptions['denormalization_context']['groups'],
$this->getDefaultGroups($shortName, false, $isItem, $operationName)
));
$operations[$operationName] = $operationOptions;
}
return $operations;
}
private function getDefaultGroups(string $shortName, bool $normalization, bool $isItem, string $operationName): array
{
// TODO: $shortName can come empty, or preg_replace may fail. Maybe throw an error?
$shortName = (string) preg_replace('/(?<!^)[A-Z]/', '_$0', $shortName);
$shortName = strtolower($shortName);
$readOrWrite = $normalization ? 'read' : 'write';
$itemOrCollection = $isItem ? 'item' : 'collection';
return [
// {shortName}:{read/write}
// e.g. user:read
sprintf('%s:%s', $shortName, $readOrWrite),
// {shortName}:{item/collection}:{read/write}
// e.g. user:collection:read
sprintf('%s:%s:%s', $shortName, $itemOrCollection, $readOrWrite),
// {shortName}:{item/collection}:{operationName}
// e.g. user:collection:get
sprintf('%s:%s:%s', $shortName, $itemOrCollection, $operationName),
];
}
}
ResourceMetadataFactoryInterface was removed in v3. So, I rewrote the class this way (not complete yet, but works for me):
AutoGroupResourceMetadataCollectionFactory.php
<?php
declare(strict_types=1);
namespace App\ApiPlatform;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Operations;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
/**
* This class is responsible for auto-creation of normalization/denormalization groups
* to allow flexible transformations/settings on a per-entity/per-property basis.
*
* @see https://symfonycasts.com/screencast/api-platform-security/resource-metadata-factory
*/
class AutoGroupResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
{
public function __construct(private readonly ResourceMetadataCollectionFactoryInterface $decorated)
{
}
public function create(string $resourceClass): ResourceMetadataCollection
{
$resourceMetadataCollection = $this->decorated->create($resourceClass);
/**
* @var ApiResource $resourceMetadata */
foreach ($resourceMetadataCollection as $i => $resourceMetadata) {
$operations = $resourceMetadata->getOperations();
$resourceMetadata->withOperations(
$this->updateContextOnOperations(
$operations,
$resourceMetadata->getShortName() ?? '',
)
);
}
return $resourceMetadataCollection;
}
private function updateContextOnOperations(Operations $operations, string $shortName): Operations
{
/** @var Operation $operation */
foreach ($operations as $operationName => $operation) {
$isItem = !($operation instanceof GetCollection);
$normalizationContext = $operation->getNormalizationContext() ?? [];
$normalizationContext['groups'] = $normalizationContext['groups'] ?? [];
$normalizationContext['groups'] = array_unique(array_merge(
$normalizationContext['groups'],
$this->getDefaultGroups($shortName, true, $isItem, $operationName)
));
$operation = $operation->withNormalizationContext($normalizationContext);
$denormalizationContext = $operation->getDenormalizationContext() ?? [];
$denormalizationContext['groups'] = $denormalizationContext['groups'] ?? [];
$denormalizationContext['groups'] = array_unique(array_merge(
$denormalizationContext['groups'],
$this->getDefaultGroups($shortName, false, $isItem, $operationName)
));
$operation = $operation->withDenormalizationContext($denormalizationContext);
$operations->add($operationName, $operation);
}
return $operations;
}
private function getDefaultGroups(string $shortName, bool $normalization, bool $isItem, string $operationName): array
{
// TODO: $shortName can come empty, or preg_replace may fail. Maybe throw an error?
$shortName = (string) preg_replace('/(?<!^)[A-Z]/', '_$0', $shortName);
$shortName = strtolower($shortName);
$readOrWrite = $normalization ? 'read' : 'write';
$itemOrCollection = $isItem ? 'item' : 'collection';
return [
// {shortName}:{read/write}
// e.g. user:read
sprintf('%s:%s', $shortName, $readOrWrite),
// {shortName}:{item/collection}:{read/write}
// e.g. user:collection:read
sprintf('%s:%s:%s', $shortName, $itemOrCollection, $readOrWrite),
// {shortName}:{item/collection}:{operationName}
// e.g. user:collection:get
sprintf('%s:%s:%s', $shortName, $itemOrCollection, $operationName),
];
}
}
For this to work, I had to use ApiPlatform\Metadata\Operations, but it is marked as @internal. I still can use it, but my IDE crosses it out. And as it's marked as internal, there seems to be no guarantee on the interface stability.
Also, while ApiPlatform\Metadata\Operations is marked as @internal, ApiPlatform\Metadata\Operation is not. Which seems to be inconsistent.
Finally, ApiPlatform\Metadata\ApiResource::getOperations(...) returns ?Operations and ApiPlatform\Metadata\ApiResource::withOperations(...) accepts Operations. ApiResource is not @internal and the mentioned methods are public and are not marked as @internal.
Summarizing the facts, I believe ApiPlatform\Metadata\Operations should not be considered @internal.
What do you think?
Possible Solution
Remove @internal from ApiPlatform\Metadata\Operations.
API Platform version(s) affected: 3.0.2
Description
I'm migrating from v2.6.5 to v3.0.2. In v2 I used
AutoGroupResourceMetadataFactoryproposed in this Symfony Cast with my minor modifications.AutoGroupResourceMetadataFactory.php
ResourceMetadataFactoryInterfacewas removed in v3. So, I rewrote the class this way (not complete yet, but works for me):AutoGroupResourceMetadataCollectionFactory.php
For this to work, I had to use
ApiPlatform\Metadata\Operations, but it is marked as@internal. I still can use it, but my IDE crosses it out. And as it's marked as internal, there seems to be no guarantee on the interface stability.Also, while
ApiPlatform\Metadata\Operationsis marked as@internal,ApiPlatform\Metadata\Operationis not. Which seems to be inconsistent.Finally,
ApiPlatform\Metadata\ApiResource::getOperations(...)returns?OperationsandApiPlatform\Metadata\ApiResource::withOperations(...)acceptsOperations.ApiResourceis not@internaland the mentioned methods are public and are not marked as@internal.Summarizing the facts, I believe
ApiPlatform\Metadata\Operationsshould not be considered@internal.What do you think?
Possible Solution
Remove
@internalfromApiPlatform\Metadata\Operations.