FieldConfig.php 22.5 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
114
115
116
117

  /**
   * Flag indicating whether the field is translatable.
   *
   * Defaults to FALSE.
   *
   * @var bool
   */
118
  public $translatable = FALSE;
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
201
202
   * parameter as in this constructor.
   *
   * @see entity_create()
   *
   * @ingroup field_crud
203
   */
204
  public function __construct(array $values, $entity_type = 'field_config') {
205
    // Check required properties.
206
    if (empty($values['name'])) {
207
208
      throw new FieldException('Attempt to create an unnamed field.');
    }
209
    if (!preg_match('/^[_a-z]+[_a-z0-9]*$/', $values['name'])) {
210
      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'])));
211
    }
212
    if (empty($values['type'])) {
213
      throw new FieldException(String::format('Attempt to create field @field_name with no type.', array('@field_name' => $values['name'])));
214
215
    }
    if (empty($values['entity_type'])) {
216
      throw new FieldException(String::format('Attempt to create a field @field_name with no entity_type.', array('@field_name' => $values['name'])));
217
    }
218
219
220
221

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

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

229
230
231
  /**
   * {@inheritdoc}
   */
232
  public function toArray() {
233
234
235
236
    $names = array(
      'uuid',
      'status',
      'langcode',
237
238
      'name',
      'entity_type',
239
240
241
242
243
244
245
      'type',
      'settings',
      'module',
      'locked',
      'cardinality',
      'translatable',
      'indexes',
246
      'dependencies',
247
    );
248
249
250
    $properties = array(
      'id' => $this->id(),
    );
251
252
253
254
255
256
257
    foreach ($names as $name) {
      $properties[$name] = $this->get($name);
    }
    return $properties;
  }

  /**
258
   * Overrides \Drupal\Core\Entity\Entity::preSave().
259
260
261
262
263
   *
   * @throws \Drupal\field\FieldException
   *   If the field definition is invalid.
   * @throws \Drupal\Core\Entity\EntityStorageException
   *   In case of failures at the configuration storage level.
264
   */
265
  public function preSave(EntityStorageInterface $storage) {
266
    // Clear the derived data about the field.
267
    unset($this->schema);
268
269

    if ($this->isNew()) {
270
      $this->preSaveNew($storage);
271
272
    }
    else {
273
      $this->preSaveUpdated($storage);
274
275
276
277
    }
    if (!$this->isSyncing()) {
      // Ensure the correct dependencies are present.
      $this->calculateDependencies();
278
279
    }
  }
280

281
  /**
282
   * Prepares saving a new field definition.
283
   *
284
285
   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
   *   The entity storage.
286
   *
287
   * @throws \Drupal\field\FieldException If the field definition is invalid.
288
   */
289
   protected function preSaveNew(EntityStorageInterface $storage) {
290
    $entity_manager = \Drupal::entityManager();
291
    $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
292

293
294
295
    // Assign the ID.
    $this->id = $this->id();

296
297
    // Field name cannot be longer than FieldConfig::NAME_MAX_LENGTH characters.
    // We use Unicode::strlen() because the DB layer assumes that column widths
298
    // are given in characters rather than bytes.
299
    if (Unicode::strlen($this->name) > static::NAME_MAX_LENGTH) {
300
      throw new FieldException(String::format(
301
302
303
        'Attempt to create a field with an ID longer than @max characters: %name', array(
          '@max' => static::NAME_MAX_LENGTH,
          '%name' => $this->name,
304
305
306
        )
      ));
    }
307

308
309
310
    // Disallow reserved field names.
    $disallowed_field_names = array_keys($entity_manager->getBaseFieldDefinitions($this->entity_type));
    if (in_array($this->name, $disallowed_field_names)) {
311
      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)));
312
313
314
    }

    // Check that the field type is known.
315
    $field_type = $field_type_manager->getDefinition($this->type, FALSE);
316
    if (!$field_type) {
317
      throw new FieldException(String::format('Attempt to create a field of unknown type %type.', array('%type' => $this->type)));
318
    }
319
    $this->module = $field_type['provider'];
320

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

325
326
    // Notify the entity storage.
    $entity_manager->getStorage($this->entity_type)->onFieldCreate($this);
327
  }
328

329
330
331
332
333
334
335
  /**
   * {@inheritdoc}
   */
  public function calculateDependencies() {
    parent::calculateDependencies();
    // Ensure the field is dependent on the providing module.
    $this->addDependency('module', $this->module);
336
337
338
    // 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());
339
340
341
    return $this->dependencies;
  }

342
  /**
343
   * Prepares saving an updated field definition.
344
   *
345
346
   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
   *   The entity storage.
347
   */
