diff --git a/src/Entity/LocalDataAwareInterface.php b/src/Entity/LocalDataAwareInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..9aa8daf27965142ed64409445a137e5779f48a32 --- /dev/null +++ b/src/Entity/LocalDataAwareInterface.php @@ -0,0 +1,27 @@ +<?php + +declare(strict_types=1); + +namespace Dbp\Relay\CoreBundle\Entity; + +interface LocalDataAwareInterface +{ + /** + * Returns the unique name (shortName of the ApiResource) of this entity. + */ + public static function getUniqueEntityName(): string; + + /** + * Sets the value of a local data attribute. + * + * @param mixed|null $value + */ + public function setLocalDataValue(string $key, $value): void; + + /** + * Returns the value of local data value attribute or null if the attribute is not found. + * + * @return ?mixed + */ + public function getLocalDataValue(string $key); +} diff --git a/src/Entity/LocalDataAwareTrait.php b/src/Entity/LocalDataAwareTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..e2cd56b43635232da4741e6cef04a655a2e6a2e4 --- /dev/null +++ b/src/Entity/LocalDataAwareTrait.php @@ -0,0 +1,49 @@ +<?php + +declare(strict_types=1); + +namespace Dbp\Relay\CoreBundle\Entity; + +use ApiPlatform\Core\Annotation\ApiProperty; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Annotation\Ignore; + +trait LocalDataAwareTrait +{ + /** + * @ApiProperty(iri="https://schema.org/additionalProperty") + * @Groups({"LocalData:output"}) + * + * @var array + */ + private $localData; + + public function getLocalData(): array + { + return $this->localData; + } + + /** + * Adds a local data entry. + * + * @param mixed|null $value + */ + public function setLocalDataValue(string $key, $value): void + { + if (!$this->localData) { + $this->localData = []; + } + $this->localData[$key] = $value; + } + + /** + * @Ignore + * Returns the local data value for the given key or null if the key is not found. + * + * @return ?mixed + */ + public function getLocalDataValue(string $key) + { + return $this->localData ? ($this->localData[$key] ?? null) : null; + } +} diff --git a/src/Event/LocalDataAwarePostEvent.php b/src/Event/LocalDataAwarePostEvent.php new file mode 100644 index 0000000000000000000000000000000000000000..5ba46740f6e17d53a5a385137330de8c53ff03b4 --- /dev/null +++ b/src/Event/LocalDataAwarePostEvent.php @@ -0,0 +1,55 @@ +<?php + +declare(strict_types=1); + +namespace Dbp\Relay\CoreBundle\Event; + +use Dbp\Relay\CoreBundle\Entity\LocalDataAwareInterface; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Contracts\EventDispatcher\Event; + +class LocalDataAwarePostEvent extends Event +{ + /** @var LocalDataAwareInterface */ + private $entity; + + /** @var array */ + private $requestedAttributes; + + protected function __construct(LocalDataAwareInterface $entity) + { + $this->entity = $entity; + } + + public function setRequestedAttributes(array $requestedAttributes) + { + $this->requestedAttributes = $requestedAttributes; + } + + public function getRemainingRequestedAttributes(): array + { + return $this->requestedAttributes; + } + + public function getEntity(): LocalDataAwareInterface + { + return $this->entity; + } + + public function setLocalDataAttribute(string $key, $value) + { + $arrayKey = array_search($key, $this->requestedAttributes, true); + if ($arrayKey === false) { + throw new HttpException(500, sprintf("local data attribute '%s' not requested for entity '%s'", $key, $this->entity->getUniqueEntityName())); + } + + // once set, remove the attribute from the list of requested attributes + array_splice($this->requestedAttributes, $arrayKey, 1); + $this->entity->setLocalDataValue($key, $value); + } + + public function isLocalDataAttributeRequested(string $key): bool + { + return in_array($key, $this->requestedAttributes, true); + } +} diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml index 7162883223a82552f9f6451ad16aa2210724c9d2..7b77597f427ac324128cea97a038b2e6be2f5054 100644 --- a/src/Resources/config/services.yaml +++ b/src/Resources/config/services.yaml @@ -56,4 +56,8 @@ services: Dbp\Relay\CoreBundle\Auth\ProxyAuthenticator: autowire: true - autoconfigure: true \ No newline at end of file + autoconfigure: true + + Dbp\Relay\CoreBundle\Service\LocalDataAwareEventDispatcher: + autowire: true + autoconfigure: true diff --git a/src/Service/LocalDataAwareEventDispatcher.php b/src/Service/LocalDataAwareEventDispatcher.php new file mode 100644 index 0000000000000000000000000000000000000000..2d08fe2ed553b2217a2cd80d08de7ddc6c9644f2 --- /dev/null +++ b/src/Service/LocalDataAwareEventDispatcher.php @@ -0,0 +1,78 @@ +<?php + +declare(strict_types=1); + +namespace Dbp\Relay\CoreBundle\Service; + +use Dbp\Relay\CoreBundle\Event\LocalDataAwarePostEvent; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpKernel\Exception\HttpException; + +class LocalDataAwareEventDispatcher +{ + private $requestedAttributes; + + /** @var string */ + private $unqiueEntityName; + + /** @var EventDispatcherInterface */ + private $eventDispatcher; + + /** @var string */ + private $eventName; + + public function __construct(string $unqiueEntityName, EventDispatcherInterface $eventDispatcher, string $eventName) + { + $this->unqiueEntityName = $unqiueEntityName; + $this->eventDispatcher = $eventDispatcher; + $this->eventName = $eventName; + } + + public function initRequestedLocalDataAttributes(array $options) + { + $this->requestedAttributes = []; + if ($include = $options['include'] ?? null) { + $requestedLocalDataAttributes = explode(',', $include); + + foreach ($requestedLocalDataAttributes as $requestedLocalDataAttribute) { + $requestedLocalDataAttribute = trim($requestedLocalDataAttribute); + if (!empty($requestedLocalDataAttribute)) { + $requestedUniqueEntityName = null; + $requestedAttributeName = null; + if (!self::parseLocalDataAttribute($requestedLocalDataAttribute, $requestedUniqueEntityName, $requestedAttributeName)) { + throw new HttpException(400, sprintf("value of 'include' parameter has invalid format: '%s' (Example: 'ResourceName.attr,ResourceName.attr2')", $requestedLocalDataAttribute)); + } + + if ($this->unqiueEntityName === $requestedUniqueEntityName) { + $this->requestedAttributes[] = $requestedAttributeName; + } + } + } + $this->requestedAttributes = array_unique($this->requestedAttributes); + } + } + + public function dispatch(LocalDataAwarePostEvent $event) + { + $event->setRequestedAttributes($this->requestedAttributes); + + $this->eventDispatcher->dispatch($event, $this->eventName); + + $remainingLocalDataAttributes = $event->getRemainingRequestedAttributes(); + if (!empty($remainingLocalDataAttributes)) { + throw new HttpException(500, sprintf("the following local data attributes were not provided for resource '%s': %s", $this->unqiueEntityName, implode(', ', $remainingLocalDataAttributes))); + } + } + + private static function parseLocalDataAttribute(string $localDataAttribute, ?string &$entityUniqueName, ?string &$attributeName): bool + { + $parts = explode('.', $localDataAttribute); + if (count($parts) !== 2 || empty($parts[0]) || empty($parts[1])) { + return false; + } + $entityUniqueName = $parts[0]; + $attributeName = $parts[1]; + + return true; + } +}