FieldConfig.php 22 KB
Newer Older
1
2
3
4
<?php

/**
 * @file
5
 * Contains \Drupal\field\Entity\FieldConfig.
6
7
 */

8
namespace Drupal\field\Entity;
9

10
use Drupal\Component\Utility\String;
11
use Drupal\Component\Utility\Unicode;
12
use Drupal\Core\Config\Entity\ConfigEntityBase;
13
use Drupal\Core\Entity\EntityStorageInterface;
14
use Drupal\Core\Field\FieldStorageDefinitionInterface;
15
use Drupal\field\FieldException;
16
use Drupal\field\FieldConfigInterface;
17
18
19
20

/**
 * Defines the Field entity.
 *
21
 * @ConfigEntityType(
22
 *   id = "field_config",
23
 *   label = @Translation("Field"),
24
 *   controllers = {
25
 *     "storage" = "Drupal\field\FieldConfigStorage"
26
 *   },
27
 *   config_prefix = "field",
28
29
 *   entity_keys = {
 *     "id" = "id",
30
 *     "label" = "id"
31
32
33
 *   }
 * )
 */
34
class FieldConfig extends ConfigEntityBase implements FieldConfigInterface {
35
36

  /**
37
   * The maximum length of the field name, in characters.
38
39
40
   *
   * For fields created through Field UI, this includes the 'field_' prefix.
   */
41
  const NAME_MAX_LENGTH = 32;
42
43

  /**
44
45
46
47
48
49
50
51
52
53
54
55
   * The field ID.
   *
   * The ID consists of 2 parts: the entity type and the field name.
   *
   * Example: node.body, user.field_main_image.
   *
   * @var string
   */
  public $id;

  /**
   * The field name.
56
57
   *
   * This is the name of the property under which the field values are placed in
58
59
   * an entity: $entity->{$field_name}. The maximum length is
   * Field:NAME_MAX_LENGTH.
60
61
62
63
64
   *
   * Example: body, field_main_image.
   *
   * @var string
   */
65
  public $name;
66

67
68
69
70
71
72
73
  /**
   * The name of the entity type the field can be attached to.
   *
   * @var string
   */
  public $entity_type;

74
75
76
  /**
   * The field type.
   *
77
   * Example: text, integer.
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
   *
   * @var string
   */
  public $type;

  /**
   * The name of the module that provides the field type.
   *
   * @var string
   */
  public $module;

  /**
   * Field-type specific settings.
   *
   * An array of key/value pairs, The keys and default values are defined by the
94
   * field type.
95
96
97
   *
   * @var array
   */
98
  public $settings = array();
99
100
101
102
103

  /**
   * The field cardinality.
   *
   * The maximum number of values the field can hold. Possible values are
104
105
   * positive integers or
   * FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED. Defaults to 1.
106
   *
107
   * @var int
108
   */
109
  public $cardinality = 1;
110
111
112
113

  /**
   * Flag indicating whether the field is translatable.
   *
114
   * Defaults to TRUE.
115
116
117
   *
   * @var bool
   */
118
  public $translatable = TRUE;
119
120
121
122
123
124
125
126
127
128
129
130
131

  /**
   * Flag indicating whether the field is available for editing.
   *
   * If TRUE, some actions not available though the UI (but are still possible
   * through direct API manipulation):
   * - field settings cannot be changed,
   * - new instances cannot be created
   * - existing instances cannot be deleted.
   * Defaults to FALSE.
   *
   * @var bool
   */
132
  public $locked = FALSE;
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149

  /**
   * The custom storage indexes for the field data storage.
   *
   * This set of indexes is merged with the "default" indexes specified by the
   * field type in hook_field_schema() to determine the actual set of indexes
   * that get created.
   *
   * The indexes are defined using the same definition format as Schema API
   * index specifications. Only columns that are part of the field schema, as
   * defined by the field type in hook_field_schema(), are allowed.
   *
   * Some storage backends might not support indexes, and discard that
   * information.
   *
   * @var array
   */
150
  public $indexes = array();
151
152
153
154
155
156
157
158
159
160
161
162
163
164

