1<?php
2
3namespace Drupal\Core\Field\Entity;
4
5use Drupal\Core\Entity\EntityStorageInterface;
6use Drupal\Core\Field\BaseFieldDefinition;
7use Drupal\Core\Field\FieldConfigBase;
8use Drupal\Core\Field\FieldException;
9
10/**
11 * Defines the base field override entity.
12 *
13 * Allows base fields to be overridden on the bundle level.
14 *
15 * @ConfigEntityType(
16 *   id = "base_field_override",
17 *   label = @Translation("Base field override"),
18 *   handlers = {
19 *     "storage" = "Drupal\Core\Field\BaseFieldOverrideStorage",
20 *     "access" = "Drupal\Core\Field\BaseFieldOverrideAccessControlHandler",
21 *   },
22 *   config_prefix = "base_field_override",
23 *   entity_keys = {
24 *     "id" = "id",
25 *     "label" = "label"
26 *   },
27 *   config_export = {
28 *     "id",
29 *     "field_name",
30 *     "entity_type",
31 *     "bundle",
32 *     "label",
33 *     "description",
34 *     "required",
35 *     "translatable",
36 *     "default_value",
37 *     "default_value_callback",
38 *     "settings",
39 *     "field_type",
40 *   }
41 * )
42 */
43class BaseFieldOverride extends FieldConfigBase {
44
45  /**
46   * The base field definition.
47   *
48   * @var \Drupal\Core\Field\BaseFieldDefinition
49   */
50  protected $baseFieldDefinition;
51
52  /**
53   * Creates a base field override object.
54   *
55   * @param \Drupal\Core\Field\BaseFieldDefinition $base_field_definition
56   *   The base field definition to override.
57   * @param string $bundle
58   *   The bundle to which the override applies.
59   *
60   * @return \Drupal\Core\Field\Entity\BaseFieldOverride
61   *   A new base field override object.
62   */
63  public static function createFromBaseFieldDefinition(BaseFieldDefinition $base_field_definition, $bundle) {
64    $values = $base_field_definition->toArray();
65    $values['bundle'] = $bundle;
66    $values['baseFieldDefinition'] = $base_field_definition;
67    return \Drupal::entityTypeManager()->getStorage('base_field_override')->create($values);
68  }
69
70  /**
71   * Constructs a BaseFieldOverride object.
72   *
73   * In most cases, base field override entities are created via
74   * BaseFieldOverride::createFromBaseFieldDefinition($definition, 'bundle')
75   *
76   * @param array $values
77   *   An array of base field bundle override properties, keyed by property
78   *   name. The field to override is specified by referring to an existing
79   *   field with:
80   *   - field_name: The field name.
81   *   - entity_type: The entity type.
82   *   Additionally, a 'bundle' property is required to indicate the entity
83   *   bundle to which the bundle field override is attached to. Other array
84   *   elements will be used to set the corresponding properties on the class;
85   *   see the class property documentation for details.
86   * @param string $entity_type
87   *   (optional) The type of the entity to create. Defaults to
88   *   'base_field_override'.
89   *
90   * @throws \Drupal\Core\Field\FieldException
91   *   Exception thrown if $values does not contain a field_name, entity_type or
92   *   bundle value.
93   */
94  public function __construct(array $values, $entity_type = 'base_field_override') {
95    if (empty($values['field_name'])) {
96      throw new FieldException('Attempt to create a base field bundle override of a field without a field_name');
97    }
98    if (empty($values['entity_type'])) {
99      throw new FieldException("Attempt to create a base field bundle override of field {$values['field_name']} without an entity_type");
100    }
101    if (empty($values['bundle'])) {
102      throw new FieldException("Attempt to create a base field bundle override of field {$values['field_name']} without a bundle");
103    }
104
105    parent::__construct($values, $entity_type);
106  }
107
108  /**
109   * {@inheritdoc}
110   */
111  public function getFieldStorageDefinition() {
112    return $this->getBaseFieldDefinition()->getFieldStorageDefinition();
113  }
114
115  /**
116   * {@inheritdoc}
117   */
118  public function isDisplayConfigurable($context) {
119    return $this->getBaseFieldDefinition()->isDisplayConfigurable($context);
120  }
121
122  /**
123   * {@inheritdoc}
124   */
125  public function getDisplayOptions($display_context) {
126    return $this->getBaseFieldDefinition()->getDisplayOptions($display_context);
127  }
128
129  /**
130   * {@inheritdoc}
131   */
132  public function isReadOnly() {
133    return $this->getBaseFieldDefinition()->isReadOnly();
134  }
135
136  /**
137   * {@inheritdoc}
138   */
139  public function isComputed() {
140    return $this->getBaseFieldDefinition()->isComputed();
141  }
142
143  /**
144   * {@inheritdoc}
145   */
146  public function getClass() {
147    return $this->getBaseFieldDefinition()->getClass();
148  }
149
150  /**
151   * {@inheritdoc}
152   */
153  public function getUniqueIdentifier() {
154    return $this->getBaseFieldDefinition()->getUniqueIdentifier();
155  }
156
157  /**
158   * Gets the base field definition.
159   *
160   * @return \Drupal\Core\Field\BaseFieldDefinition
161   */
162  protected function getBaseFieldDefinition() {
163    if (!isset($this->baseFieldDefinition)) {
164      $fields = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions($this->entity_type);
165      $this->baseFieldDefinition = $fields[$this->getName()];
166    }
167    return $this->baseFieldDefinition;
168  }
169
170  /**
171   * {@inheritdoc}
172   *
173   * @throws \Drupal\Core\Field\FieldException
174   *   If the bundle is being changed.
175   */
176  public function preSave(EntityStorageInterface $storage) {
177    // Filter out unknown settings and make sure all settings are present, so
178    // that a complete field definition is passed to the various hooks and
179    // written to config.
180    $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
181    $default_settings = $field_type_manager->getDefaultFieldSettings($this->getType());
182    $this->settings = array_intersect_key($this->settings, $default_settings) + $default_settings;
183
184    // Call the parent's presave method to perform validate and calculate
185    // dependencies.
186    parent::preSave($storage);
187
188    if ($this->isNew()) {
189      // @todo This assumes that the previous definition isn't some
190      //   non-config-based override, but that might not be the case:
191      //   https://www.drupal.org/node/2321071.
192      $previous_definition = $this->getBaseFieldDefinition();
193    }
194    else {
195      // Some updates are always disallowed.
196      if ($this->entity_type != $this->original->entity_type) {
197        throw new FieldException("Cannot change the entity_type of an existing base field bundle override (entity type:{$this->entity_type}, bundle:{$this->original->bundle}, field name: {$this->field_name})");
198      }
199      if ($this->bundle != $this->original->bundle) {
200        throw new FieldException("Cannot change the bundle of an existing base field bundle override (entity type:{$this->entity_type}, bundle:{$this->original->bundle}, field name: {$this->field_name})");
201      }
202      $previous_definition = $this->original;
203    }
204    // Notify the entity storage.
205    $this->entityTypeManager()->getStorage($this->getTargetEntityTypeId())->onFieldDefinitionUpdate($this, $previous_definition);
206  }
207
208  /**
209   * {@inheritdoc}
210   */
211  public static function postDelete(EntityStorageInterface $storage, array $field_overrides) {
212    $entity_type_manager = \Drupal::entityTypeManager();
213    // Clear the cache upfront, to refresh the results of getBundles().
214    \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
215    /** @var \Drupal\Core\Field\Entity\BaseFieldOverride $field_override */
216    foreach ($field_overrides as $field_override) {
217      // Inform the system that the field definition is being updated back to
218      // its non-overridden state.
219      // @todo This assumes that there isn't a non-config-based override that
220      //   we're returning to, but that might not be the case:
221      //   https://www.drupal.org/node/2321071.
222      $entity_type_manager->getStorage($field_override->getTargetEntityTypeId())->onFieldDefinitionUpdate($field_override->getBaseFieldDefinition(), $field_override);
223    }
224  }
225
226  /**
227   * Loads a base field bundle override config entity.
228   *
229   * @param string $entity_type_id
230   *   ID of the entity type.
231   * @param string $bundle
232   *   Bundle name.
233   * @param string $field_name
234   *   Name of the field.
235   *
236   * @return \Drupal\Core\Field\FieldConfigInterface|null
237   *   The base field bundle override config entity if one exists for the
238   *   provided field name, otherwise NULL.
239   */
240  public static function loadByName($entity_type_id, $bundle, $field_name) {
241    return \Drupal::entityTypeManager()->getStorage('base_field_override')->load($entity_type_id . '.' . $bundle . '.' . $field_name);
242  }
243
244  /**
245   * Implements the magic __sleep() method.
246   */
247  public function __sleep() {
248    // Only serialize necessary properties, excluding those that can be
249    // recalculated.
250    unset($this->baseFieldDefinition);
251    return parent::__sleep();
252  }
253
254}
255