PageRenderTime 42ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/core/modules/serialization/src/Normalizer/FieldableEntityNormalizerTrait.php

http://github.com/drupal/drupal
PHP | 265 lines | 111 code | 30 blank | 124 comment | 15 complexity | d88ed9c3fb5df6936a0c5eb60fa10b1f MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. namespace Drupal\serialization\Normalizer;
  3. use Drupal\Core\Entity\EntityTypeInterface;
  4. use Drupal\Core\Entity\FieldableEntityInterface;
  5. use Drupal\Core\Field\FieldItemInterface;
  6. use Drupal\Core\Field\TypedData\FieldItemDataDefinitionInterface;
  7. use Symfony\Component\Serializer\Exception\UnexpectedValueException;
  8. /**
  9. * A trait for providing fieldable entity normalization/denormalization methods.
  10. *
  11. * @todo Move this into a FieldableEntityNormalizer in Drupal 9. This is a trait
  12. * used in \Drupal\serialization\Normalizer\EntityNormalizer to maintain BC.
  13. * @see https://www.drupal.org/node/2834734
  14. */
  15. trait FieldableEntityNormalizerTrait {
  16. /**
  17. * The entity field manager.
  18. *
  19. * @var \Drupal\Core\Entity\EntityFieldManagerInterface
  20. */
  21. protected $entityFieldManager;
  22. /**
  23. * The entity type manager.
  24. *
  25. * @var \Drupal\Core\Entity\EntityTypeManagerInterface
  26. */
  27. protected $entityTypeManager;
  28. /**
  29. * The entity type repository.
  30. *
  31. * @var \Drupal\Core\Entity\EntityTypeRepositoryInterface
  32. */
  33. protected $entityTypeRepository;
  34. /**
  35. * Determines the entity type ID to denormalize as.
  36. *
  37. * @param string $class
  38. * The entity type class to be denormalized to.
  39. * @param array $context
  40. * The serialization context data.
  41. *
  42. * @return string
  43. * The entity type ID.
  44. */
  45. protected function determineEntityTypeId($class, $context) {
  46. // Get the entity type ID while letting context override the $class param.
  47. return !empty($context['entity_type']) ? $context['entity_type'] : $this->getEntityTypeRepository()->getEntityTypeFromClass($class);
  48. }
  49. /**
  50. * Gets the entity type definition.
  51. *
  52. * @param string $entity_type_id
  53. * The entity type ID to load the definition for.
  54. *
  55. * @return \Drupal\Core\Entity\EntityTypeInterface
  56. * The loaded entity type definition.
  57. *
  58. * @throws \Symfony\Component\Serializer\Exception\UnexpectedValueException
  59. */
  60. protected function getEntityTypeDefinition($entity_type_id) {
  61. /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type_definition */
  62. // Get the entity type definition.
  63. $entity_type_definition = $this->getEntityTypeManager()->getDefinition($entity_type_id, FALSE);
  64. // Don't try to create an entity without an entity type id.
  65. if (!$entity_type_definition) {
  66. throw new UnexpectedValueException(sprintf('The specified entity type "%s" does not exist. A valid entity type is required for denormalization', $entity_type_id));
  67. }
  68. return $entity_type_definition;
  69. }
  70. /**
  71. * Denormalizes the bundle property so entity creation can use it.
  72. *
  73. * @param array $data
  74. * The data being denormalized.
  75. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type_definition
  76. * The entity type definition.
  77. *
  78. * @throws \Symfony\Component\Serializer\Exception\UnexpectedValueException
  79. *
  80. * @return string
  81. * The valid bundle name.
  82. */
  83. protected function extractBundleData(array &$data, EntityTypeInterface $entity_type_definition) {
  84. $bundle_key = $entity_type_definition->getKey('bundle');
  85. // Get the base field definitions for this entity type.
  86. $base_field_definitions = $this->getEntityFieldManager()->getBaseFieldDefinitions($entity_type_definition->id());
  87. // Get the ID key from the base field definition for the bundle key or
  88. // default to 'value'.
  89. $key_id = isset($base_field_definitions[$bundle_key]) ? $base_field_definitions[$bundle_key]->getFieldStorageDefinition()->getMainPropertyName() : 'value';
  90. // Normalize the bundle if it is not explicitly set.
  91. $bundle_value = isset($data[$bundle_key][0][$key_id]) ? $data[$bundle_key][0][$key_id] : (isset($data[$bundle_key]) ? $data[$bundle_key] : NULL);
  92. // Unset the bundle from the data.
  93. unset($data[$bundle_key]);
  94. // Get the bundle entity type from the entity type definition.
  95. $bundle_type_id = $entity_type_definition->getBundleEntityType();
  96. $bundle_types = $bundle_type_id ? $this->getEntityTypeManager()->getStorage($bundle_type_id)->getQuery()->execute() : [];
  97. // Make sure a bundle has been provided.
  98. if (!is_string($bundle_value)) {
  99. throw new UnexpectedValueException(sprintf('Could not determine entity type bundle: "%s" field is missing.', $bundle_key));
  100. }
  101. // Make sure the submitted bundle is a valid bundle for the entity type.
  102. if ($bundle_types && !in_array($bundle_value, $bundle_types)) {
  103. throw new UnexpectedValueException(sprintf('"%s" is not a valid bundle type for denormalization.', $bundle_value));
  104. }
  105. return [$bundle_key => $bundle_value];
  106. }
  107. /**
  108. * Denormalizes entity data by denormalizing each field individually.
  109. *
  110. * @param array $data
  111. * The data to denormalize.
  112. * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
  113. * The fieldable entity to set field values for.
  114. * @param string $format
  115. * The serialization format.
  116. * @param array $context
  117. * The context data.
  118. */
  119. protected function denormalizeFieldData(array $data, FieldableEntityInterface $entity, $format, array $context) {
  120. foreach ($data as $field_name => $field_data) {
  121. $field_item_list = $entity->get($field_name);
  122. // Remove any values that were set as a part of entity creation (e.g
  123. // uuid). If the incoming field data is set to an empty array, this will
  124. // also have the effect of emptying the field in REST module.
  125. $field_item_list->setValue([]);
  126. $field_item_list_class = get_class($field_item_list);
  127. if ($field_data) {
  128. // The field instance must be passed in the context so that the field
  129. // denormalizer can update field values for the parent entity.
  130. $context['target_instance'] = $field_item_list;
  131. $this->serializer->denormalize($field_data, $field_item_list_class, $format, $context);
  132. }
  133. }
  134. }
  135. /**
  136. * Returns the entity type repository.
  137. *
  138. * @return \Drupal\Core\Entity\EntityTypeRepositoryInterface
  139. * The entity type repository.
  140. */
  141. protected function getEntityTypeRepository() {
  142. if (!$this->entityTypeRepository) {
  143. @trigger_error('The entityTypeRepository property must be set on the FieldEntityNormalizerTrait, it is required before Drupal 9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
  144. $this->entityTypeRepository = \Drupal::service('entity_type.repository');
  145. }
  146. return $this->entityTypeRepository;
  147. }
  148. /**
  149. * Returns the entity field manager.
  150. *
  151. * @return \Drupal\Core\Entity\EntityFieldManagerInterface
  152. * The entity field manager.
  153. */
  154. protected function getEntityFieldManager() {
  155. if (!$this->entityFieldManager) {
  156. @trigger_error('The entityFieldManager property must be set on the FieldEntityNormalizerTrait, it is required before Drupal 9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
  157. $this->entityFieldManager = \Drupal::service('entity_field.manager');
  158. }
  159. return $this->entityFieldManager;
  160. }
  161. /**
  162. * Returns the entity type manager.
  163. *
  164. * @return \Drupal\Core\Entity\EntityTypeManagerInterface
  165. * The entity type manager.
  166. */
  167. protected function getEntityTypeManager() {
  168. if (!$this->entityTypeManager) {
  169. @trigger_error('The entityTypeManager property must be set on the FieldEntityNormalizerTrait, it is required before Drupal 9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
  170. $this->entityTypeManager = \Drupal::service('entity_type.manager');
  171. }
  172. return $this->entityTypeManager;
  173. }
  174. /**
  175. * Build the field item value using the incoming data.
  176. *
  177. * Most normalizers that extend this class can simply use this method to
  178. * construct the denormalized value without having to override denormalize()
  179. * and reimplementing its validation logic or its call to set the field value.
  180. *
  181. * It's recommended to not override this and instead provide a (de)normalizer
  182. * at the DataType level.
  183. *
  184. * @param mixed $data
  185. * The incoming data for this field item.
  186. * @param array $context
  187. * The context passed into the Normalizer.
  188. *
  189. * @return mixed
  190. * The value to use in Entity::setValue().
  191. */
  192. protected function constructValue($data, $context) {
  193. $field_item = $context['target_instance'];
  194. // Get the property definitions.
  195. assert($field_item instanceof FieldItemInterface);
  196. $field_definition = $field_item->getFieldDefinition();
  197. $item_definition = $field_definition->getItemDefinition();
  198. assert($item_definition instanceof FieldItemDataDefinitionInterface);
  199. $property_definitions = $item_definition->getPropertyDefinitions();
  200. $serialized_property_names = $this->getCustomSerializedPropertyNames($field_item);
  201. $denormalize_property = function ($property_name, $property_value, $property_value_class, $context) use ($serialized_property_names) {
  202. if ($this->serializer->supportsDenormalization($property_value, $property_value_class, NULL, $context)) {
  203. return $this->serializer->denormalize($property_value, $property_value_class, NULL, $context);
  204. }
  205. else {
  206. if (in_array($property_name, $serialized_property_names, TRUE)) {
  207. $property_value = serialize($property_value);
  208. }
  209. return $property_value;
  210. }
  211. };
  212. if (!is_array($data)) {
  213. $property_value = $data;
  214. $property_name = $item_definition->getMainPropertyName();
  215. $property_value_class = $property_definitions[$property_name]->getClass();
  216. return $denormalize_property($property_name, $property_value, $property_value_class, $context);
  217. }
  218. $data_internal = [];
  219. if (!empty($property_definitions)) {
  220. foreach ($property_definitions as $property_name => $property_definition) {
  221. // Not every property is required to be sent.
  222. if (!array_key_exists($property_name, $data)) {
  223. continue;
  224. }
  225. $property_value = $data[$property_name];
  226. $property_value_class = $property_definition->getClass();
  227. $data_internal[$property_name] = $denormalize_property($property_name, $property_value, $property_value_class, $context);
  228. }
  229. }
  230. else {
  231. $data_internal = $data;
  232. }
  233. return $data_internal;
  234. }
  235. }