  /**
   * Flag indicating whether the field is deleted.
   *
   * The delete() method marks the field as "deleted" and removes the
   * corresponding entry from the config storage, but keeps its definition in
   * the state storage while field data is purged by a separate
   * garbage-collection process.
   *
   * Deleted fields stay out of the regular entity lifecycle (notably, their
   * values are not populated in loaded entities, and are not saved back).
   *
   * @var bool
   */
165
  public $deleted = FALSE;
166
167
168
169
170
171
172
173

  /**
   * The field schema.
   *
   * @var array
   */
  protected $schema;

174
175
176
177
178
179
180
181
182
  /**
   * An array of field property definitions.
   *
   * @var \Drupal\Core\TypedData\DataDefinitionInterface[]
   *
   * @see \Drupal\Core\TypedData\ComplexDataDefinitionInterface::getPropertyDefinitions()
   */
  protected $propertyDefinitions;

183
  /**
184
   * Constructs a FieldConfig object.
185
186
187
188
189
190
   *
   * @param array $values
   *   An array of field properties, keyed by property name. Most array
   *   elements will be used to set the corresponding properties on the class;
   *   see the class property documentation for details. Some array elements
   *   have special meanings and a few are required. Special elements are:
191
   *   - name: required. As a temporary Backwards Compatibility layer right now,
192
   *     a 'field_name' property can be accepted in place of 'id'.
193
   *   - entity_type: required.
194
195
196
   *   - type: required.
   *
   * In most cases, Field entities are created via
197
   * entity_create('field_config', $values)), where $values is the same
198
199
200
   * parameter as in this constructor.
   *
   * @see entity_create()
201
   */
202
  public function __construct(array $values, $entity_type = 'field_config') {
203
    // Check required properties.
204
    if (empty($values['name'])) {
205
206
      throw new FieldException('Attempt to create an unnamed field.');
    }
207
    if (!preg_match('/^[_a-z]+[_a-z0-9]*$/', $values['name'])) {
208
      throw new FieldException(String::format('Attempt to create a field @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', array('@field_name' => $values['name'])));
209
    }
210
    if (empty($values['type'])) {
211
      throw new FieldException(String::format('Attempt to create field @field_name with no type.', array('@field_name' => $values['name'])));
212
213
    }
    if (empty($values['entity_type'])) {
214
      throw new FieldException(String::format('Attempt to create a field @field_name with no entity_type.', array('@field_name' => $values['name'])));
215
    }
216
217
218
219

    parent::__construct($values, $entity_type);
  }

220
221
222
223
224
225
226
  /**
   * {@inheritdoc}
   */
  public function id() {
    return $this->entity_type . '.' . $this->name;
  }

227
  /**
228
   * Overrides \Drupal\Core\Entity\Entity::preSave().
229
230
231
232
233
   *
   * @throws \Drupal\field\FieldException
   *   If the field definition is invalid.
   * @throws \Drupal\Core\Entity\EntityStorageException
   *   In case of failures at the configuration storage level.
234
   */
235
  public function preSave(EntityStorageInterface $storage) {
236
    // Clear the derived data about the field.
237
    unset($this->schema);
238
239

    if ($this->isNew()) {
240
      $this->preSaveNew($storage);
241
242
    }
    else {
243
      $this->preSaveUpdated($storage);
244
245
246
247
    }
    if (!$this->isSyncing()) {
      // Ensure the correct dependencies are present.
      $this->calculateDependencies();
248
249
    }
  }
250

251
  /**
252
   * Prepares saving a new field definition.
253
   *
254
255
   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
   *   The entity storage.
256
   *
257
   * @throws \Drupal\field\FieldException If the field definition is invalid.
258
   */
259
   protected function preSaveNew(EntityStorageInterface $storage) {
260
    $entity_manager = \Drupal::entityManager();
261
    $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
262

263
264
265
    // Assign the ID.
    $this->id = $this->id();

266
267
    // Field name cannot be longer than FieldConfig::NAME_MAX_LENGTH characters.
    // We use Unicode::strlen() because the DB layer assumes that column widths
268
    // are given in characters rather than bytes.
269
    if (Unicode::strlen($this->name) > static::NAME_MAX_LENGTH) {
270
      throw new FieldException(String::format(
271
272
273
        'Attempt to create a field with an ID longer than @max characters: %name', array(
          '@max' => static::NAME_MAX_LENGTH,
          '%name' => $this->name,
274
275
276
        )
      ));
    }
277

278
279
280
    // Disallow reserved field names.
    $disallowed_field_names = array_keys($entity_manager->getBaseFieldDefinitions($this->entity_type));
    if (in_array($this->name, $disallowed_field_names)) {
281
      throw new FieldException(String::format('Attempt to create field %name which is reserved by entity type %type.', array('%name' => $this->name, '%type' => $this->entity_type)));
282
283
284
    }

    // Check that the field type is known.
285
    $field_type = $field_type_manager->getDefinition($this->type, FALSE);
286
    if (!$field_type) {
287
      throw new FieldException(String::format('Attempt to create a field of unknown type %type.', array('%type' => $this->type)));
288
    }
289
    $this->module = $field_type['provider'];
290

291
292
    // Make sure all settings are present, so that a complete field
    // definition is passed to the various hooks and written to config.
293
     $this->settings += $field_type_manager->getDefaultSettings($this->type);
294

295
296
    // Notify the entity storage.
    $entity_manager->getStorage($this->entity_type)->onFieldCreate($this);
297
  }
298

299
300
301
302
303
304
305
  /**
   * {@inheritdoc}
   */
  public function calculateDependencies() {
    parent::calculateDependencies();
    // Ensure the field is dependent on the providing module.
    $this->addDependency('module', $this->module);
306
307
308
    // Ensure the field is dependent on the provider of the entity type.
    $entity_type = \Drupal::entityManager()->getDefinition($this->entity_type);
    $this->addDependency('module', $entity_type->getProvider());
309
310
311
    return $this->dependencies;
  }

312
  /**
313
   * Prepares saving an updated field definition.
314
   *
315
316
   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
   *   The entity storage.
317
   */
318
  protected function preSaveUpdated(EntityStorageInterface $storage) {
319
    $module_handler = \Drupal::moduleHandler();
320
    $entity_manager = \Drupal::entityManager();
321
    $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
322

323
    // Some updates are always disallowed.
324
    if ($this->type != $this->original->type) {
325
326
      throw new FieldException("Cannot change an existing field's type.");
    }
327
    if ($this->entity_type != $this->original->entity_type) {
328
      throw new FieldException("Cannot change an existing field's entity_type.");
329
330
    }

331
332
333
    // Make sure all settings are present, so that a complete field
    // definition is passed to the various hooks and written to config.
    $this->settings += $field_type_manager->getDefaultSettings($this->type);
334
335

    // See if any module forbids the update by throwing an exception. This
336
337
    // invokes hook_field_config_update_forbid().
    $module_handler->invokeAll('field_config_update_forbid', array($this, $this->original));
338

339
    // Notify the storage. The controller can reject the definition
340
341
    // update as invalid by raising an exception, which stops execution before
    // the definition is written to config.
342
    $entity_manager->getStorage($this->entity_type)->onFieldUpdate($this);
343
  }
344

345
346
347
  /**
   * {@inheritdoc}
   */
348
  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
349
    // Clear the cache.
350
    \Drupal::entityManager()->clearCachedFieldDefinitions();
351
352
353
354
355
356
357
358
359

