BaseFieldDefinition.php 20.4 KB
Newer Older
1
2
3
4
<?php

/**
 * @file
5
 * Contains \Drupal\Core\Field\BaseFieldDefinition.
6
7
 */

8
9
namespace Drupal\Core\Field;

10
use Drupal\Core\Cache\UnchangingCacheableDependencyTrait;
11
use Drupal\Core\Entity\FieldableEntityInterface;
12
use Drupal\Core\Field\Entity\BaseFieldOverride;
13
14
use Drupal\Core\Field\TypedData\FieldItemDataDefinition;
use Drupal\Core\TypedData\ListDataDefinition;
15
use Drupal\Core\TypedData\OptionsProviderInterface;
16
17
18
19

/**
 * A class for defining entity fields.
 */
20
class BaseFieldDefinition extends ListDataDefinition implements FieldDefinitionInterface, FieldStorageDefinitionInterface {
21

22
23
  use UnchangingCacheableDependencyTrait;

24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
  /**
   * The field type.
   *
   * @var string
   */
  protected $type;

  /**
   * An array of field property definitions.
   *
   * @var \Drupal\Core\TypedData\DataDefinitionInterface[]
   *
   * @see \Drupal\Core\TypedData\ComplexDataDefinitionInterface::getPropertyDefinitions()
   */
  protected $propertyDefinitions;
39

40
41
42
43
44
45
46
  /**
   * The field schema.
   *
   * @var array
   */
  protected $schema;

47
48
49
50
51
  /**
   * @var array
   */
  protected $indexes = array();

52
  /**
53
   * Creates a new field definition.
54
   *
55
56
   * @param string $type
   *   The type of the field.
57
   *
58
   * @return static
59
   *   A new field definition object.
60
   */
61
  public static function create($type) {
62
63
64
    $field_definition = new static(array());
    $field_definition->type = $type;
    $field_definition->itemDefinition = FieldItemDataDefinition::create($field_definition);
65
66
    // Create a definition for the items, and initialize it with the default
    // settings for the field type.
67
    // @todo Cleanup in https://www.drupal.org/node/2116341.
68
    $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
69
    $default_settings = $field_type_manager->getDefaultStorageSettings($type) + $field_type_manager->getDefaultFieldSettings($type);
70
71
72
    $field_definition->itemDefinition->setSettings($default_settings);
    return $field_definition;
  }
73

74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
  /**
   * Creates a new field definition based upon a field storage definition.
   *
   * In cases where one needs a field storage definitions to act like full
   * field definitions, this creates a new field definition based upon the
   * (limited) information available. That way it is possible to use the field
   * definition in places where a full field definition is required; e.g., with
   * widgets or formatters.
   *
   * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $definition
   *   The field storage definition to base the new field definition upon.
   *
   * @return $this
   */
  public static function createFromFieldStorageDefinition(FieldStorageDefinitionInterface $definition) {
    return static::create($definition->getType())
      ->setCardinality($definition->getCardinality())
      ->setConstraints($definition->getConstraints())
      ->setCustomStorage($definition->hasCustomStorage())
      ->setDescription($definition->getDescription())
      ->setLabel($definition->getLabel())
      ->setName($definition->getName())
      ->setProvider($definition->getProvider())
      ->setQueryable($definition->isQueryable())
      ->setRevisionable($definition->isRevisionable())
      ->setSettings($definition->getSettings())
      ->setTargetEntityTypeId($definition->getTargetEntityTypeId())
      ->setTranslatable($definition->isTranslatable());
  }

104
105
106
107
108
109
110
  /**
   * {@inheritdoc}
   */
  public static function createFromItemType($item_type) {
    // The data type of a field item is in the form of "field_item:$field_type".
    $parts = explode(':', $item_type, 2);
    return static::create($parts[1]);
111
112
113
114
115
  }

  /**
   * {@inheritdoc}
   */
116
  public function getName() {
117
118
119
120
121
122
123
124
125
    return $this->definition['field_name'];
  }

