Version 1
[yaffs-website] / web / core / modules / field / src / Entity / FieldStorageConfig.php
1 <?php
2
3 namespace Drupal\field\Entity;
4
5 use Drupal\Component\Utility\Unicode;
6 use Drupal\Core\Config\Entity\ConfigEntityBase;
7 use Drupal\Core\Entity\EntityStorageInterface;
8 use Drupal\Core\Entity\FieldableEntityInterface;
9 use Drupal\Core\Field\FieldException;
10 use Drupal\Core\Field\FieldStorageDefinitionInterface;
11 use Drupal\Core\TypedData\OptionsProviderInterface;
12 use Drupal\field\FieldStorageConfigInterface;
13
14 /**
15  * Defines the Field storage configuration entity.
16  *
17  * @ConfigEntityType(
18  *   id = "field_storage_config",
19  *   label = @Translation("Field storage"),
20  *   handlers = {
21  *     "access" = "Drupal\field\FieldStorageConfigAccessControlHandler",
22  *     "storage" = "Drupal\field\FieldStorageConfigStorage"
23  *   },
24  *   config_prefix = "storage",
25  *   entity_keys = {
26  *     "id" = "id",
27  *     "label" = "id"
28  *   },
29  *   config_export = {
30  *     "id",
31  *     "field_name",
32  *     "entity_type",
33  *     "type",
34  *     "settings",
35  *     "module",
36  *     "locked",
37  *     "cardinality",
38  *     "translatable",
39  *     "indexes",
40  *     "persist_with_no_fields",
41  *     "custom_storage",
42  *   }
43  * )
44  */
45 class FieldStorageConfig extends ConfigEntityBase implements FieldStorageConfigInterface {
46
47   /**
48    * The maximum length of the field name, in characters.
49    *
50    * For fields created through Field UI, this includes the 'field_' prefix.
51    */
52   const NAME_MAX_LENGTH = 32;
53
54   /**
55    * The field ID.
56    *
57    * The ID consists of 2 parts: the entity type and the field name.
58    *
59    * Example: node.body, user.field_main_image.
60    *
61    * @var string
62    */
63   protected $id;
64
65   /**
66    * The field name.
67    *
68    * This is the name of the property under which the field values are placed in
69    * an entity: $entity->{$field_name}. The maximum length is
70    * Field:NAME_MAX_LENGTH.
71    *
72    * Example: body, field_main_image.
73    *
74    * @var string
75    */
76   protected $field_name;
77
78   /**
79    * The name of the entity type the field can be attached to.
80    *
81    * @var string
82    */
83   protected $entity_type;
84
85   /**
86    * The field type.
87    *
88    * Example: text, integer.
89    *
90    * @var string
91    */
92   protected $type;
93
94   /**
95    * The name of the module that provides the field type.
96    *
97    * @var string
98    */
99   protected $module;
100
101   /**
102    * Field-type specific settings.
103    *
104    * An array of key/value pairs, The keys and default values are defined by the
105    * field type.
106    *
107    * @var array
108    */
109   protected $settings = [];
110
111   /**
112    * The field cardinality.
113    *
114    * The maximum number of values the field can hold. Possible values are
115    * positive integers or
116    * FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED. Defaults to 1.
117    *
118    * @var int
119    */
120   protected $cardinality = 1;
121
122   /**
123    * Flag indicating whether the field is translatable.
124    *
125    * Defaults to TRUE.
126    *
127    * @var bool
128    */
129   protected $translatable = TRUE;
130
131   /**
132    * Flag indicating whether the field is available for editing.
133    *
134    * If TRUE, some actions not available though the UI (but are still possible
135    * through direct API manipulation):
136    * - field settings cannot be changed,
137    * - new fields cannot be created
138    * - existing fields cannot be deleted.
139    * Defaults to FALSE.
140    *
141    * @var bool
142    */
143   protected $locked = FALSE;
144
145   /**
146    * Flag indicating whether the field storage should be deleted when orphaned.
147    *
148    * By default field storages for configurable fields are removed when there
149    * are no remaining fields using them. If multiple modules provide bundles
150    * which need to use the same field storage then setting this to TRUE will
151    * preserve the field storage regardless of what happens to the bundles. The
152    * classic use case for this is node body field storage since Book, Forum, the
153    * Standard profile and bundle (node type) creation through the UI all use
154    * same field storage.
155    *
156    * @var bool
157    */
158   protected $persist_with_no_fields = FALSE;
159
160   /**
161    * A boolean indicating whether or not the field item uses custom storage.
162    *
163    * @var bool
164    */
165   public $custom_storage = FALSE;
166
167   /**
168    * The custom storage indexes for the field data storage.
169    *
170    * This set of indexes is merged with the "default" indexes specified by the
171    * field type in hook_field_schema() to determine the actual set of indexes
172    * that get created.
173    *
174    * The indexes are defined using the same definition format as Schema API
175    * index specifications. Only columns that are part of the field schema, as
176    * defined by the field type in hook_field_schema(), are allowed.
177    *
178    * Some storage backends might not support indexes, and discard that
179    * information.
180    *
181    * @var array
182    */
183   protected $indexes = [];
184
185   /**
186    * Flag indicating whether the field is deleted.
187    *
188    * The delete() method marks the field as "deleted" and removes the
189    * corresponding entry from the config storage, but keeps its definition in
190    * the state storage while field data is purged by a separate
191    * garbage-collection process.
192    *
193    * Deleted fields stay out of the regular entity lifecycle (notably, their
194    * values are not populated in loaded entities, and are not saved back).
195    *
196    * @var bool
197    */
198   protected $deleted = FALSE;
199
200   /**
201    * The field schema.
202    *
203    * @var array
204    */
205   protected $schema;
206
207   /**
208    * An array of field property definitions.
209    *
210    * @var \Drupal\Core\TypedData\DataDefinitionInterface[]
211    *
212    * @see \Drupal\Core\TypedData\ComplexDataDefinitionInterface::getPropertyDefinitions()
213    */
214   protected $propertyDefinitions;
215
216   /**
217    * Static flag set to prevent recursion during field deletes.
218    *
219    * @var bool
220    */
221   protected static $inDeletion = FALSE;
222
223   /**
224    * Constructs a FieldStorageConfig object.
225    *
226    * In most cases, Field entities are created via
227    * FieldStorageConfig::create($values)), where $values is the same parameter
228    * as in this constructor.
229    *
230    * @param array $values
231    *   An array of field properties, keyed by property name. Most array
232    *   elements will be used to set the corresponding properties on the class;
233    *   see the class property documentation for details. Some array elements
234    *   have special meanings and a few are required. Special elements are:
235    *   - name: required. As a temporary Backwards Compatibility layer right now,
236    *     a 'field_name' property can be accepted in place of 'id'.
237    *   - entity_type: required.
238    *   - type: required.
239    *
240    * @see entity_create()
241    */
242   public function __construct(array $values, $entity_type = 'field_storage_config') {
243     // Check required properties.
244     if (empty($values['field_name'])) {
245       throw new FieldException('Attempt to create a field storage without a field name.');
246     }
247     if (!preg_match('/^[_a-z]+[_a-z0-9]*$/', $values['field_name'])) {
248       throw new FieldException("Attempt to create a field storage {$values['field_name']} with invalid characters. Only lowercase alphanumeric characters and underscores are allowed, and only lowercase letters and underscore are allowed as the first character");
249     }
250     if (empty($values['type'])) {
251       throw new FieldException("Attempt to create a field storage {$values['field_name']} with no type.");
252     }
253     if (empty($values['entity_type'])) {
254       throw new FieldException("Attempt to create a field storage {$values['field_name']} with no entity_type.");
255     }
256
257     parent::__construct($values, $entity_type);
258   }
259
260   /**
261    * {@inheritdoc}
262    */
263   public function id() {
264     return $this->getTargetEntityTypeId() . '.' . $this->getName();
265   }
266
267   /**
268    * Overrides \Drupal\Core\Entity\Entity::preSave().
269    *
270    * @throws \Drupal\Core\Field\FieldException
271    *   If the field definition is invalid.
272    * @throws \Drupal\Core\Entity\EntityStorageException
273    *   In case of failures at the configuration storage level.
274    */
275   public function preSave(EntityStorageInterface $storage) {
276     // Clear the derived data about the field.
277     unset($this->schema);
278
279     // Filter out unknown settings and make sure all settings are present, so
280     // that a complete field definition is passed to the various hooks and
281     // written to config.
282     $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
283     $default_settings = $field_type_manager->getDefaultStorageSettings($this->type);
284     $this->settings = array_intersect_key($this->settings, $default_settings) + $default_settings;
285
286     if ($this->isNew()) {
287       $this->preSaveNew($storage);
288     }
289     else {
290       $this->preSaveUpdated($storage);
291     }
292
293     parent::preSave($storage);
294   }
295
296   /**
297    * Prepares saving a new field definition.
298    *
299    * @param \Drupal\Core\Entity\EntityStorageInterface $storage
300    *   The entity storage.
301    *
302    * @throws \Drupal\Core\Field\FieldException
303    *   If the field definition is invalid.
304    */
305   protected function preSaveNew(EntityStorageInterface $storage) {
306     $entity_manager = \Drupal::entityManager();
307     $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
308
309     // Assign the ID.
310     $this->id = $this->id();
311
312     // Field name cannot be longer than FieldStorageConfig::NAME_MAX_LENGTH characters.
313     // We use Unicode::strlen() because the DB layer assumes that column widths
314     // are given in characters rather than bytes.
315     if (Unicode::strlen($this->getName()) > static::NAME_MAX_LENGTH) {
316       throw new FieldException('Attempt to create a field storage with an name longer than ' . static::NAME_MAX_LENGTH . ' characters: ' . $this->getName());
317     }
318
319     // Disallow reserved field names.
320     $disallowed_field_names = array_keys($entity_manager->getBaseFieldDefinitions($this->getTargetEntityTypeId()));
321     if (in_array($this->getName(), $disallowed_field_names)) {
322       throw new FieldException("Attempt to create field storage {$this->getName()} which is reserved by entity type {$this->getTargetEntityTypeId()}.");
323     }
324
325     // Check that the field type is known.
326     $field_type = $field_type_manager->getDefinition($this->getType(), FALSE);
327     if (!$field_type) {
328       throw new FieldException("Attempt to create a field storage of unknown type {$this->getType()}.");
329     }
330     $this->module = $field_type['provider'];
331
332     // Notify the entity manager.
333     $entity_manager->onFieldStorageDefinitionCreate($this);
334   }
335
336   /**
337    * {@inheritdoc}
338    */
339   public function calculateDependencies() {
340     parent::calculateDependencies();
341     // Ensure the field is dependent on the providing module.
342     $this->addDependency('module', $this->getTypeProvider());
343     // Ask the field type for any additional storage dependencies.
344     // @see \Drupal\Core\Field\FieldItemInterface::calculateStorageDependencies()
345     $definition = \Drupal::service('plugin.manager.field.field_type')->getDefinition($this->getType(), FALSE);
346     $this->addDependencies($definition['class']::calculateStorageDependencies($this));
347
348     // Ensure the field is dependent on the provider of the entity type.
349     $entity_type = \Drupal::entityManager()->getDefinition($this->entity_type);
350     $this->addDependency('module', $entity_type->getProvider());
351     return $this;
352   }
353
354   /**
355    * Prepares saving an updated field definition.
356    *
357    * @param \Drupal\Core\Entity\EntityStorageInterface $storage
358    *   The entity storage.
359    */
360   protected function preSaveUpdated(EntityStorageInterface $storage) {
361     $module_handler = \Drupal::moduleHandler();
362     $entity_manager = \Drupal::entityManager();
363
364     // Some updates are always disallowed.
365     if ($this->getType() != $this->original->getType()) {
366       throw new FieldException("Cannot change the field type for an existing field storage.");
367     }
368     if ($this->getTargetEntityTypeId() != $this->original->getTargetEntityTypeId()) {
369       throw new FieldException("Cannot change the entity type for an existing field storage.");
370     }
371
372     // See if any module forbids the update by throwing an exception. This
373     // invokes hook_field_storage_config_update_forbid().
374     $module_handler->invokeAll('field_storage_config_update_forbid', [$this, $this->original]);
375
376     // Notify the entity manager. A listener can reject the definition
377     // update as invalid by raising an exception, which stops execution before
378     // the definition is written to config.
379     $entity_manager->onFieldStorageDefinitionUpdate($this, $this->original);
380   }
381
382   /**
383    * {@inheritdoc}
384    */
385   public function postSave(EntityStorageInterface $storage, $update = TRUE) {
386     if ($update) {
387       // Invalidate the render cache for all affected entities.
388       $entity_manager = \Drupal::entityManager();
389       $entity_type = $this->getTargetEntityTypeId();
390       if ($entity_manager->hasHandler($entity_type, 'view_builder')) {
391         $entity_manager->getViewBuilder($entity_type)->resetCache();
392       }
393     }
394   }
395
396   /**
397    * {@inheritdoc}
398    */
399   public static function preDelete(EntityStorageInterface $storage, array $field_storages) {
400     $state = \Drupal::state();
401
402     // Set the static flag so that we don't delete field storages whilst
403     // deleting fields.
404     static::$inDeletion = TRUE;
405
406     // Delete or fix any configuration that is dependent, for example, fields.
407     parent::preDelete($storage, $field_storages);
408
409     // Keep the field definitions in the state storage so we can use them later
410     // during field_purge_batch().
411     $deleted_storages = $state->get('field.storage.deleted') ?: [];
412     foreach ($field_storages as $field_storage) {
413       if (!$field_storage->deleted) {
414         $config = $field_storage->toArray();
415         $config['deleted'] = TRUE;
416         $config['bundles'] = $field_storage->getBundles();
417         $deleted_storages[$field_storage->uuid()] = $config;
418       }
419     }
420
421     $state->set('field.storage.deleted', $deleted_storages);
422   }
423
424   /**
425    * {@inheritdoc}
426    */
427   public static function postDelete(EntityStorageInterface $storage, array $fields) {
428     // Notify the storage.
429     foreach ($fields as $field) {
430       if (!$field->deleted) {
431         \Drupal::entityManager()->onFieldStorageDefinitionDelete($field);
432         $field->deleted = TRUE;
433       }
434     }
435     // Unset static flag.
436     static::$inDeletion = FALSE;
437   }
438
439   /**
440    * {@inheritdoc}
441    */
442   public function getSchema() {
443     if (!isset($this->schema)) {
444       // Get the schema from the field item class.
445       $class = $this->getFieldItemClass();
446       $schema = $class::schema($this);
447       // Fill in default values for optional entries.
448       $schema += [
449         'columns' => [],
450         'unique keys' => [],
451         'indexes' => [],
452         'foreign keys' => [],
453       ];
454
455       // Merge custom indexes with those specified by the field type. Custom
456       // indexes prevail.
457       $schema['indexes'] = $this->indexes + $schema['indexes'];
458
459       $this->schema = $schema;
460     }
461
462     return $this->schema;
463   }
464
465   /**
466    * {@inheritdoc}
467    */
468   public function hasCustomStorage() {
469     return $this->custom_storage;
470   }
471
472   /**
473    * {@inheritdoc}
474    */
475   public function isBaseField() {
476     return FALSE;
477   }
478
479   /**
480    * {@inheritdoc}
481    */
482   public function getColumns() {
483     $schema = $this->getSchema();
484     // A typical use case for the method is to iterate on the columns, while
485     // some other use cases rely on identifying the first column with the key()
486     // function. Since the schema is persisted in the Field object, we take care
487     // of resetting the array pointer so that the former does not interfere with
488     // the latter.
489     reset($schema['columns']);
490     return $schema['columns'];
491   }
492
493   /**
494    * {@inheritdoc}
495    */
496   public function getBundles() {
497     if (!$this->isDeleted()) {
498       $map = \Drupal::entityManager()->getFieldMap();
499       if (isset($map[$this->getTargetEntityTypeId()][$this->getName()]['bundles'])) {
500         return $map[$this->getTargetEntityTypeId()][$this->getName()]['bundles'];
501       }
502     }
503     return [];
504   }
505
506   /**
507    * {@inheritdoc}
508    */
509   public function getName() {
510     return $this->field_name;
511   }
512
513   /**
514    * {@inheritdoc}
515    */
516   public function isDeleted() {
517     return $this->deleted;
518   }
519
520   /**
521    * {@inheritdoc}
522    */
523   public function getTypeProvider() {
524     return $this->module;
525   }
526
527   /**
528    * {@inheritdoc}
529    */
530   public function getType() {
531     return $this->type;
532   }
533
534   /**
535    * {@inheritdoc}
536    */
537   public function getSettings() {
538     // @todo FieldTypePluginManager maintains its own static cache. However, do
539     //   some CPU and memory profiling to see if it's worth statically caching
540     //   $field_type_info, or the default field storage and field settings,
541     //   within $this.
542     $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
543
544     $settings = $field_type_manager->getDefaultStorageSettings($this->getType());
545     return $this->settings + $settings;
546   }
547
548   /**
549    * {@inheritdoc}
550    */
551   public function getSetting($setting_name) {
552     // @todo See getSettings() about potentially statically caching this.
553     // We assume here that one call to array_key_exists() is more efficient
554     // than calling getSettings() when all we need is a single setting.
555     if (array_key_exists($setting_name, $this->settings)) {
556       return $this->settings[$setting_name];
557     }
558     $settings = $this->getSettings();
559     if (array_key_exists($setting_name, $settings)) {
560       return $settings[$setting_name];
561     }
562     else {
563       return NULL;
564     }
565   }
566
567   /**
568    * {@inheritdoc}
569    */
570   public function setSetting($setting_name, $value) {
571     $this->settings[$setting_name] = $value;
572     return $this;
573   }
574
575   /**
576    * {@inheritdoc}
577    */
578   public function setSettings(array $settings) {
579     $this->settings = $settings + $this->settings;
580     return $this;
581   }
582
583   /**
584    * {@inheritdoc}
585    */
586   public function isTranslatable() {
587     return $this->translatable;
588   }
589
590   /**
591    * {@inheritdoc}
592    */
593   public function isRevisionable() {
594     // All configurable fields are revisionable.
595     return TRUE;
596   }
597
598   /**
599    * {@inheritdoc}
600    */
601   public function setTranslatable($translatable) {
602     $this->translatable = $translatable;
603     return $this;
604   }
605
606   /**
607    * {@inheritdoc}
608    */
609   public function getProvider() {
610     return 'field';
611   }
612
613   /**
614    * {@inheritdoc}
615    */
616   public function getLabel() {
617     return $this->label();
618   }
619
620   /**
621    * {@inheritdoc}
622    */
623   public function getDescription() {
624     return NULL;
625   }
626
627   /**
628    * {@inheritdoc}
629    */
630   public function getCardinality() {
631     return $this->cardinality;
632   }
633
634   /**
635    * {@inheritdoc}
636    */
637   public function setCardinality($cardinality) {
638     $this->cardinality = $cardinality;
639     return $this;
640   }
641
642   /**
643    * {@inheritdoc}
644    */
645   public function getOptionsProvider($property_name, FieldableEntityInterface $entity) {
646     // If the field item class implements the interface, create an orphaned
647     // runtime item object, so that it can be used as the options provider
648     // without modifying the entity being worked on.
649     if (is_subclass_of($this->getFieldItemClass(), OptionsProviderInterface::class)) {
650       $items = $entity->get($this->getName());
651       return \Drupal::service('plugin.manager.field.field_type')->createFieldItem($items, 0);
652     }
653     // @todo: Allow setting custom options provider, see
654     // https://www.drupal.org/node/2002138.
655   }
656
657   /**
658    * {@inheritdoc}
659    */
660   public function isMultiple() {
661     $cardinality = $this->getCardinality();
662     return ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) || ($cardinality > 1);
663   }
664
665   /**
666    * {@inheritdoc}
667    */
668   public function isLocked() {
669     return $this->locked;
670   }
671
672   /**
673    * {@inheritdoc}
674    */
675   public function setLocked($locked) {
676     $this->locked = $locked;
677     return $this;
678   }
679
680   /**
681    * {@inheritdoc}
682    */
683   public function getTargetEntityTypeId() {
684     return $this->entity_type;
685   }
686
687   /**
688    * {@inheritdoc}
689    */
690   public function isQueryable() {
691     return TRUE;
692   }
693
694   /**
695    * Determines whether a field has any data.
696    *
697    * @return bool
698    *   TRUE if the field has data for any entity; FALSE otherwise.
699    */
700   public function hasData() {
701     return \Drupal::entityManager()->getStorage($this->entity_type)->countFieldData($this, TRUE);
702   }
703
704   /**
705    * Implements the magic __sleep() method.
706    *
707    * Using the Serialize interface and serialize() / unserialize() methods
708    * breaks entity forms in PHP 5.4.
709    * @todo Investigate in https://www.drupal.org/node/2074253.
710    */
711   public function __sleep() {
712     // Only serialize necessary properties, excluding those that can be
713     // recalculated.
714     $properties = get_object_vars($this);
715     unset($properties['schema'], $properties['propertyDefinitions'], $properties['original']);
716     return array_keys($properties);
717   }
718
719   /**
720    * {@inheritdoc}
721    */
722   public function getConstraints() {
723     return [];
724   }
725
726   /**
727    * {@inheritdoc}
728    */
729   public function getConstraint($constraint_name) {
730     return NULL;
731   }
732
733   /**
734    * {@inheritdoc}
735    */
736   public function getPropertyDefinition($name) {
737     if (!isset($this->propertyDefinitions)) {
738       $this->getPropertyDefinitions();
739     }
740     if (isset($this->propertyDefinitions[$name])) {
741       return $this->propertyDefinitions[$name];
742     }
743   }
744
745   /**
746    * {@inheritdoc}
747    */
748   public function getPropertyDefinitions() {
749     if (!isset($this->propertyDefinitions)) {
750       $class = $this->getFieldItemClass();
751       $this->propertyDefinitions = $class::propertyDefinitions($this);
752     }
753     return $this->propertyDefinitions;
754   }
755
756   /**
757    * {@inheritdoc}
758    */
759   public function getPropertyNames() {
760     return array_keys($this->getPropertyDefinitions());
761   }
762
763   /**
764    * {@inheritdoc}
765    */
766   public function getMainPropertyName() {
767     $class = $this->getFieldItemClass();
768     return $class::mainPropertyName();
769   }
770
771   /**
772    * {@inheritdoc}
773    */
774   public function getUniqueStorageIdentifier() {
775     return $this->uuid();
776   }
777
778   /**
779    * Helper to retrieve the field item class.
780    */
781   protected function getFieldItemClass() {
782     $type_definition = \Drupal::typedDataManager()
783       ->getDefinition('field_item:' . $this->getType());
784     return $type_definition['class'];
785   }
786
787   /**
788    * Loads a field config entity based on the entity type and field name.
789    *
790    * @param string $entity_type_id
791    *   ID of the entity type.
792    * @param string $field_name
793    *   Name of the field.
794    *
795    * @return static
796    *   The field config entity if one exists for the provided field name,
797    *   otherwise NULL.
798    */
799   public static function loadByName($entity_type_id, $field_name) {
800     return \Drupal::entityManager()->getStorage('field_storage_config')->load($entity_type_id . '.' . $field_name);
801   }
802
803   /**
804    * {@inheritdoc}
805    */
806   public function isDeletable() {
807     // The field storage is not deleted, is configured to be removed when there
808     // are no fields, the field storage has no bundles, and field storages are
809     // not in the process of being deleted.
810     return !$this->deleted && !$this->persist_with_no_fields && count($this->getBundles()) == 0 && !static::$inDeletion;
811   }
812
813   /**
814    * {@inheritdoc}
815    */
816   public function getIndexes() {
817     return $this->indexes;
818   }
819
820   /**
821    * {@inheritdoc}
822    */
823   public function setIndexes(array $indexes) {
824     $this->indexes = $indexes;
825     return $this;
826   }
827
828 }