    if ($update) {
      // Invalidate the render cache for all affected entities.
      $entity_manager = \Drupal::entityManager();
      $entity_type = $this->getTargetEntityTypeId();
      if ($entity_manager->hasController($entity_type, 'view_builder')) {
        $entity_manager->getViewBuilder($entity_type)->resetCache();
      }
    }
360
361
362
363
364
  }

  /**
   * {@inheritdoc}
   */
365
  public static function preDelete(EntityStorageInterface $storage, array $fields) {
366
    $state = \Drupal::state();
367
    $instance_storage = \Drupal::entityManager()->getStorage('field_instance_config');
368
369

    // Delete instances first. Note: when deleting a field through
370
    // FieldInstanceConfig::postDelete(), the instances have been deleted already, so
371
372
373
374
375
376
377
    // no instances will be found here.
    $instance_ids = array();
    foreach ($fields as $field) {
      if (!$field->deleted) {
        foreach ($field->getBundles() as $bundle) {
          $instance_ids[] = "{$field->entity_type}.$bundle.{$field->name}";
        }
378
      }
379
380
    }
    if ($instance_ids) {
381
      $instances = $instance_storage->loadMultiple($instance_ids);
382
383
384
      // Tag the objects to preserve recursive deletion of the field.
      foreach ($instances as $instance) {
        $instance->noFieldDelete = TRUE;
385
      }
386
      $instance_storage->delete($instances);
387
    }
388

389
390
391
392
393
    // Keep the field definitions in the state storage so we can use them later
    // during field_purge_batch().
    $deleted_fields = $state->get('field.field.deleted') ?: array();
    foreach ($fields as $field) {
      if (!$field->deleted) {
394
        $config = $field->toArray();
395
396
397
398
399
        $config['deleted'] = TRUE;
        $config['bundles'] = $field->getBundles();
        $deleted_fields[$field->uuid] = $config;
      }
    }
400

401
402
    $state->set('field.field.deleted', $deleted_fields);
  }