348
  protected function preSaveUpdated(EntityStorageInterface $storage) {
349
    $module_handler = \Drupal::moduleHandler();
350
    $entity_manager = \Drupal::entityManager();
351
    $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
352

353
    // Some updates are always disallowed.
354
    if ($this->type != $this->original->type) {
355
356
      throw new FieldException("Cannot change an existing field's type.");
    }
357
    if ($this->entity_type != $this->original->entity_type) {
358
      throw new FieldException("Cannot change an existing field's entity_type.");
359
360
    }

361
362
363
    // 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);
364
365

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

369
    // Notify the storage. The controller can reject the definition
370
371
    // update as invalid by raising an exception, which stops execution before
    // the definition is written to config.
372
    $entity_manager->getStorage($this->entity_type)->onFieldUpdate($this);
373
  }
374

375
376
377
  /**
   * {@inheritdoc}
   */
378
  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
379
    // Clear the cache.
380
    \Drupal::entityManager()->clearCachedFieldDefinitions();
381
382
383
384
385
386
387
388
389

    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();
      }
    }
390
391
392
393
394
  }

  /**
   * {@inheritdoc}
   */
395
  public static function preDelete(EntityStorageInterface $storage, array $fields) {
396
    $state = \Drupal::state();
397
    $instance_storage = \Drupal::entityManager()->getStorage('field_instance_config');
398
399

    // Delete instances first. Note: when deleting a field through
400
    // FieldInstanceConfig::postDelete(), the instances have been deleted already, so
401
402
403
404
405
406
407
    // 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}";
        }
408
      }
409
410
    }
    if ($instance_ids) {
411
      $instances = $instance_storage->loadMultiple($instance_ids);
412
413
414
      // Tag the objects to preserve recursive deletion of the field.
      foreach ($instances as $instance) {
        $instance->noFieldDelete = TRUE;
415
      }
416
      $instance_storage->delete($instances);
417
    }
418

419
420
421
422
423
    // 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) {
424
        $config = $field->toArray();
425
426
427
428
429
        $config['deleted'] = TRUE;
        $config['bundles'] = $field->getBundles();
        $deleted_fields[$field->uuid] = $config;
      }
    }
430

431
432
    $state->set('field.field.deleted', $deleted_fields);
  }
433

434
435
436
  /**
   * {@inheritdoc}
   */
