1<?php
2
3namespace Drupal\Tests\Core\Field;
4
5use Drupal\Component\Plugin\Exception\PluginNotFoundException;
6use Drupal\Core\Cache\CacheBackendInterface;
7use Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface;
8use Drupal\Core\Entity\EntityFieldManagerInterface;
9use Drupal\Core\Entity\EntityInterface;
10use Drupal\Core\Entity\EntityTypeInterface;
11use Drupal\Core\Entity\EntityTypeManagerInterface;
12use Drupal\Core\Field\FieldDefinitionInterface;
13use Drupal\Core\Field\FieldDefinitionListener;
14use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
15use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
16use Drupal\Tests\UnitTestCase;
17use Prophecy\Argument;
18
19/**
20 * @coversDefaultClass \Drupal\Core\Field\FieldDefinitionListener
21 * @group Field
22 */
23class FieldDefinitionListenerTest extends UnitTestCase {
24
25  /**
26   * The key-value factory.
27   *
28   * @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface|\Prophecy\Prophecy\ProphecyInterface
29   */
30  protected $keyValueFactory;
31
32  /**
33   * The entity type manager.
34   *
35   * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\Prophecy\Prophecy\ProphecyInterface
36   */
37  protected $entityTypeManager;
38
39  /**
40   * The entity field manager.
41   *
42   * @var \Drupal\Core\Entity\EntityFieldManagerInterface|\Prophecy\Prophecy\ProphecyInterface
43   */
44  protected $entityFieldManager;
45
46  /**
47   * The cache backend.
48   *
49   * @var \Drupal\Core\Cache\CacheBackendInterface|\Prophecy\Prophecy\ProphecyInterface
50   */
51  protected $cacheBackend;
52
53  /**
54   * The field definition listener under test.
55   *
56   * @var \Drupal\Core\Field\FieldDefinitionListener
57   */
58  protected $fieldDefinitionListener;
59
60  /**
61   * {@inheritdoc}
62   */
63  protected function setUp(): void {
64    parent::setUp();
65
66    $this->keyValueFactory = $this->prophesize(KeyValueFactoryInterface::class);
67    $this->entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
68    $this->entityFieldManager = $this->prophesize(EntityFieldManagerInterface::class);
69    $this->cacheBackend = $this->prophesize(CacheBackendInterface::class);
70
71    $this->fieldDefinitionListener = new FieldDefinitionListener($this->entityTypeManager->reveal(), $this->entityFieldManager->reveal(), $this->keyValueFactory->reveal(), $this->cacheBackend->reveal());
72  }
73
74  /**
75   * Sets up the entity type manager to be tested.
76   *
77   * @param \Drupal\Core\Entity\EntityTypeInterface[]|\Prophecy\Prophecy\ProphecyInterface[] $definitions
78   *   (optional) An array of entity type definitions.
79   */
80  protected function setUpEntityTypeManager($definitions = []) {
81    $class = $this->getMockClass(EntityInterface::class);
82    foreach ($definitions as $key => $entity_type) {
83      // \Drupal\Core\Entity\EntityTypeInterface::getLinkTemplates() is called
84      // by \Drupal\Core\Entity\EntityTypeManager::processDefinition() so it must
85      // always be mocked.
86      $entity_type->getLinkTemplates()->willReturn([]);
87
88      // Give the entity type a legitimate class to return.
89      $entity_type->getClass()->willReturn($class);
90
91      $definitions[$key] = $entity_type->reveal();
92    }
93
94    $this->entityTypeManager->getDefinition(Argument::cetera())
95      ->will(function ($args) use ($definitions) {
96        $entity_type_id = $args[0];
97        $exception_on_invalid = $args[1];
98        if (isset($definitions[$entity_type_id])) {
99          return $definitions[$entity_type_id];
100        }
101        elseif (!$exception_on_invalid) {
102          return NULL;
103        }
104        else {
105          throw new PluginNotFoundException($entity_type_id);
106        }
107      });
108    $this->entityTypeManager->getDefinitions()->willReturn($definitions);
109  }
110
111  /**
112   * @covers ::onFieldDefinitionCreate
113   */
114  public function testOnFieldDefinitionCreateNewField() {
115    $field_definition = $this->prophesize(FieldDefinitionInterface::class);
116    $field_definition->getTargetEntityTypeId()->willReturn('test_entity_type');
117    $field_definition->getTargetBundle()->willReturn('test_bundle');
118    $field_definition->getName()->willReturn('test_field');
119    $field_definition->getType()->willReturn('test_type');
120
121    $storage = $this->prophesize(DynamicallyFieldableEntityStorageInterface::class);
122    $storage->onFieldDefinitionCreate($field_definition->reveal())->shouldBeCalledTimes(1);
123    $this->entityTypeManager->getStorage('test_entity_type')->willReturn($storage->reveal());
124
125    $entity = $this->prophesize(EntityTypeInterface::class);
126    $this->setUpEntityTypeManager(['test_entity_type' => $entity]);
127
128    // Set up the stored bundle field map.
129    $key_value_store = $this->prophesize(KeyValueStoreInterface::class);
130    $this->keyValueFactory->get('entity.definitions.bundle_field_map')->willReturn($key_value_store->reveal());
131    $key_value_store->get('test_entity_type')->willReturn([]);
132    $key_value_store->set('test_entity_type', [
133      'test_field' => [
134        'type' => 'test_type',
135        'bundles' => ['test_bundle' => 'test_bundle'],
136      ],
137    ])->shouldBeCalled();
138
139    $this->fieldDefinitionListener->onFieldDefinitionCreate($field_definition->reveal());
140  }
141
142  /**
143   * @covers ::onFieldDefinitionCreate
144   */
145  public function testOnFieldDefinitionCreateExistingField() {
146    $field_definition = $this->prophesize(FieldDefinitionInterface::class);
147    $field_definition->getTargetEntityTypeId()->willReturn('test_entity_type');
148    $field_definition->getTargetBundle()->willReturn('test_bundle');
149    $field_definition->getName()->willReturn('test_field');
150
151    $storage = $this->prophesize(DynamicallyFieldableEntityStorageInterface::class);
152    $storage->onFieldDefinitionCreate($field_definition->reveal())->shouldBeCalledTimes(1);
153    $this->entityTypeManager->getStorage('test_entity_type')->willReturn($storage->reveal());
154
155    $entity = $this->prophesize(EntityTypeInterface::class);
156    $this->setUpEntityTypeManager(['test_entity_type' => $entity]);
157
158    // Set up the stored bundle field map.
159    $key_value_store = $this->prophesize(KeyValueStoreInterface::class);
160    $this->keyValueFactory->get('entity.definitions.bundle_field_map')->willReturn($key_value_store->reveal());
161    $key_value_store->get('test_entity_type')->willReturn([
162      'test_field' => [
163        'type' => 'test_type',
164        'bundles' => ['existing_bundle' => 'existing_bundle'],
165      ],
166    ]);
167    $key_value_store->set('test_entity_type', [
168      'test_field' => [
169        'type' => 'test_type',
170        'bundles' => ['existing_bundle' => 'existing_bundle', 'test_bundle' => 'test_bundle'],
171      ],
172    ])
173      ->shouldBeCalled();
174
175    $this->fieldDefinitionListener->onFieldDefinitionCreate($field_definition->reveal());
176  }
177
178  /**
179   * @covers ::onFieldDefinitionUpdate
180   */
181  public function testOnFieldDefinitionUpdate() {
182    $field_definition = $this->prophesize(FieldDefinitionInterface::class);
183    $field_definition->getTargetEntityTypeId()->willReturn('test_entity_type');
184
185    $storage = $this->prophesize(DynamicallyFieldableEntityStorageInterface::class);
186    $storage->onFieldDefinitionUpdate($field_definition->reveal(), $field_definition->reveal())->shouldBeCalledTimes(1);
187    $this->entityTypeManager->getStorage('test_entity_type')->willReturn($storage->reveal());
188
189    $entity = $this->prophesize(EntityTypeInterface::class);
190    $this->setUpEntityTypeManager(['test_entity_type' => $entity]);
191
192    $this->fieldDefinitionListener->onFieldDefinitionUpdate($field_definition->reveal(), $field_definition->reveal());
193  }
194
195  /**
196   * @covers ::onFieldDefinitionDelete
197   */
198  public function testOnFieldDefinitionDeleteMultipleBundles() {
199    $field_definition = $this->prophesize(FieldDefinitionInterface::class);
200    $field_definition->getTargetEntityTypeId()->willReturn('test_entity_type');
201    $field_definition->getTargetBundle()->willReturn('test_bundle');
202    $field_definition->getName()->willReturn('test_field');
203
204    $storage = $this->prophesize(DynamicallyFieldableEntityStorageInterface::class);
205    $storage->onFieldDefinitionDelete($field_definition->reveal())->shouldBeCalledTimes(1);
206    $this->entityTypeManager->getStorage('test_entity_type')->willReturn($storage->reveal());
207
208    $entity = $this->prophesize(EntityTypeInterface::class);
209    $this->setUpEntityTypeManager(['test_entity_type' => $entity]);
210
211    // Set up the stored bundle field map.
212    $key_value_store = $this->prophesize(KeyValueStoreInterface::class);
213    $this->keyValueFactory->get('entity.definitions.bundle_field_map')->willReturn($key_value_store->reveal());
214    $key_value_store->get('test_entity_type')->willReturn([
215      'test_field' => [
216        'type' => 'test_type',
217        'bundles' => ['test_bundle' => 'test_bundle'],
218      ],
219      'second_field' => [
220        'type' => 'test_type',
221        'bundles' => ['test_bundle' => 'test_bundle'],
222      ],
223    ]);
224    $key_value_store->set('test_entity_type', [
225      'second_field' => [
226        'type' => 'test_type',
227        'bundles' => ['test_bundle' => 'test_bundle'],
228      ],
229    ])
230      ->shouldBeCalled();
231
232    $this->fieldDefinitionListener->onFieldDefinitionDelete($field_definition->reveal());
233  }
234
235  /**
236   * @covers ::onFieldDefinitionDelete
237   */
238  public function testOnFieldDefinitionDeleteSingleBundles() {
239    $field_definition = $this->prophesize(FieldDefinitionInterface::class);
240    $field_definition->getTargetEntityTypeId()->willReturn('test_entity_type');
241    $field_definition->getTargetBundle()->willReturn('test_bundle');
242    $field_definition->getName()->willReturn('test_field');
243
244    $storage = $this->prophesize(DynamicallyFieldableEntityStorageInterface::class);
245    $storage->onFieldDefinitionDelete($field_definition->reveal())->shouldBeCalledTimes(1);
246    $this->entityTypeManager->getStorage('test_entity_type')->willReturn($storage->reveal());
247
248    $entity = $this->prophesize(EntityTypeInterface::class);
249    $this->setUpEntityTypeManager(['test_entity_type' => $entity]);
250
251    // Set up the stored bundle field map.
252    $key_value_store = $this->prophesize(KeyValueStoreInterface::class);
253    $this->keyValueFactory->get('entity.definitions.bundle_field_map')->willReturn($key_value_store->reveal());
254    $key_value_store->get('test_entity_type')->willReturn([
255      'test_field' => [
256        'type' => 'test_type',
257        'bundles' => ['test_bundle' => 'test_bundle', 'second_bundle' => 'second_bundle'],
258      ],
259    ]);
260    $key_value_store->set('test_entity_type', [
261      'test_field' => [
262        'type' => 'test_type',
263        'bundles' => ['second_bundle' => 'second_bundle'],
264      ],
265    ])
266      ->shouldBeCalled();
267
268    $this->fieldDefinitionListener->onFieldDefinitionDelete($field_definition->reveal());
269  }
270
271}
272