FieldItemList.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. <?php
  2. namespace Drupal\Core\Field;
  3. use Drupal\Core\Access\AccessResult;
  4. use Drupal\Core\Entity\FieldableEntityInterface;
  5. use Drupal\Core\Form\FormStateInterface;
  6. use Drupal\Core\Language\LanguageInterface;
  7. use Drupal\Core\Session\AccountInterface;
  8. use Drupal\Core\TypedData\DataDefinitionInterface;
  9. use Drupal\Core\TypedData\Plugin\DataType\ItemList;
  10. /**
  11. * Represents an entity field; that is, a list of field item objects.
  12. *
  13. * An entity field is a list of field items, each containing a set of
  14. * properties. Note that even single-valued entity fields are represented as
  15. * list of field items, however for easy access to the contained item the entity
  16. * field delegates __get() and __set() calls directly to the first item.
  17. */
  18. class FieldItemList extends ItemList implements FieldItemListInterface {
  19. /**
  20. * Numerically indexed array of field items.
  21. *
  22. * @var \Drupal\Core\Field\FieldItemInterface[]
  23. */
  24. protected $list = [];
  25. /**
  26. * The langcode of the field values held in the object.
  27. *
  28. * @var string
  29. */
  30. protected $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED;
  31. /**
  32. * {@inheritdoc}
  33. */
  34. protected function createItem($offset = 0, $value = NULL) {
  35. return \Drupal::service('plugin.manager.field.field_type')->createFieldItem($this, $offset, $value);
  36. }
  37. /**
  38. * {@inheritdoc}
  39. */
  40. public function getEntity() {
  41. // The "parent" is the TypedData object for the entity, we need to unwrap
  42. // the actual entity.
  43. return $this->getParent()->getValue();
  44. }
  45. /**
  46. * {@inheritdoc}
  47. */
  48. public function setLangcode($langcode) {
  49. $this->langcode = $langcode;
  50. }
  51. /**
  52. * {@inheritdoc}
  53. */
  54. public function getLangcode() {
  55. return $this->langcode;
  56. }
  57. /**
  58. * {@inheritdoc}
  59. */
  60. public function getFieldDefinition() {
  61. return $this->definition;
  62. }
  63. /**
  64. * {@inheritdoc}
  65. */
  66. public function getSettings() {
  67. return $this->definition->getSettings();
  68. }
  69. /**
  70. * {@inheritdoc}
  71. */
  72. public function getSetting($setting_name) {
  73. return $this->definition->getSetting($setting_name);
  74. }
  75. /**
  76. * {@inheritdoc}
  77. */
  78. public function filterEmptyItems() {
  79. $this->filter(function ($item) {
  80. return !$item->isEmpty();
  81. });
  82. return $this;
  83. }
  84. /**
  85. * {@inheritdoc}
  86. */
  87. public function setValue($values, $notify = TRUE) {
  88. // Support passing in only the value of the first item, either as a literal
  89. // (value of the first property) or as an array of properties.
  90. if (isset($values) && (!is_array($values) || (!empty($values) && !is_numeric(current(array_keys($values)))))) {
  91. $values = [0 => $values];
  92. }
  93. parent::setValue($values, $notify);
  94. }
  95. /**
  96. * {@inheritdoc}
  97. */
  98. public function __get($property_name) {
  99. // For empty fields, $entity->field->property is NULL.
  100. if ($item = $this->first()) {
  101. return $item->__get($property_name);
  102. }
  103. }
  104. /**
  105. * {@inheritdoc}
  106. */
  107. public function __set($property_name, $value) {
  108. // For empty fields, $entity->field->property = $value automatically
  109. // creates the item before assigning the value.
  110. $item = $this->first() ?: $this->appendItem();
  111. $item->__set($property_name, $value);
  112. }
  113. /**
  114. * {@inheritdoc}
  115. */
  116. public function __isset($property_name) {
  117. if ($item = $this->first()) {
  118. return $item->__isset($property_name);
  119. }
  120. return FALSE;
  121. }
  122. /**
  123. * {@inheritdoc}
  124. */
  125. public function __unset($property_name) {
  126. if ($item = $this->first()) {
  127. $item->__unset($property_name);
  128. }
  129. }
  130. /**
  131. * {@inheritdoc}
  132. */
  133. public function access($operation = 'view', AccountInterface $account = NULL, $return_as_object = FALSE) {
  134. $access_control_handler = \Drupal::entityManager()->getAccessControlHandler($this->getEntity()->getEntityTypeId());
  135. return $access_control_handler->fieldAccess($operation, $this->getFieldDefinition(), $account, $this, $return_as_object);
  136. }
  137. /**
  138. * {@inheritdoc}
  139. */
  140. public function defaultAccess($operation = 'view', AccountInterface $account = NULL) {
  141. // Grant access per default.
  142. return AccessResult::allowed();
  143. }
  144. /**
  145. * {@inheritdoc}
  146. */
  147. public function applyDefaultValue($notify = TRUE) {
  148. if ($value = $this->getFieldDefinition()->getDefaultValue($this->getEntity())) {
  149. $this->setValue($value, $notify);
  150. }
  151. else {
  152. // Create one field item and give it a chance to apply its defaults.
  153. // Remove it if this ended up doing nothing.
  154. // @todo Having to create an item in case it wants to set a value is
  155. // absurd. Remove that in https://www.drupal.org/node/2356623.
  156. $item = $this->first() ?: $this->appendItem();
  157. $item->applyDefaultValue(FALSE);
  158. $this->filterEmptyItems();
  159. }
  160. return $this;
  161. }
  162. /**
  163. * {@inheritdoc}
  164. */
  165. public function preSave() {
  166. // Filter out empty items.
  167. $this->filterEmptyItems();
  168. $this->delegateMethod('preSave');
  169. }
  170. /**
  171. * {@inheritdoc}
  172. */
  173. public function postSave($update) {
  174. $result = $this->delegateMethod('postSave', $update);
  175. return (bool) array_filter($result);
  176. }
  177. /**
  178. * {@inheritdoc}
  179. */
  180. public function delete() {
  181. $this->delegateMethod('delete');
  182. }
  183. /**
  184. * {@inheritdoc}
  185. */
  186. public function deleteRevision() {
  187. $this->delegateMethod('deleteRevision');
  188. }
  189. /**
  190. * Calls a method on each FieldItem.
  191. *
  192. * Any argument passed will be forwarded to the invoked method.
  193. *
  194. * @param string $method
  195. * The name of the method to be invoked.
  196. *
  197. * @return array
  198. * An array of results keyed by delta.
  199. */
  200. protected function delegateMethod($method) {
  201. $result = [];
  202. $args = array_slice(func_get_args(), 1);
  203. foreach ($this->list as $delta => $item) {
  204. // call_user_func_array() is way slower than a direct call so we avoid
  205. // using it if have no parameters.
  206. $result[$delta] = $args ? call_user_func_array([$item, $method], $args) : $item->{$method}();
  207. }
  208. return $result;
  209. }
  210. /**
  211. * {@inheritdoc}
  212. */
  213. public function view($display_options = []) {
  214. $view_builder = \Drupal::entityManager()->getViewBuilder($this->getEntity()->getEntityTypeId());
  215. return $view_builder->viewField($this, $display_options);
  216. }
  217. /**
  218. * {@inheritdoc}
  219. */
  220. public function generateSampleItems($count = 1) {
  221. $field_definition = $this->getFieldDefinition();
  222. $field_type_class = $field_definition->getItemDefinition()->getClass();
  223. for ($delta = 0; $delta < $count; $delta++) {
  224. $values[$delta] = $field_type_class::generateSampleValue($field_definition);
  225. }
  226. $this->setValue($values);
  227. }
  228. /**
  229. * {@inheritdoc}
  230. */
  231. public function getConstraints() {
  232. $constraints = parent::getConstraints();
  233. // Check that the number of values doesn't exceed the field cardinality. For
  234. // form submitted values, this can only happen with 'multiple value'
  235. // widgets.
  236. $cardinality = $this->getFieldDefinition()->getFieldStorageDefinition()->getCardinality();
  237. if ($cardinality != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
  238. $constraints[] = $this->getTypedDataManager()
  239. ->getValidationConstraintManager()
  240. ->create('Count', [
  241. 'max' => $cardinality,
  242. 'maxMessage' => t('%name: this field cannot hold more than @count values.', ['%name' => $this->getFieldDefinition()->getLabel(), '@count' => $cardinality]),
  243. ]);
  244. }
  245. return $constraints;
  246. }
  247. /**
  248. * {@inheritdoc}
  249. */
  250. public function defaultValuesForm(array &$form, FormStateInterface $form_state) {
  251. if (empty($this->getFieldDefinition()->getDefaultValueCallback())) {
  252. if ($widget = $this->defaultValueWidget($form_state)) {
  253. // Place the input in a separate place in the submitted values tree.
  254. $element = ['#parents' => ['default_value_input']];
  255. $element += $widget->form($this, $element, $form_state);
  256. return $element;
  257. }
  258. else {
  259. return ['#markup' => $this->t('No widget available for: %type.', ['%type' => $this->getFieldDefinition()->getType()])];
  260. }
  261. }
  262. }
  263. /**
  264. * {@inheritdoc}
  265. */
  266. public function defaultValuesFormValidate(array $element, array &$form, FormStateInterface $form_state) {
  267. // Extract the submitted value, and validate it.
  268. if ($widget = $this->defaultValueWidget($form_state)) {
  269. $widget->extractFormValues($this, $element, $form_state);
  270. // Force a non-required field definition.
  271. // @see self::defaultValueWidget().
  272. $this->getFieldDefinition()->setRequired(FALSE);
  273. $violations = $this->validate();
  274. // Assign reported errors to the correct form element.
  275. if (count($violations)) {
  276. $widget->flagErrors($this, $violations, $element, $form_state);
  277. }
  278. }
  279. }
  280. /**
  281. * {@inheritdoc}
  282. */
  283. public function defaultValuesFormSubmit(array $element, array &$form, FormStateInterface $form_state) {
  284. // Extract the submitted value, and return it as an array.
  285. if ($widget = $this->defaultValueWidget($form_state)) {
  286. $widget->extractFormValues($this, $element, $form_state);
  287. return $this->getValue();
  288. }
  289. }
  290. /**
  291. * {@inheritdoc}
  292. */
  293. public static function processDefaultValue($default_value, FieldableEntityInterface $entity, FieldDefinitionInterface $definition) {
  294. return $default_value;
  295. }
  296. /**
  297. * Returns the widget object used in default value form.
  298. *
  299. * @param \Drupal\Core\Form\FormStateInterface $form_state
  300. * The form state of the (entire) configuration form.
  301. *
  302. * @return \Drupal\Core\Field\WidgetInterface|null
  303. * A Widget object or NULL if no widget is available.
  304. */
  305. protected function defaultValueWidget(FormStateInterface $form_state) {
  306. if (!$form_state->has('default_value_widget')) {
  307. $entity = $this->getEntity();
  308. // Force a non-required widget.
  309. $definition = $this->getFieldDefinition();
  310. $definition->setRequired(FALSE);
  311. $definition->setDescription('');
  312. // Use the widget currently configured for the 'default' form mode, or
  313. // fallback to the default widget for the field type.
  314. $entity_form_display = entity_get_form_display($entity->getEntityTypeId(), $entity->bundle(), 'default');
  315. $widget = $entity_form_display->getRenderer($this->getFieldDefinition()->getName());
  316. if (!$widget) {
  317. $widget = \Drupal::service('plugin.manager.field.widget')->getInstance(['field_definition' => $this->getFieldDefinition()]);
  318. }
  319. $form_state->set('default_value_widget', $widget);
  320. }
  321. return $form_state->get('default_value_widget');
  322. }
  323. /**
  324. * {@inheritdoc}
  325. */
  326. public function equals(FieldItemListInterface $list_to_compare) {
  327. $count1 = count($this);
  328. $count2 = count($list_to_compare);
  329. if ($count1 === 0 && $count2 === 0) {
  330. // Both are empty we can safely assume that it did not change.
  331. return TRUE;
  332. }
  333. if ($count1 !== $count2) {
  334. // One of them is empty but not the other one so the value changed.
  335. return FALSE;
  336. }
  337. $value1 = $this->getValue();
  338. $value2 = $list_to_compare->getValue();
  339. if ($value1 === $value2) {
  340. return TRUE;
  341. }
  342. // If the values are not equal ensure a consistent order of field item
  343. // properties and remove properties which will not be saved.
  344. $property_definitions = $this->getFieldDefinition()->getFieldStorageDefinition()->getPropertyDefinitions();
  345. $non_computed_properties = array_filter($property_definitions, function (DataDefinitionInterface $property) {
  346. return !$property->isComputed();
  347. });
  348. $callback = function (&$value) use ($non_computed_properties) {
  349. if (is_array($value)) {
  350. $value = array_intersect_key($value, $non_computed_properties);
  351. ksort($value);
  352. }
  353. };
  354. array_walk($value1, $callback);
  355. array_walk($value2, $callback);
  356. return $value1 == $value2;
  357. }
  358. }