437
  public static function postDelete(EntityStorageInterface $storage, array $fields) {
438
439
440
    // Notify the storage.
    foreach ($fields as $field) {
      if (!$field->deleted) {
441
        \Drupal::entityManager()->getStorage($field->entity_type)->onFieldDelete($field);
442
        $field->deleted = TRUE;
443
      }
444
    }
445
446

    // Clear the cache.
447
    \Drupal::entityManager()->clearCachedFieldDefinitions();
448
449
450
  }

  /**
451
   * {@inheritdoc}
452
453
454
   */
  public function getSchema() {
    if (!isset($this->schema)) {
455
      // Get the schema from the field item class.
456
      $class = $this->getFieldItemClass();
457
458
      $schema = $class::schema($this);
      // Fill in default values for optional entries.
459
460
461
462
463
      $schema += array(
        'unique keys' => array(),
        'indexes' => array(),
        'foreign keys' => array(),
      );
464
465

      // Check that the schema does not include forbidden column names.
466
      if (array_intersect(array_keys($schema['columns']), static::getReservedColumns())) {
467
        throw new FieldException(String::format('Illegal field type @field_type on @field_name.', array('@field_type' => $this->type, '@field_name' => $this->name)));
468
469
470
471
472
473
474
475
476
477
478
479
      }

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

480
481
482
483
484
485
486
  /**
   * {@inheritdoc}
   */
  public function hasCustomStorage() {
    return FALSE;
  }

487
488
489
490
491
492
493
494
495
496
497
498
499
500
  /**
   * {@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'];
  }

501
  /**
502
   * {@inheritdoc}
503
504
505
   */
  public function getBundles() {
    if (empty($this->deleted)) {
506
      $map = \Drupal::entityManager()->getFieldMap();
507
508
      if (isset($map[$this->entity_type][$this->name]['bundles'])) {
        return $map[$this->entity_type][$this->name]['bundles'];
509
510
511
512
513
      }
    }
    return array();
  }

514
515
516
  /**
   * {@inheritdoc}
   */
517
  public function getName() {
518
    return $this->name;
519
520
521
522
523
  }

  /**
   * {@inheritdoc}
   */
524
  public function getType() {
525
526
527
528
529
530
    return $this->type;
  }

  /**
   * {@inheritdoc}
   */
531
  public function getSettings() {
532
533
534
535
    // @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.
536
    $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
537

538
    $settings = $field_type_manager->getDefaultSettings($this->type);
539
    return $this->settings + $settings;
540
541
542
543
544
  }

  /**
   * {@inheritdoc}
   */
545
546
  public function getSetting($setting_name) {
    // @todo See getSettings() about potentially statically caching this.
547
548
    // We assume here that one call to array_key_exists() is more efficient
    // than calling getSettings() when all we need is a single setting.
549
    if (array_key_exists($setting_name, $this->settings)) {
550
551
      return $this->settings[$setting_name];
    }
552
553
554
    $settings = $this->getSettings();
    if (array_key_exists($setting_name, $settings)) {
      return $settings[$setting_name];
555
    }
556
557
558
    else {
      return NULL;
    }
559
560
561
562
563
  }

  /**
   * {@inheritdoc}
   */
564
  public function isTranslatable() {
565
566
567
    return $this->translatable;
  }

568
569
570
571
572
573
574
575
  /**
   * {@inheritdoc}
   */
  public function isRevisionable() {
    // All configurable fields are revisionable.
    return TRUE;
  }

576
577
578
579
580
581
  /**
   * Sets whether the field is translatable.
   *
   * @param bool $translatable
   *   Whether the field is translatable.
   *
582
   * @return $this
583
584
585
586
587
588
   */
  public function setTranslatable($translatable) {
    $this->translatable = $translatable;
    return $this;
  }

589
590
591
592
593
594
595
  /**
   * {@inheritdoc}
   */
  public function getProvider() {
    return 'field';
  }

596
597
598
  /**
   * {@inheritdoc}
   */
599
  public function getLabel() {
600
601
602
603
604
605
    return $this->label();
  }

  /**
   * {@inheritdoc}
   */
606
  public function getDescription() {
607
    return NULL;
608
609
610
611
612
  }

  /**
   * {@inheritdoc}
   */
613
  public function getCardinality() {
614
615
616
617
618
619
    return $this->cardinality;
  }

  /**
   * {@inheritdoc}
   */
620
  public function isRequired() {
621
622
623
    return FALSE;
  }

624
625
626
  /**
   * {@inheritdoc}
   */
627
628
  public function isMultiple() {
    $cardinality = $this->getCardinality();
629
    return ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) || ($cardinality > 1);
630
631
  }

632
633
634
635
636
637
638
  /**
   * {@inheritdoc}
   */
  public function isLocked() {
    return $this->locked;
  }

639
640
  /**
   * {@inheritdoc}
641
642
643
644
645
646
647
   */
  public function getTargetEntityTypeId() {
    return $this->entity_type;
  }

  /**
   * {@inheritdoc}
648
   */
649
  public function isQueryable() {
650
651
652
    return TRUE;
  }

653
654
655
656
657
658
659
660
  /**
   * A list of columns that can not be used as field type columns.
   *
   * @return array
   */
  public static function getReservedColumns() {
    return array('deleted');
  }
661

662
663
664
  /**
   * Determines whether a field has any data.
   *
665
   * @return bool
666
667
668
   *   TRUE if the field has data for any entity; FALSE otherwise.
   */
  public function hasData() {
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
    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);
691
      }
692
693
694
695
696
697
698
699
700
      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);
701
      }
702
703
704
705
706
707
708
709
710
711
712

      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();
713
714
715
      }
    }

716
717
718
719
720
721
    if ($as_bool) {
      return (bool) $count;
    }
    else {
      return (int) $count;
    }
722
  }
723
724
725
726
727
728
729
730
731

  /**
   * 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() {
732
733
    // Only serialize properties from self::toArray().
    return array_keys(array_intersect_key($this->toArray(), get_object_vars($this)));
734
735
736
737
738
739
  }

  /**
   * Implements the magic __wakeup() method.
   */
  public function __wakeup() {
740
741
    // Run the values from self::toArray() through __construct().
    $values = array_intersect_key($this->toArray(), get_object_vars($this));
742
743
744
    $this->__construct($values);
  }

745
746
747
748
749
750
751
  /**
   * {@inheritdoc}
   */
  public function getConstraints() {
    return array();
  }

752
753
754
755
756
757
758
  /**
   * {@inheritdoc}
   */
  public function getConstraint($constraint_name) {
    return NULL;
  }

759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
  /**
   * {@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() {
801
802
803
    $type_definition = \Drupal::typedDataManager()
      ->getDefinition('field_item:' . $this->getType());
    return $type_definition['class'];
804
805
  }

806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
  /**
   * 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);
  }

822
}