403

404
405
406
  /**
   * {@inheritdoc}
   */
407
  public static function postDelete(EntityStorageInterface $storage, array $fields) {
408
409
410
    // Notify the storage.
    foreach ($fields as $field) {
      if (!$field->deleted) {
411
        \Drupal::entityManager()->getStorage($field->entity_type)->onFieldDelete($field);
412
        $field->deleted = TRUE;
413
      }
414
    }
415
416

    // Clear the cache.
417
    \Drupal::entityManager()->clearCachedFieldDefinitions();
418
419
420
  }

  /**
421
   * {@inheritdoc}
422
423
424
   */
  public function getSchema() {
    if (!isset($this->schema)) {
425
      // Get the schema from the field item class.
426
      $class = $this->getFieldItemClass();
427
428
      $schema = $class::schema($this);
      // Fill in default values for optional entries.
429
430
431
432
433
      $schema += array(
        'unique keys' => array(),
        'indexes' => array(),
        'foreign keys' => array(),
      );
434
435

      // Check that the schema does not include forbidden column names.
436
      if (array_intersect(array_keys($schema['columns']), static::getReservedColumns())) {
437
        throw new FieldException(String::format('Illegal field type @field_type on @field_name.', array('@field_type' => $this->type, '@field_name' => $this->name)));
438
439
440
441
442
443
444
445
446
447
448
449
      }

      // Merge custom indexes with those specified by the field type. Custom
      // indexes prevail.
      $schema['indexes'] = $this->indexes + $schema['indexes'];

      $this->schema = $schema;
    }

    return $this->schema;
  }

450
451
452
453
454
455
456
  /**
   * {@inheritdoc}
   */
  public function hasCustomStorage() {
    return FALSE;
  }

457
458
459
460
461
462
463
464
465
466
467
468
469
470
  /**
   * {@inheritdoc}
   */
  public function getColumns() {
    $schema = $this->getSchema();
    // A typical use case for the method is to iterate on the columns, while
    // some other use cases rely on identifying the first column with the key()
    // function. Since the schema is persisted in the Field object, we take care
    // of resetting the array pointer so that the former does not interfere with
    // the latter.
    reset($schema['columns']);
    return $schema['columns'];
  }

471
  /**
472
   * {@inheritdoc}
473
474
475
   */
  public function getBundles() {
    if (empty($this->deleted)) {
476
      $map = \Drupal::entityManager()->getFieldMap();
477
478
      if (isset($map[$this->entity_type][$this->name]['bundles'])) {
        return $map[$this->entity_type][$this->name]['bundles'];
479
480
481
482
483
      }
    }
    return array();
  }