  /**
   * Sets the field name.
   *
   * @param string $name
   *   The field name to set.
   *
126
   * @return static
127
128
   *   The object itself for chaining.
   */
129
  public function setName($name) {
130
131
132
133
134
135
136
    $this->definition['field_name'] = $name;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
137
  public function getType() {
138
    return $this->type;
139
140
141
  }

  /**
142
   * {@inheritdoc}
143
   */
144
  public function getSettings() {
145
    return $this->getItemDefinition()->getSettings();
146
147
148
  }

  /**
149
   * {@inheritdoc}
150
   *
151
152
   * Note that the method does not unset existing settings not specified in the
   * incoming $settings array.
153
   *
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
   * For example:
   * @code
   *   // Given these are the default settings.
   *   $field_definition->getSettings() === [
   *     'fruit' => 'apple',
   *     'season' => 'summer',
   *   ];
   *   // Change only the 'fruit' setting.
   *   $field_definition->setSettings(['fruit' => 'banana']);
   *   // The 'season' setting persists unchanged.
   *   $field_definition->getSettings() === [
   *     'fruit' => 'banana',
   *     'season' => 'summer',
   *   ];
   * @endcode
   *
   * For clarity, it is preferred to use setSetting() if not all available
   * settings are supplied.
172
   */
173
  public function setSettings(array $settings) {
174
    // Assign settings individually, in order to keep the current values
175
176
177
178
    // of settings not specified in $settings.
    foreach ($settings as $setting_name => $setting) {
      $this->getItemDefinition()->setSetting($setting_name, $setting);
    }
179
180
181
182
183
184
    return $this;
  }

  /**
   * {@inheritdoc}
   */
185
  public function getSetting($setting_name) {
186
    return $this->getItemDefinition()->getSetting($setting_name);
187
188
189
  }

  /**
190
   * {@inheritdoc}
191
   */
192
  public function setSetting($setting_name, $value) {
193
194
    $this->getItemDefinition()->setSetting($setting_name, $value);
    return $this;
195
196
  }

197
198
199
200
  /**
   * {@inheritdoc}
   */
  public function getProvider() {
201
    return isset($this->definition['provider']) ? $this->definition['provider'] : NULL;
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
  }

  /**
   * Sets the name of the provider of this field.
   *
   * @param string $provider
   *   The provider name to set.
   *
   * @return $this
   */
  public function setProvider($provider) {
    $this->definition['provider'] = $provider;
    return $this;
  }

217
218
219
  /**
   * {@inheritdoc}
   */
220
  public function isTranslatable() {
221
222
223
224
225
226
227
228
229
    return !empty($this->definition['translatable']);
  }

  /**
   * Sets whether the field is translatable.
   *
   * @param bool $translatable
   *   Whether the field is translatable.
   *
230
   * @return $this
231
232
233
234
235
236
237
   *   The object itself for chaining.
   */
  public function setTranslatable($translatable) {
    $this->definition['translatable'] = $translatable;
    return $this;
  }

238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
  /**
   * {@inheritdoc}
   */
  public function isRevisionable() {
    return !empty($this->definition['revisionable']);
  }

  /**
   * Sets whether the field is revisionable.
   *
   * @param bool $revisionable
   *   Whether the field is revisionable.
   *
   * @return $this
   *   The object itself for chaining.
   */
  public function setRevisionable($revisionable) {
    $this->definition['revisionable'] = $revisionable;
    return $this;
  }

259
260
261
  /**
   * {@inheritdoc}
   */
262
  public function getCardinality() {
263
264
265
266
    // @todo: Allow to control this.
    return isset($this->definition['cardinality']) ? $this->definition['cardinality'] : 1;
  }

267
268
269
270
  /**
   * Sets the maximum number of items allowed for the field.
   *
   * Possible values are positive integers or
271
   * FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED.
272
273
274
275
276
277
278
279
280
281
282
   *
   * @param int $cardinality
   *  The field cardinality.
   *
   * @return $this
   */
  public function setCardinality($cardinality) {
    $this->definition['cardinality'] = $cardinality;
    return $this;
  }

283
284
285
  /**
   * {@inheritdoc}
   */
286
287
  public function isMultiple() {
    $cardinality = $this->getCardinality();
288
289
290
    return ($cardinality == static::CARDINALITY_UNLIMITED) || ($cardinality > 1);
  }

291
292
293
  /**
   * {@inheritdoc}
   */
294
  public function isQueryable() {
295
296
297
298
299
300
301
302
303
    return isset($this->definition['queryable']) ? $this->definition['queryable'] : !$this->isComputed();
  }

  /**
   * Sets whether the field is queryable.
   *
   * @param bool $queryable
   *   Whether the field is queryable.
   *
304
   * @return static
305
306
   *   The object itself for chaining.
   */
307
  public function setQueryable($queryable) {
308
    $this->definition['queryable'] = $queryable;
309
310
311
312
313
314
    return $this;
  }

  /**
   * Sets constraints for a given field item property.
   *
315
316
317
318
   * Note: this overwrites any existing property constraints. If you need to
   * add to the existing constraints, use
   * \Drupal\Core\Field\BaseFieldDefinition::addPropertyConstraints()
   *
319
320
321
322
323
   * @param string $name
   *   The name of the property to set constraints for.
   * @param array $constraints
   *   The constraints to set.
   *
324
   * @return static
325
326
327
   *   The object itself for chaining.
   */
  public function setPropertyConstraints($name, array $constraints) {
328
329
330
    $item_constraints = $this->getItemDefinition()->getConstraints();
    $item_constraints['ComplexData'][$name] = $constraints;
    $this->getItemDefinition()->setConstraints($item_constraints);
331
332
333
    return $this;
  }

334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
  /**
   * Adds constraints for a given field item property.
   *
   * Adds a constraint to a property of a base field item. e.g.
   * @code
   * // Limit the field item's value property to the range 0 through 10.
   * // e.g. $node->size->value.
   * $field->addPropertyConstraints('value', [
   *   'Range' => [
   *     'min' => 0,
   *     'max' => 10,
   *   ]
   * ]);
   * @endcode
   *
   * If you want to add a validation constraint that applies to the
   * \Drupal\Core\Field\FieldItemList, use BaseFieldDefinition::addConstraint()
   * instead.
   *
   * Note: passing a new set of options for an existing property constraint will
   * overwrite with the new options.
   *
   * @param string $name
   *   The name of the property to set constraints for.
   * @param array $constraints
   *   The constraints to set.
   *
   * @return static
   *   The object itself for chaining.
   *
   * @see \Drupal\Core\Field\BaseFieldDefinition::addConstraint()
   */
  public function addPropertyConstraints($name, array $constraints) {
    $item_constraints = $this->getItemDefinition()->getConstraint('ComplexData') ?: [];
    if (isset($item_constraints[$name])) {
      // Add the new property constraints, overwriting as required.
      $item_constraints[$name] = $constraints + $item_constraints[$name];
    }
    else {
      $item_constraints[$name] = $constraints;
    }
    $this->getItemDefinition()->addConstraint('ComplexData', $item_constraints);
    return $this;
  }

379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
  /**
   * Sets the display options for the field in forms or rendered entities.
   *
   * This enables generic rendering of the field with widgets / formatters,
   * including automated support for "In place editing", and with optional
   * configurability in the "Manage display" / "Manage form display" UI screens.
   *
   * Unless this method is called, the field remains invisible (or requires
   * ad-hoc rendering logic).
   *
   * @param string $display_context
   *   The display context. Either 'view' or 'form'.
   * @param array $options
   *   An array of display options. Refer to
   *   \Drupal\Core\Field\FieldDefinitionInterface::getDisplayOptions() for
   *   a list of supported keys. The options should include at least a 'weight',
   *   or specify 'type' = 'hidden'. The 'default_widget' / 'default_formatter'
   *   for the field type will be used if no 'type' is specified.
   *
   * @return static
   *   The object itself for chaining.
   */
  public function setDisplayOptions($display_context, array $options) {
    $this->definition['display'][$display_context]['options'] = $options;
    return $this;
  }

  /**
   * Sets whether the display for the field can be configured.
   *
   * @param string $display_context
   *   The display context. Either 'view' or 'form'.
   * @param bool $configurable
   *   Whether the display options can be configured (e.g., via the "Manage
   *   display" / "Manage form display" UI screens). If TRUE, the options
   *   specified via getDisplayOptions() act as defaults.
   *
   * @return static
   *   The object itself for chaining.
   */
  public function setDisplayConfigurable($display_context, $configurable) {
    // If no explicit display options have been specified, default to 'hidden'.
    if (empty($this->definition['display'][$display_context])) {
      $this->definition['display'][$display_context]['options'] = array('type' => 'hidden');
    }
    $this->definition['display'][$display_context]['configurable'] = $configurable;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getDisplayOptions($display_context) {
    return isset($this->definition['display'][$display_context]['options']) ? $this->definition['display'][$display_context]['options'] : NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function isDisplayConfigurable($display_context) {
    return isset($this->definition['display'][$display_context]['configurable']) ? $this->definition['display'][$display_context]['configurable'] : FALSE;
  }

442
443
444
445
446
447
448
449
450
451
452
453
454
455
  /**
   * {@inheritdoc}
   */
  public function getDefaultValueLiteral() {
    return isset($this->definition['default_value']) ? $this->definition['default_value'] : [];
  }

  /**
   * {@inheritdoc}
   */
  public function getDefaultValueCallback() {
    return isset($this->definition['default_value_callback']) ? $this->definition['default_value_callback'] : NULL;
  }

456
457
458
  /**
   * {@inheritdoc}
   */
459
  public function getDefaultValue(FieldableEntityInterface $entity) {
460
    // Allow custom default values function.
461
462
    if ($callback = $this->getDefaultValueCallback()) {
      $value = call_user_func($callback, $entity, $this);
463
464
    }
    else {
465
      $value = $this->getDefaultValueLiteral();
466
    }
467
468
469
470
471
472
473
474
    // Normalize into the "array keyed by delta" format.
    if (isset($value) && !is_array($value)) {
      $properties = $this->getPropertyNames();
      $property = reset($properties);
      $value = array(
        array($property => $value),
      );
    }
475
476
477
478
479
480
    // Allow the field type to process default values.
    $field_item_list_class = $this->getClass();
    return $field_item_list_class::processDefaultValue($value, $entity, $this);
  }

  /**
481
   * {@inheritdoc}
482
483
   */
  public function setDefaultValue($value) {
484
485
486
487
488
    if ($value === NULL) {
      $value = [];
    }
    // Unless the value is an empty array, we may need to transform it.
    if (!is_array($value) || !empty($value)) {
489
490
491
      if (!is_array($value)) {
        $value = array(array($this->getMainPropertyName() => $value));
      }
492
      elseif (is_array($value) && !is_numeric(array_keys($value)[0])) {
493
494
495
        $value = array(0 => $value);
      }
    }
496
497
    $this->definition['default_value'] = $value;
    return $this;
498
499
  }

500
501
502
503
504
505
506
507
508
509
510
  /**
   * {@inheritdoc}
   */
  public function setDefaultValueCallback($callback) {
    if (isset($callback) && !is_string($callback)) {
      throw new \InvalidArgumentException('Default value callback must be a string, like "function_name" or "ClassName::methodName"');
    }
    $this->definition['default_value_callback'] = $callback;
    return $this;
  }

511
512
513
  /**
   * {@inheritdoc}
   */
514
  public function getOptionsProvider($property_name, FieldableEntityInterface $entity) {
515
516
517
518
519
    // If the field item class implements the interface, create an orphaned
    // runtime item object, so that it can be used as the options provider
    // without modifying the entity being worked on.
    if (is_subclass_of($this->getFieldItemClass(), '\Drupal\Core\TypedData\OptionsProviderInterface')) {
      $items = $entity->get($this->getName());
520
      return \Drupal::service('plugin.manager.field.field_type')->createFieldItem($items, 0);
521
522
523
524
525
    }
    // @todo: Allow setting custom options provider, see
    // https://www.drupal.org/node/2002138.
  }

526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
  /**
   * {@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.
   *
   * @todo: Remove once getClass() adds in defaults. See
568
   * https://www.drupal.org/node/2116341.
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
   */
  protected function getFieldItemClass() {
    if ($class = $this->getItemDefinition()->getClass()) {
      return $class;
    }
    else {
      $type_definition = \Drupal::typedDataManager()
        ->getDefinition($this->getItemDefinition()->getDataType());
      return $type_definition['class'];
    }
  }

  /**
   * {@inheritdoc}
   */
  public function __sleep() {
    // Do not serialize the statically cached property definitions.
    $vars = get_object_vars($this);
    unset($vars['propertyDefinitions']);
    return array_keys($vars);
  }

591
592
593
594
595
596
597
598
599
600
601
602
603
  /**
   * {@inheritdoc}
   */
  public function getTargetEntityTypeId() {
    return isset($this->definition['entity_type']) ? $this->definition['entity_type'] : NULL;
  }

  /**
   * Sets the ID of the type of the entity this field is attached to.
   *
   * @param string $entity_type_id
   *   The name of the target entity type to set.
   *
604
   * @return $this
605
606
607
608
609
610
   */
  public function setTargetEntityTypeId($entity_type_id) {
    $this->definition['entity_type'] = $entity_type_id;
    return $this;
  }

611
612
613
  /**
   * {@inheritdoc}
   */
614
  public function getTargetBundle() {
615
616
617
618
619
620
621
622
623
624
625
    return isset($this->definition['bundle']) ? $this->definition['bundle'] : NULL;
  }

  /**
   * Sets the bundle this field is defined for.
   *
   * @param string|null $bundle
   *   The bundle, or NULL if the field is not bundle-specific.
   *
   * @return $this
   */
626
  public function setTargetBundle($bundle) {
627
628
629
630
    $this->definition['bundle'] = $bundle;
    return $this;
  }

631
632
633
634
635
636
  /**
   * {@inheritdoc}
   */
  public function getSchema() {
    if (!isset($this->schema)) {
      // Get the schema from the field item class.
637
      $definition = \Drupal::service('plugin.manager.field.field_type')->getDefinition($this->getType());
638
639
      $class = $definition['class'];
      $schema = $class::schema($this);
640
641
642
      // Fill in default values.
      $schema += array(
        'columns' => array(),
643
        'unique keys' => array(),
644
645
646
        'indexes' => array(),
        'foreign keys' => array(),
      );
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672

      // 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;
  }

  /**
   * {@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'];
  }

  /**
673
   * {@inheritdoc}
674
   */
675
676
  public function hasCustomStorage() {
    return !empty($this->definition['custom_storage']) || $this->isComputed();
677
678
  }

679
680
681
  /**
   * {@inheritdoc}
   */
682
683
  public function isBaseField() {
    return TRUE;
684
685
686
687
688
689
  }

  /**
   * Sets the storage behavior for this field.
   *
   * @param bool $custom_storage
690
   *   Pass FALSE if the storage takes care of storing the field,
691
692
693
   *   TRUE otherwise.
   *
   * @return $this
694
695
696
   *
   * @throws \LogicException
   *   Thrown if custom storage is to be set to FALSE for a computed field.
697
698
   */
  public function setCustomStorage($custom_storage) {
699
700
701
    if (!$custom_storage && $this->isComputed()) {
      throw new \LogicException("Entity storage cannot store a computed field.");
    }
702
703
704
705
    $this->definition['custom_storage'] = $custom_storage;
    return $this;
  }

706
707
708
709
710
711
712
  /**
   * {@inheritdoc}
   */
  public function getFieldStorageDefinition() {
    return $this;
  }

713
714
715
716
717
718
719
  /**
   * {@inheritdoc}
   */
  public function getUniqueStorageIdentifier() {
    return $this->getTargetEntityTypeId() . '-' . $this->getName();
  }

720
721
722
723
724
725
726
727
728
729
730
  /**
   * {@inheritdoc}
   */
  public function getConfig($bundle) {
    $override = BaseFieldOverride::loadByName($this->getTargetEntityTypeId(), $bundle, $this->getName());
    if ($override) {
      return $override;
    }
    return BaseFieldOverride::createFromBaseFieldDefinition($this, $bundle);
  }

731
}