class EntityResource in CMS Content Sync 8
Same name and namespace in other branches
- 2.1.x src/Plugin/rest/resource/EntityResource.php \Drupal\cms_content_sync\Plugin\rest\resource\EntityResource
- 2.0.x src/Plugin/rest/resource/EntityResource.php \Drupal\cms_content_sync\Plugin\rest\resource\EntityResource
Provides entity interfaces for Content Sync, allowing Sync Core to request and manipulate entities.
Plugin annotation
@RestResource(
id = "cms_content_sync_entity_resource",
label = @Translation("Flow Entity Resource"),
uri_paths = {
"canonical" = "/rest/cms-content-sync/{api}/{entity_type}/{entity_bundle}/{entity_type_version}/{entity_uuid}",
"create" = "/rest/cms-content-sync/{api}/{entity_type}/{entity_bundle}/{entity_type_version}"
}
)
Hierarchy
- class \Drupal\Component\Plugin\PluginBase implements DerivativeInspectionInterface, PluginInspectionInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
- class \Drupal\rest\Plugin\ResourceBase implements ContainerFactoryPluginInterface, ResourceInterface
- class \Drupal\cms_content_sync\Plugin\rest\resource\EntityResource
- class \Drupal\rest\Plugin\ResourceBase implements ContainerFactoryPluginInterface, ResourceInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
Expanded class hierarchy of EntityResource
File
- src/
Plugin/ rest/ resource/ EntityResource.php, line 36
Namespace
Drupal\cms_content_sync\Plugin\rest\resourceView source
class EntityResource extends ResourceBase {
/**
* @var int CODE_INVALID_DATA The provided data could not be interpreted
*/
public const CODE_INVALID_DATA = 401;
/**
* @var int CODE_NOT_FOUND The entity doesn't exist or can't be accessed
*/
public const CODE_NOT_FOUND = 404;
/**
* @var string TYPE_HAS_NOT_BEEN_FOUND
* The entity type doesn't exist or can't be accessed
*/
public const TYPE_HAS_NOT_BEEN_FOUND = 'The entity type has not been found.';
/**
* @var string TYPE_HAS_INCOMPATIBLE_VERSION The version hashes are different
*/
public const TYPE_HAS_INCOMPATIBLE_VERSION = 'The entity type has an incompatible version.';
/**
* @var string READ_LIST_ENTITY_ID
* "ID" used to perform list requests in the
* {@see EntityResource}. Should be refactored later.
*/
public const READ_LIST_ENTITY_ID = '0';
/**
* @var string PING_PARAMETER
*/
public const PING_PARAMETER = 'ping';
/**
* @var \Drupal\Core\Entity\EntityTypeBundleInfo
*/
protected $entityTypeBundleInfo;
/**
* @var \Drupal\Core\Entity\EntityTypeManager
*/
protected $entityTypeManager;
/**
* @var \Drupal\Core\Render\Renderer
*/
protected $renderedManager;
/**
* @var \Drupal\Core\Entity\EntityRepositoryInterface
*/
protected $entityRepository;
/**
* Constructs an object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance
* @param string $plugin_id
* The plugin_id for the plugin instance
* @param mixed $plugin_definition
* The plugin implementation definition
* @param array $serializer_formats
* The available serialization formats
* @param \Psr\Log\LoggerInterface $logger
* A logger instance
* @param \Drupal\Core\Entity\EntityTypeBundleInfo $entity_type_bundle_info
* An entity type bundle info instance
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* An entity type manager instance
* @param \Drupal\Core\Render\Renderer $render_manager
* A rendered instance
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
* The entity repository interface
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, LoggerInterface $logger, EntityTypeBundleInfo $entity_type_bundle_info, EntityTypeManagerInterface $entity_type_manager, Renderer $render_manager, EntityRepositoryInterface $entity_repository) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
$this->entityTypeBundleInfo = $entity_type_bundle_info;
$this->entityTypeManager = $entity_type_manager;
$this->renderedManager = $render_manager;
$this->entityRepository = $entity_repository;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container
->getParameter('serializer.formats'), $container
->get('logger.factory')
->get('rest'), $container
->get('entity_type.bundle.info'), $container
->get('entity_type.manager'), $container
->get('renderer'), $container
->get('entity.repository'));
}
/**
* @param string $method
*
* @return string a URL the Sync Core can use to ping this site and check if all methods work (so inbound traffic
* is allowed from the Sync Core)
*/
public static function getInternalPingUrl($method) {
return DrupalApplication::get()
->getRestUrl(self::PING_PARAMETER, self::PING_PARAMETER, self::PING_PARAMETER, self::PING_PARAMETER, 'POST' === $method ? null : self::PING_PARAMETER);
}
/**
* Responds to entity GET requests.
*
* @param string $entity_type
* The name of an entity type
* @param string $entity_bundle
* The name of an entity bundle
* @param string $entity_uuid
* The uuid of an entity
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\cms_content_sync\Exception\SyncException
*
* @return \Symfony\Component\HttpFoundation\Response
* A list of entities of the given type and bundle
*/
public function get($entity_type, $entity_bundle, $entity_uuid) {
return new ResourceResponse([
'message' => t(self::TYPE_HAS_NOT_BEEN_FOUND)
->render(),
], self::CODE_NOT_FOUND);
}
/**
* Responds to entity PATCH requests.
*
* @param string $api
* The used content sync api
* @param string $entity_type
* The name of an entity type
* @param string $entity_bundle
* The name of an entity bundle
* @param string $entity_type_version
* The version of the entity type to compare ours against
* @param string $entity_uuid
* The uuid of an entity
* @param array $data
* The data to be stored in the entity
*
* @throws \Drupal\cms_content_sync\Exception\SyncException
*
* @return \Symfony\Component\HttpFoundation\Response
* A list of entities of the given type and bundle
*/
public function patch($api, $entity_type, $entity_bundle, $entity_type_version, $entity_uuid, array $data) {
return $this
->handleIncomingEntity($api, $entity_type, $entity_bundle, $entity_type_version, $data, SyncIntent::ACTION_UPDATE);
}
/**
* Responds to entity DELETE requests.
*
* @param string $api
* The used content sync api
* @param string $entity_type
* The name of an entity type
* @param string $entity_bundle
* The name of an entity bundle
* @param string $entity_type_version
* The version of the entity type
* @param string $entity_uuid
* The uuid of an entity
*
* @throws \Drupal\cms_content_sync\Exception\SyncException
*
* @return \Symfony\Component\HttpFoundation\Response
* A list of entities of the given type and bundle
*/
public function delete($api, $entity_type, $entity_bundle, $entity_type_version, $entity_uuid) {
return $this
->handleIncomingEntity($api, $entity_type, $entity_bundle, $entity_type_version, [
'uuid' => $entity_uuid,
'id' => $entity_uuid,
], SyncIntent::ACTION_DELETE);
}
/**
* Responds to entity POST requests.
*
* @param string $api
* The used content sync api
* @param string $entity_type
* The posted entity type
* @param string $entity_bundle
* The name of an entity bundle
* @param string $entity_type_version
* The version of the entity type
* @param array $data
* The data to be stored in the entity
*
* @throws \Drupal\cms_content_sync\Exception\SyncException
*
* @return \Symfony\Component\HttpFoundation\Response
* A list of entities of the given type and bundle
*/
public function post($api, $entity_type, $entity_bundle, $entity_type_version, array $data) {
return $this
->handleIncomingEntity($api, $entity_type, $entity_bundle, $entity_type_version, $data, SyncIntent::ACTION_CREATE);
}
/**
* Save that the pull for the given entity failed.
*
* @param string $pool_id
* The Pool ID
* @param $entity_type
* The Entity Type ID
* @param $entity_bundle
* The bundle name
* @param $entity_type_version
* The requested entity type version
* @param $entity_uuid
* The entity UUID
* @param $failure_reason
* @param $action
* @param $reason
* @param null $flow_id
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\Entity\EntityStorageException
*/
protected function saveFailedPull($pool_id, $entity_type, $entity_bundle, $entity_type_version, $entity_uuid, $failure_reason, $action, $reason, $flow_id = null) {
$entity_status = EntityStatus::getInfoForEntity($entity_type, $entity_uuid, $flow_id, $pool_id);
if (!$entity_status) {
$entity_status = EntityStatus::create([
'flow' => $flow_id ? $flow_id : EntityStatus::FLOW_NO_FLOW,
'pool' => $pool_id,
'entity_type' => $entity_type,
'entity_uuid' => $entity_uuid,
'entity_type_version' => $entity_type_version,
'flags' => 0,
'source_url' => null,
]);
}
$soft_fails = [
PullIntent::PULL_FAILED_UNKNOWN_POOL,
PullIntent::PULL_FAILED_NO_FLOW,
PullIntent::PULL_FAILED_HANDLER_DENIED,
];
$soft = in_array($failure_reason, $soft_fails);
$entity_status
->didPullFail(true, $soft, [
'error' => $failure_reason,
'action' => $action,
'reason' => $reason,
'bundle' => $entity_bundle,
]);
$entity_status
->save();
}
/**
* @param string $api
* The API {@see Flow}
* @param string $entity_type_name
* The entity type of the processed entity
* @param string $entity_bundle
* The bundle of the processed entity
* @param string $entity_type_version
* The version the config was saved for
* @param array $data
* For {@see ::ACTION_CREATE} and
* {@see ::ACTION_UPDATE}: the data for the entity. Will
* be passed to {@see SyncIntent}.
* @param string $action
* The {@see ::ACTION_*} to be performed on the entity
*
* @throws \Drupal\cms_content_sync\Exception\SyncException
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\Entity\EntityStorageException
*
* @return \Symfony\Component\HttpFoundation\Response the result (error,
* ignorance or success)
*/
private function handleIncomingEntity($api, $entity_type_name, $entity_bundle, $entity_type_version, array $data, $action) {
if (self::PING_PARAMETER === $api) {
return new ResourceResponse(SyncIntent::ACTION_DELETE === $action ? null : [
'pong' => true,
]);
}
$entity_types = $this->entityTypeBundleInfo
->getAllBundleInfo();
if (empty($entity_types[$entity_type_name])) {
return new ResourceResponse(SyncIntent::ACTION_DELETE == $action ? null : [
'message' => t(self::TYPE_HAS_NOT_BEEN_FOUND)
->render(),
], self::CODE_NOT_FOUND);
}
$is_dependency = isset($_GET['is_dependency']) && 'true' == $_GET['is_dependency'];
$is_manual = isset($_GET['is_manual']) && 'true' == $_GET['is_manual'];
$reason = $is_dependency ? PullIntent::PULL_AS_DEPENDENCY : ($is_manual ? PullIntent::PULL_MANUALLY : PullIntent::PULL_AUTOMATICALLY);
$entity_uuid = null;
$pool = Pool::getAll()[$api];
if (empty($pool)) {
\Drupal::logger('cms_content_sync')
->warning('@not PULL @action @entity_type:@bundle @uuid @reason: @message<br>Flow: @flow_id', [
'@reason' => $reason,
'@action' => $action,
'@entity_type' => $entity_type_name,
'@bundle' => $entity_bundle,
'@uuid' => $entity_uuid,
'@not' => 'NO',
'@flow_id' => $api,
'@message' => t('No pool config matches this request (@api).', [
'@api' => $api,
])
->render(),
]);
$this
->saveFailedPull($api, $entity_type_name, $entity_bundle, $entity_type_version, $entity_uuid, PullIntent::PULL_FAILED_UNKNOWN_POOL, $action, $reason);
return new ResourceResponse(SyncIntent::ACTION_DELETE == $action ? null : [
'message' => t(self::TYPE_HAS_NOT_BEEN_FOUND)
->render(),
], self::CODE_NOT_FOUND);
}
$flow = Flow::getFlowForApiAndEntityType($pool, $entity_type_name, $entity_bundle, $reason, $action);
// Deletion requests will not provide the "is_dependency" query parameter.
if (empty($flow) && SyncIntent::ACTION_DELETE == $action && PullIntent::PULL_AS_DEPENDENCY != $reason) {
$flow = Flow::getFlowForApiAndEntityType($pool, $entity_type_name, $entity_bundle, PullIntent::PULL_AS_DEPENDENCY, $action);
if (!empty($flow)) {
$reason = PullIntent::PULL_AS_DEPENDENCY;
}
}
if (empty($flow)) {
\Drupal::logger('cms_content_sync')
->notice('@not PULL @action @entity_type:@bundle @uuid @reason: @message', [
'@reason' => $reason,
'@action' => $action,
'@entity_type' => $entity_type_name,
'@bundle' => $entity_bundle,
'@uuid' => $entity_uuid,
'@not' => 'NO',
'@message' => t('No synchronization config matches this request (dependency: @dependency, manual: @manual).', [
'@dependency' => $is_dependency ? 'YES' : 'NO',
'@manual' => $is_manual ? 'YES' : 'NO',
])
->render(),
]);
$this
->saveFailedPull($api, $entity_type_name, $entity_bundle, $entity_type_version, $entity_uuid, PullIntent::PULL_FAILED_NO_FLOW, $action, $reason);
return new ResourceResponse(SyncIntent::ACTION_DELETE == $action ? null : [
'message' => t(self::TYPE_HAS_NOT_BEEN_FOUND)
->render(),
], self::CODE_NOT_FOUND);
}
$operation = $pool
->getClient()
->getSyndicationService()
->handlePull($flow->id, $entity_type_name, $entity_bundle, $data);
// DELETE requests only give the ID of the config, not their UUID. So we need to grab the UUID from our local
// database before continuing.
if (SyncIntent::ACTION_DELETE === $action && EntityHandlerPluginManager::isEntityTypeConfiguration($entity_type_name)) {
$entity_uuid = \Drupal::entityTypeManager()
->getStorage($entity_type_name)
->load($operation
->getId())
->uuid();
}
else {
$entity_uuid = $operation
->getUuid();
}
$local_version = Flow::getEntityTypeVersion($entity_type_name, $entity_bundle);
// Allow DELETE requests- when an entity is deleted, the entity type definition may have changed in the meantime
// but this doesn't prevent us from deleting it. The version is only important for creations and updates.
if ($entity_type_version != $local_version && SyncIntent::ACTION_DELETE != $action) {
\Drupal::logger('cms_content_sync')
->warning('@not PULL @action @entity_type:@bundle @uuid @reason: @message<br>Flow: @flow_id | Pool: @pool_id', [
'@reason' => $reason,
'@action' => $action,
'@entity_type' => $entity_type_name,
'@bundle' => $entity_bundle,
'@uuid' => $entity_uuid,
'@not' => 'NO',
'@flow_id' => $api,
'@pool_id' => $pool
->id(),
'@message' => t('The requested entity type version @requested doesn\'t match the local entity type version @local.', [
'@requested' => $entity_type_version,
'@local' => $local_version,
])
->render(),
]);
$this
->saveFailedPull($api, $entity_type_name, $entity_bundle, $entity_type_version, $entity_uuid, PullIntent::PULL_FAILED_DIFFERENT_VERSION, $action, $reason, $flow->id);
return new ResourceResponse([
'message' => t(self::TYPE_HAS_INCOMPATIBLE_VERSION)
->render(),
], self::CODE_NOT_FOUND);
}
try {
$intent = new PullIntent($flow, $pool, $reason, $action, $entity_type_name, $entity_bundle, $operation);
$status = $intent
->execute();
} catch (SyncException $e) {
$message = $e->parentException ? $e->parentException
->getMessage() : ($e->errorCode == $e
->getMessage() ? '' : $e
->getMessage());
if ($message) {
$message = t('Internal error @code: @message', [
'@code' => $e->errorCode,
'@message' => $message,
])
->render();
}
else {
$message = t('Internal error @code', [
'@code' => $e->errorCode,
])
->render();
}
\Drupal::logger('cms_content_sync')
->error('@not PULL @action @entity_type:@bundle @uuid @reason: @message' . "\n" . '@trace' . "\n" . '@request_body<br>Flow: @flow_id | Pool: @pool_id', [
'@reason' => $reason,
'@action' => $action,
'@entity_type' => $entity_type_name,
'@bundle' => $entity_bundle,
'@uuid' => $entity_uuid,
'@not' => 'NO',
'@flow_id' => $api,
'@pool_id' => $pool
->id(),
'@message' => $message,
'@trace' => ($e->parentException ? $e->parentException
->getTraceAsString() . "\n\n\n" : '') . $e
->getTraceAsString(),
'@request_body' => json_encode($data),
]);
$this
->saveFailedPull($api, $entity_type_name, $entity_bundle, $entity_type_version, $entity_uuid, PullIntent::PULL_FAILED_CONTENT_SYNC_ERROR, $action, $reason, $flow->id);
return new ResourceResponse(SyncIntent::ACTION_DELETE == $action ? null : [
'message' => t('SyncException @code: @message', [
'@code' => $e->errorCode,
'@message' => $e
->getMessage(),
])
->render(),
'code' => $e->errorCode,
], 500);
} catch (\Exception $e) {
$message = $e
->getMessage();
\Drupal::logger('cms_content_sync')
->error('@not PULL @action @entity_type:@bundle @uuid @reason: @message' . "\n" . '@trace' . "\n" . '@request_body<br>Flow: @flow_id | Pool: @pool_id', [
'@reason' => $reason,
'@action' => $action,
'@entity_type' => $entity_type_name,
'@bundle' => $entity_bundle,
'@uuid' => $entity_uuid,
'@not' => 'NO',
'@flow_id' => $api,
'@pool_id' => $pool
->id(),
'@message' => $message,
'@trace' => $e
->getTraceAsString(),
'@request_body' => json_encode($data),
]);
$this
->saveFailedPull($api, $entity_type_name, $entity_bundle, $entity_type_version, $entity_uuid, PullIntent::PULL_FAILED_INTERNAL_ERROR, $action, $reason, $flow->id);
return new ResourceResponse(SyncIntent::ACTION_DELETE == $action ? null : [
'message' => t('Unexpected error: @message', [
'@message' => $e
->getMessage(),
])
->render(),
], 500);
}
if (!$status) {
$this
->saveFailedPull($api, $entity_type_name, $entity_bundle, $entity_type_version, $entity_uuid, PullIntent::PULL_FAILED_HANDLER_DENIED, $action, $reason, $flow->id);
}
if ($status) {
$entity = $intent
->getEntity();
$url = null;
if ($entity && $entity
->hasLinkTemplate('canonical')) {
try {
$url = $entity
->toUrl('canonical', [
'absolute' => true,
])
->toString(true)
->getGeneratedUrl();
} catch (\Exception $e) {
throw new SyncException(SyncException::CODE_UNEXPECTED_EXCEPTION, $e);
}
}
$response_body = $operation
->getResponseBody($url);
// If we send data for DELETE requests, the Drupal Serializer will throw
// a random error. So we just leave the body empty then.
return new ModifiedResourceResponse(SyncIntent::ACTION_DELETE == $action ? null : $response_body);
}
return new ResourceResponse(SyncIntent::ACTION_DELETE == $action ? null : [
'message' => t('Entity is not configured to be pulled yet.')
->render(),
], 404);
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
DependencySerializationTrait:: |
protected | property | An array of entity type IDs keyed by the property name of their storages. | |
DependencySerializationTrait:: |
protected | property | An array of service IDs keyed by property name used for serialization. | |
DependencySerializationTrait:: |
public | function | 1 | |
DependencySerializationTrait:: |
public | function | 2 | |
EntityResource:: |
protected | property | ||
EntityResource:: |
protected | property | ||
EntityResource:: |
protected | property | ||
EntityResource:: |
protected | property | ||
EntityResource:: |
public | constant | ||
EntityResource:: |
public | constant | ||
EntityResource:: |
public static | function |
Creates an instance of the plugin. Overrides ResourceBase:: |
|
EntityResource:: |
public | function | Responds to entity DELETE requests. | |
EntityResource:: |
public | function | Responds to entity GET requests. | |
EntityResource:: |
public static | function | ||
EntityResource:: |
private | function | ||
EntityResource:: |
public | function | Responds to entity PATCH requests. | |
EntityResource:: |
public | constant | ||
EntityResource:: |
public | function | Responds to entity POST requests. | |
EntityResource:: |
public | constant | "ID" used to perform list requests in the { | |
EntityResource:: |
protected | function | Save that the pull for the given entity failed. | |
EntityResource:: |
public | constant | ||
EntityResource:: |
public | constant | The entity type doesn't exist or can't be accessed | |
EntityResource:: |
public | function |
Constructs an object. Overrides ResourceBase:: |
|
MessengerTrait:: |
protected | property | The messenger. | 29 |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
MessengerTrait:: |
public | function | Sets the messenger. | |
PluginBase:: |
protected | property | Configuration information passed into the plugin. | 1 |
PluginBase:: |
protected | property | The plugin implementation definition. | 1 |
PluginBase:: |
protected | property | The plugin_id. | |
PluginBase:: |
constant | A string which is used to separate base plugin IDs from the derivative ID. | ||
PluginBase:: |
public | function |
Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the definition of the plugin implementation. Overrides PluginInspectionInterface:: |
3 |
PluginBase:: |
public | function |
Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface:: |
|
PluginBase:: |
public | function | Determines if the plugin is configurable. | |
ResourceBase:: |
protected | property | A logger instance. | |
ResourceBase:: |
protected | property | The available serialization formats. | |
ResourceBase:: |
public | function |
Returns the available HTTP request methods on this plugin. Overrides ResourceInterface:: |
1 |
ResourceBase:: |
protected | function | Gets the base route for a particular method. | 2 |
ResourceBase:: |
protected | function | Gets the base route requirements for a particular method. | 1 |
ResourceBase:: |
public | function |
Implements ResourceInterface::permissions(). Overrides ResourceInterface:: |
2 |
ResourceBase:: |
protected | function | Provides predefined HTTP request methods. | |
ResourceBase:: |
public | function |
Returns a collection of routes with URL path information for the resource. Overrides ResourceInterface:: |
|
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. |