484
485
486
  /**
   * {@inheritdoc}
   */
487
  public function getName() {
488
    return $this->name;
489
490
491
492
493
  }

  /**
   * {@inheritdoc}
   */
494
  public function getType() {
495
496
497
498
499
500
    return $this->type;
  }

  /**
   * {@inheritdoc}
   */
501
  public function getSettings() {
502
503
504
505
    // @todo FieldTypePluginManager maintains its own static cache. However, do
    //   some CPU and memory profiling to see if it's worth statically caching
    //   $field_type_info, or the default field and instance settings, within
    //   $this.
506
    $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
507

508
    $settings = $field_type_manager->getDefaultSettings($this->type);
509
    return $this->settings + $settings;
510
511
512
513
514
  }

  /**
   * {@inheritdoc}
   */
515
516
  public function getSetting($setting_name) {
    // @todo See getSettings() about potentially statically caching this.
517
518
    // We assume here that one call to array_key_exists() is more efficient
    // than calling getSettings() when all we need is a single setting.
519
    if (array_key_exists($setting_name, $this->settings)) {
520
521
      return $this->settings[$setting_name];
    }
522
523
524
    $settings = $this->getSettings();
    if (array_key_exists($setting_name, $settings)) {
      return $settings[$setting_name];
525
    }
526
527
528
    else {
      return NULL;
    }
529
530
531
532
533
  }

  /**
   * {@inheritdoc}
   */
534
  public function isTranslatable() {
535
536
537
    return $this->translatable;
  }

538
539
540
541
542
543
544
545
  /**
   * {@inheritdoc}
   */
  public function isRevisionable() {
    // All configurable fields are revisionable.
    return TRUE;
  }

546
  /**
547
   * {@inheritdoc}
548
549
550
551
552
553
   */
  public function setTranslatable($translatable) {
    $this->translatable = $translatable;
    return $this;
  }

554
555
556
557
558
559
560
  /**
   * {@inheritdoc}
   */
  public function getProvider() {
    return 'field';
  }

561
562
563
  /**
   * {@inheritdoc}
   */
564
  public function getLabel() {
565
566
567
568
569
570
    return $this->label();
  }

  /**
   * {@inheritdoc}
   */
571
  public function getDescription() {
572
    return NULL;
573
574
575
576
577
  }

  /**
   * {@inheritdoc}
   */
578
  public function getCardinality() {
579
580
581
582
583
584
    return $this->cardinality;
  }

  /**
   * {@inheritdoc}
   */
585
  public function isRequired() {
586
587
588
    return FALSE;
  }

589
590
591
  /**
   * {@inheritdoc}
   */
592
593
  public function isMultiple() {
    $cardinality = $this->getCardinality();
594
    return ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) || ($cardinality > 1);
595
596
  }

597
598
599
600
601
602
603
  /**
   * {@inheritdoc}
   */
  public function isLocked() {
    return $this->locked;
  }

604
605
  /**
   * {@inheritdoc}
606
607
608
609
610
611
612
   */
  public function getTargetEntityTypeId() {
    return $this->entity_type;
  }

  /**
   * {@inheritdoc}
613
   */
614
  public function isQueryable() {
615
616
617
    return TRUE;
  }

618
619
620
621
622
623
624
625
  /**
   * A list of columns that can not be used as field type columns.
   *
   * @return array
   */
  public static function getReservedColumns() {
    return array('deleted');
  }
626

