Skip to content
Commits on Source (10)
This diff is collapsed.
#Local Data
Local data provides a mechanism to extend entities by attributes which are not part of the entities default set of attributes. Local data can be added in custom entity (post-)event subscribers.
Local data provides a mechanism to extend resource entities by attributes which are not part of the entities default set of attributes. Local data can be added in custom entity (post-)event subscribers.
## Local Data requests
Local data can be requested using the `inlucde` parameter provided by entity GET operations. The format is the following:
Local data can be requested using the `includeLocal` parameter provided by resource entity GET operations. The format is the following:
```php
include=<ResourceName>.<attributeName>,...
includeLocal=<ResourceName>.<attributeName>,...
```
It is a comma-separated list of 0 ... n `<ResourceName>.<attributeName>` pairs, where `ResourceName` is the `shortName` defined in the `ApiResource` annotation of an entity. The list may contain attributes form different resources.
The backend will return an error if
* The `shortName` of the entity contains `.` or `,` characters
* The format of the `include` parameter is invalid
* The format of the `includeLocal` parameter value is invalid
* Any of the requested attributes could not be provided
The backend will issue a warning if
* The backend tried to set an attribute which was not requested
##Adding Local Data Attributes to Existing Entities
......@@ -34,8 +36,13 @@ class EntityEventSubscriber implements EventSubscriberInterface
public function onPost(EntityPostEvent $event)
{
$data = $event->getSourceData();
$event->trySetLocalDataAttribute('foo', $data->getFoo());
$sourceData = $event->getSourceData();
$event->trySetLocalDataAttribute('foo', $sourceData->getFoo());
if ($event->isLocalDataAttributeRequested('bar')) {
$bar = $externalApi->getBar(); // expensive api call
$event->setLocalDataAttribute('bar', $bar);
}
}
}
```
......@@ -43,7 +50,9 @@ Events of built-in entities provide a `getSourceData()` and a `getEntity()` meth
* `getSourceData()` provides the full set of available attributes for the entity
* `getEntity()` provides the entity itself
The event's `trySetLocalDataAttribute` method provides a convient way for setting attributes without causing an error in case the attribute was not requested by the client.
To set local data attributes, use:
* `trySetLocalDataAttribute` if you have the attribute value already at hand. It is safe because it sets the value only if the attrubte was requested
* `setLocalDataAttribute`, in case `isLocalDataAttributeRequested` is `true`, if getting the attribute value is expensive
Note that local data values have to be serializable to JSON.
......@@ -58,7 +67,7 @@ You can easily add local data to your Entity (`MyEntity`) by:
normalizationContext={"groups" = {"MyEntity:output", "LocalData:output"}}
```
* Adding an event dispatcher member variable of type `LocalDataAwareEventDispatcher` to your entity provider
* On GET-requests, passing the value of the `include` parameter to the event dispatcher
* On GET-requests, passing the value of the `includeLocal` parameter to the event dispatcher
```php
$this->eventDispatcher->initRequestedLocalDataAttributes($includeParameter);
```
......@@ -80,4 +89,4 @@ $this->eventDispatcher->dispatch($postEvent, MyEntityPostEvent::NAME);
return $myEntity;
```
In case your entity has nested entities (sub-resources), your entity provider is responsible of passing the `include` parameter to sub-resource providers.
\ No newline at end of file
In case your entity has nested entities (sub-resources), your entity provider is responsible of passing the `includeLocal` parameter to sub-resource providers.
\ No newline at end of file
<?php
declare(strict_types=1);
namespace Dbp\Relay\CoreBundle\LocalData;
class LocalData
{
public const INCLUDE_PARAMETER_NAME = 'includeLocal';
public static function getIncludeParameter(array $filters): string
{
return $filters[self::INCLUDE_PARAMETER_NAME] ?? '';
}
}
......@@ -5,10 +5,14 @@ declare(strict_types=1);
namespace Dbp\Relay\CoreBundle\LocalData;
use Dbp\Relay\CoreBundle\Exception\ApiError;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Contracts\EventDispatcher\Event;
class LocalDataAwareEvent extends Event
class LocalDataAwareEvent extends Event implements LoggerAwareInterface
{
use LoggerAwareTrait;
/** @var LocalDataAwareInterface */
private $entity;
......@@ -90,12 +94,15 @@ class LocalDataAwareEvent extends Event
*
* @throws ApiError if attribute $key is not in the set of requested attributes
*/
private function setLocalDataAttributeInternal(string $key, $value, bool $throwIfNotFound): void
private function setLocalDataAttributeInternal(string $key, $value, bool $warnfNotFound): void
{
$arrayKey = array_search($key, $this->requestedAttributes, true);
if ($arrayKey === false) {
if ($throwIfNotFound) {
throw new ApiError(500, sprintf("trying to set local data attribute '%s', which was not requested for entity '%s'", $key, LocalDataAwareEventDispatcher::getUniqueEntityName(get_class($this->entity))));
if ($warnfNotFound) {
if ($this->logger !== null) {
$this->logger->warning(sprintf("trying to set local data attribute '%s', which was not requested for entity '%s'", $key, LocalDataAwareEventDispatcher::getUniqueEntityName(get_class($this->entity))));
}
assert(false);
} else {
return;
}
......
......@@ -22,20 +22,26 @@ class LocalDataAwareEventDispatcher
/** @var EventDispatcherInterface */
private $eventDispatcher;
/**
* @param string $resourceClass The class name of the entity (resource) this event dispatcher is responsible for
* @param EventDispatcherInterface $eventDispatcher The inner event dispatcher that this event dispatcher decorates
*/
public function __construct(string $resourceClass, EventDispatcherInterface $eventDispatcher)
{
$this->requestedAttributes = [];
$this->uniqueEntityName = self::getUniqueEntityName($resourceClass);
$this->eventDispatcher = $eventDispatcher;
}
/**
* Parses the 'include' option, if present, and extracts the list of requested attributes for $this->uniqueEntityName.
* Parses the 'include' parameter and extracts the list of requested attributes for this event dispatcher's entity (resource).
*
* @param string $includeParameter The value of the 'include' parameter as passed to a GET-operation
* @param ?string $includeParameter The value of the 'include' parameter as passed to a GET-operation
*/
public function initRequestedLocalDataAttributes(string $includeParameter): void
public function initRequestedLocalDataAttributes(?string $includeParameter): void
{
$this->requestedAttributes = [];
if (!empty($includeParameter)) {
$requestedLocalDataAttributes = explode(',', $includeParameter);
......@@ -57,6 +63,22 @@ class LocalDataAwareEventDispatcher
}
}
/**
* Checks if the given entity's local data attribute names matches the list of requested attributes this event dispatcher's entity (resource).
* NOTE: The resource class of the entities must match.
*
* @param LocalDataAwareInterface $entity The entity whose local data attributes to check
*/
public function checkRequestedAttributesIdentitcal(LocalDataAwareInterface $entity)
{
assert(self::getUniqueEntityName(get_class($entity)) === $this->uniqueEntityName);
$availableAttributes = $entity->getLocalData() ? array_keys($entity->getLocalData()) : [];
return count($this->requestedAttributes) === count($availableAttributes) &&
empty(array_diff($this->requestedAttributes, $availableAttributes));
}
/**
* Dispatches the given event.
*/
......
......@@ -6,6 +6,11 @@ namespace Dbp\Relay\CoreBundle\LocalData;
interface LocalDataAwareInterface
{
/**
* Returns the array of local data attributes.
*/
public function getLocalData(): ?array;
/**
* Sets the value of a local data attribute.
*
......@@ -19,7 +24,14 @@ interface LocalDataAwareInterface
*
* @param string $key The attribute name
*
* @return ?mixed The value or null if the attribute is not found
* @return ?mixed The value or null if there is no local data attribute with the given name
*/
public function getLocalDataValue(string $key);
/**
* Returns whether there is a local attribute with the given name.
*
* @param string $key The attribute name
*/
public function hasLocalDataValue(string $key): bool;
}
......@@ -21,7 +21,7 @@ trait LocalDataAwareTrait
/**
* Returns the array of local data attributes.
*/
public function getLocalData(): array
public function getLocalData(): ?array
{
return $this->localData;
}
......@@ -52,4 +52,14 @@ trait LocalDataAwareTrait
{
return $this->localData ? ($this->localData[$key] ?? null) : null;
}
/**
* Returns whether there is a local attribute with the given name.
*
* @param string $key The attribute name
*/
public function hasLocalDataValue(string $key): bool
{
return $this->localData && array_key_exists($key, $this->localData);
}
}
......@@ -58,7 +58,11 @@ services:
autowire: true
autoconfigure: true
Dbp\Relay\CoreBundle\Service\LocalDataAwareEventDispatcher:
Dbp\Relay\CoreBundle\LocalData\LocalDataAwareEventDispatcher:
autowire: true
autoconfigure: true
Dbp\Relay\CoreBundle\LocalData\LocalDataAwareEvent:
autowire: true
autoconfigure: true
......