Merge pull request #118 from goteo/fix/versioned-resources-state

[fix] Versioned resources state
This commit is contained in:
2025-05-13 15:22:19 +02:00
committed by GitHub
6 changed files with 85 additions and 58 deletions
+10 -11
View File
@@ -5,7 +5,6 @@ namespace App\ApiResource;
use ApiPlatform\Metadata as API;
use App\Filter\ResourceVersionResourceFilter;
use App\Filter\ResourceVersionResourceIdFilter;
use App\Service\ApiService;
use App\State\ResourceVersionStateProvider;
use Gedmo\Loggable\Entity\LogEntry;
@@ -23,7 +22,7 @@ class Version
{
public function __construct(
private readonly LogEntry $log,
private readonly object $entity,
private readonly string $shortName,
) {}
/**
@@ -34,14 +33,6 @@ class Version
return $this->log->getId();
}
/**
* The ID of the version for this specific resource.
*/
public function getVersion(): ?int
{
return $this->log->getVersion();
}
/**
* The type of action that performed the recorded changes.
*/
@@ -55,7 +46,7 @@ class Version
*/
public function getResource(): string
{
return ApiService::toResource($this->log->getObjectClass());
return $this->shortName;
}
/**
@@ -66,6 +57,14 @@ class Version
return $this->log->getObjectId();
}
/**
* The version number for this specific resource.
*/
public function getResourceVersion(): ?int
{
return $this->log->getVersion();
}
/**
* The changed resource data, i.e the new values of the changed properties.
*/
@@ -2,9 +2,9 @@
namespace App\DependencyInjection\Compiler;
use App\Service\ApiService;
use ApiPlatform\Metadata\ApiResource;
use App\Service\VersionedResourceService;
use Gedmo\Mapping\Annotation as Gedmo;
use Gedmo\Mapping\Annotation\Loggable;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -13,27 +13,48 @@ class VersionedResourcePass implements CompilerPassInterface
public function process(ContainerBuilder $container): void
{
$allClasses = \get_declared_classes();
$entityClasses = \array_filter($allClasses, function (string $className) {
return \str_starts_with($className, 'App\\Entity\\');
$resourceClasses = \array_filter($allClasses, function (string $className) {
return \str_starts_with($className, 'App\\ApiResource\\');
});
$versionedResourceNames = [];
foreach ($entityClasses as $entityClass) {
$reflectionClass = new \ReflectionClass($entityClass);
$attributes = $reflectionClass->getAttributes(Gedmo\Loggable::class);
$versionedResources = [];
foreach ($resourceClasses as $resourceClass) {
$resourceReflection = new \ReflectionClass($resourceClass);
$resourceAttributes = $resourceReflection->getAttributes(ApiResource::class);
if (empty($attributes)) {
if (empty($resourceAttributes)) {
continue;
}
$versionedResourceNames = [
...$versionedResourceNames,
ApiService::toResource($entityClass),
$resourceAttribute = $resourceAttributes[0];
$resourceAttributeArgs = $resourceAttribute->getArguments();
if (!\array_key_exists('stateOptions', $resourceAttributeArgs)) {
continue;
}
if (!\array_key_exists('shortName', $resourceAttributeArgs)) {
continue;
}
/** @var string */
$resourceEntityClass = $resourceAttributeArgs['stateOptions']->getEntityClass();
$entityReflection = new \ReflectionClass($resourceEntityClass);
$entityLoggable = $entityReflection->getAttributes(Loggable::class);
if (empty($entityLoggable)) {
continue;
}
$versionedResources = [
...$versionedResources,
$resourceAttributeArgs['shortName'] => $resourceEntityClass,
];
}
/** @var VersionedResourceService */
$versionedResourceService = $container->get(VersionedResourceService::class);
$versionedResourceService->compileNames($versionedResourceNames);
$versionedResourceService->compileNames($versionedResources);
}
}
+1 -1
View File
@@ -47,7 +47,7 @@ final class ResourceVersionResourceFilter extends AbstractFilter
'description' => 'The name of the resource.',
'schema' => [
'type' => Type::BUILTIN_TYPE_STRING,
'enum' => $this->versionedResourceService->getNames(),
'enum' => \array_keys($this->versionedResourceService->getNames()),
],
'openapi' => [
'allowEmptyValue' => false,
-24
View File
@@ -1,24 +0,0 @@
<?php
namespace App\Service;
class ApiService
{
/**
* Converts an Entity class name into an API resource name.
*/
public static function toResource(string $className): string
{
$classPieces = explode('\\', $className);
return lcfirst(end($classPieces));
}
/**
* Converts to an Entity class name from an API resource name.
*/
public static function toEntity(string $resourceName): string
{
return sprintf('App\\Entity\\%s', ucfirst($resourceName));
}
}
+32 -4
View File
@@ -43,7 +43,7 @@ class VersionedResourceService
/**
* Stores the given names in lock file.
*
* @param string[] $names
* @param array<string, string> $names
*/
public function compileNames(array $names)
{
@@ -51,17 +51,45 @@ class VersionedResourceService
\file_put_contents(
self::getNamesLockFile(),
implode(PHP_EOL, $names)
json_encode($names)
);
}
/**
* @return array<string> List of the versioned resource names
* @return array<string, string> List of the versioned resource names
*
* @see compileNames()
*/
public static function getNames(): array
{
return explode(PHP_EOL, \file_get_contents(self::getNamesLockFile()));
return json_decode(\file_get_contents(self::getNamesLockFile()), true);
}
public static function getEntityFromResource(string $resourceShortName): string
{
$names = self::getNames();
if (\array_key_exists($resourceShortName, $names)) {
return $names[$resourceShortName];
}
throw new \Exception(\sprintf(
"The resource shortName '%s' does not match to a known versioned resource",
$resourceShortName
));
}
public static function getResourceFromEntity(string $entityClass): string
{
foreach (self::getNames() as $shortName => $entityFqcl) {
if ($entityFqcl === $entityClass) {
return $shortName;
}
}
throw new \Exception(\sprintf(
"The entity class name '%s' does not match to a known versioned resource",
$entityClass
));
}
}
+8 -5
View File
@@ -6,7 +6,7 @@ use ApiPlatform\Metadata as API;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\ApiResource\Version;
use App\Service\ApiResourceNormalizer;
use App\Service\VersionedResourceService;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\Mapping\MappingException;
use Gedmo\Loggable\Entity\LogEntry;
@@ -33,6 +33,8 @@ class ResourceVersionStateProvider implements ProviderInterface
$context['request']->query->get('resource'),
$context['request']->query->get('resourceId')
);
default:
throw new \Exception('Operation not supported for Version resource', 1);
}
}
@@ -48,8 +50,9 @@ class ResourceVersionStateProvider implements ProviderInterface
}
$entity = $this->entityManager->find($log->getObjectClass(), $log->getObjectId());
$resourceName = VersionedResourceService::getResourceFromEntity($entity::class);
return new Version($log, $entity);
return new Version($log, $resourceName);
}
/**
@@ -57,10 +60,10 @@ class ResourceVersionStateProvider implements ProviderInterface
*/
private function getVersions(string $resourceName, int $resourceId): array
{
$resourceClass = ApiResourceNormalizer::toEntity($resourceName);
$entityClass = VersionedResourceService::getEntityFromResource($resourceName);
try {
$entity = $this->entityManager->find($resourceClass, $resourceId);
$entity = $this->entityManager->find($entityClass, $resourceId);
} catch (MappingException $e) {
throw new NotFoundHttpException(sprintf("Resource '%s' does not exist", $resourceName));
}
@@ -73,7 +76,7 @@ class ResourceVersionStateProvider implements ProviderInterface
$versions = [];
foreach ($logs as $log) {
$versions[] = new Version($log, $entity);
$versions[] = new Version($log, $resourceName);
}
return $versions;