627
628
629
  /**
   * Determines whether a field has any data.
   *
630
   * @return bool
631
632
633
   *   TRUE if the field has data for any entity; FALSE otherwise.
   */
  public function hasData() {
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
    return $this->entityCount(TRUE);
  }

  /**
   * Determines the number of entities that have field data.
   *
   * @param bool $as_bool
   *   (Optional) Optimises query for hasData(). Defaults to FALSE.
   *
   * @return bool|int
   *   The number of entities that have field data. If $as_bool parameter is
   *   TRUE then the value will either be TRUE or FALSE.
   */
  public function entityCount($as_bool = FALSE) {
    $count = 0;
    $factory = \Drupal::service('entity.query');
    $entity_type = \Drupal::entityManager()->getDefinition($this->entity_type);
    // Entity Query throws an exception if there is no base table.
    if ($entity_type->getBaseTable()) {
      if ($this->deleted) {
        $query = $factory->get($this->entity_type)
          ->condition('id:' . $this->uuid() . '.deleted', 1);
656
      }
657
658
659
660
661
662
663
664
665
      elseif ($this->getBundles()) {
        $storage_details = $this->getSchema();
        $columns = array_keys($storage_details['columns']);
        $query = $factory->get($this->entity_type);
        $group = $query->orConditionGroup();
        foreach ($columns as $column) {
          $group->exists($this->name . '.' . $column);
        }
        $query = $query->condition($group);
666
      }
667
668
669
670
671
672
673
674
675
676
677

      if (isset($query)) {
        $query
          ->count()
          ->accessCheck(FALSE);
        // If we are performing the query just to check if the field has data
        // limit the number of rows returned by the subquery.
        if ($as_bool) {
          $query->range(0, 1);
        }
        $count = $query->execute();
678
679
680
      }
    }

681
682
683
684
685
686
    if ($as_bool) {
      return (bool) $count;
    }
    else {
      return (int) $count;
    }
687
  }
688
689
690
691
692
693
694
695
696

  /**
   * Implements the magic __sleep() method.
   *
   * Using the Serialize interface and serialize() / unserialize() methods
   * breaks entity forms in PHP 5.4.
   * @todo Investigate in https://drupal.org/node/2074253.
   */
  public function __sleep() {
697
    // Only serialize properties from self::toArray().
698
699
700
701
    $properties = array_keys(array_intersect_key($this->toArray(), get_object_vars($this)));
    // Serialize $entityTypeId property so that toArray() works when waking up.
    $properties[] = 'entityTypeId';
    return $properties;
702
703
704
705
706
707
  }

  /**
   * Implements the magic __wakeup() method.
   */
  public function __wakeup() {
708
709
    // Run the values from self::toArray() through __construct().
    $values = array_intersect_key($this->toArray(), get_object_vars($this));
710
711
712
    $this->__construct($values);
  }

713
714
715
716
717
718
719
  /**
   * {@inheritdoc}
   */
  public function getConstraints() {
    return array();
  }

720
721
722
723
724
725
726
  /**
   * {@inheritdoc}
   */
  public function getConstraint($constraint_name) {
    return NULL;
  }

727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
  /**
   * {@inheritdoc}
   */
  public function getPropertyDefinition($name) {
    if (!isset($this->propertyDefinitions)) {
      $this->getPropertyDefinitions();
    }
    if (isset($this->propertyDefinitions[$name])) {
      return $this->propertyDefinitions[$name];
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getPropertyDefinitions() {
    if (!isset($this->propertyDefinitions)) {
      $class = $this->getFieldItemClass();
      $this->propertyDefinitions = $class::propertyDefinitions($this);
    }
    return $this->propertyDefinitions;
  }

  /**
   * {@inheritdoc}
   */
  public function getPropertyNames() {
    return array_keys($this->getPropertyDefinitions());
  }

  /**
   * {@inheritdoc}
   */
  public function getMainPropertyName() {
    $class = $this->getFieldItemClass();
    return $class::mainPropertyName();
  }

  /**
   * Helper to retrieve the field item class.
   */
  protected function getFieldItemClass() {
769
770
771
    $type_definition = \Drupal::typedDataManager()
      ->getDefinition('field_item:' . $this->getType());
    return $type_definition['class'];
772
773
  }

774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
  /**
   * Loads a field config entity based on the entity type and field name.
   *
   * @param string $entity_type_id
   *   ID of the entity type.
   * @param string $field_name
   *   Name of the field.
   *
   * @return static
   *   The field config entity if one exists for the provided field name,
   *   otherwise NULL.
   */
  public static function loadByName($entity_type_id, $field_name) {
    return \Drupal::entityManager()->getStorage('field_config')->load($entity_type_id . '.' . $field_name);
  }

790
}