Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / web / core / tests / Drupal / Tests / Core / Plugin / DefaultPluginManagerTest.php
1 <?php
2
3 namespace Drupal\Tests\Core\Plugin;
4
5 use Drupal\Component\Plugin\Definition\PluginDefinition;
6 use Drupal\Component\Plugin\Exception\PluginException;
7 use Drupal\Core\Extension\ModuleHandlerInterface;
8 use Drupal\Core\Form\FormStateInterface;
9 use Drupal\Core\Plugin\PluginFormInterface;
10 use Drupal\Tests\UnitTestCase;
11
12 /**
13  * Tests the DefaultPluginManager.
14  *
15  * @group Plugin
16  *
17  * @coversDefaultClass \Drupal\Core\Plugin\DefaultPluginManager
18  */
19 class DefaultPluginManagerTest extends UnitTestCase {
20
21   /**
22    * The expected plugin definitions.
23    *
24    * @var array
25    */
26   protected $expectedDefinitions;
27
28   /**
29    * The namespaces to look for plugin definitions.
30    *
31    * @var \Traversable
32    */
33   protected $namespaces;
34
35   /**
36    * {@inheritdoc}
37    */
38   protected function setUp() {
39     $this->expectedDefinitions = [
40       'apple' => [
41         'id' => 'apple',
42         'label' => 'Apple',
43         'color' => 'green',
44         'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Apple',
45       ],
46       'banana' => [
47         'id' => 'banana',
48         'label' => 'Banana',
49         'color' => 'yellow',
50         'uses' => [
51           'bread' => 'Banana bread',
52           'loaf' => [
53             'singular' => '@count loaf',
54             'plural' => '@count loaves',
55           ],
56         ],
57         'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Banana',
58       ],
59     ];
60
61     $this->namespaces = new \ArrayObject();
62     $this->namespaces['Drupal\plugin_test'] = $this->root . '/core/modules/system/tests/modules/plugin_test/src';
63   }
64
65   /**
66    * Tests the plugin manager with a plugin that extends a non-installed class.
67    */
68   public function testDefaultPluginManagerWithPluginExtendingNonInstalledClass() {
69     $definitions = [];
70     $definitions['extending_non_installed_class'] = [
71       'id' => 'extending_non_installed_class',
72       'label' => 'A plugin whose class is extending from a non-installed module class',
73       'color' => 'pink',
74       'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\ExtendingNonInstalledClass',
75       'provider' => 'plugin_test',
76     ];
77
78     $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
79     $plugin_manager = new TestPluginManager($this->namespaces, $definitions, $module_handler, 'test_alter_hook', '\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface');
80     $plugin_manager->getDefinition('plugin_test', FALSE);
81     $this->assertTrue(TRUE, 'No PHP fatal error occurred when retrieving the definitions of a module with plugins that depend on a non-installed module class should not cause a PHP fatal.');
82   }
83
84   /**
85    * Tests the plugin manager with a disabled module.
86    */
87   public function testDefaultPluginManagerWithDisabledModule() {
88     $definitions = $this->expectedDefinitions;
89     $definitions['cherry'] = [
90       'id' => 'cherry',
91       'label' => 'Cherry',
92       'color' => 'red',
93       'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Cherry',
94       'provider' => 'disabled_module',
95     ];
96
97     $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
98
99     $module_handler->expects($this->once())
100       ->method('moduleExists')
101       ->with('disabled_module')
102       ->will($this->returnValue(FALSE));
103
104     $plugin_manager = new TestPluginManager($this->namespaces, $definitions, $module_handler, 'test_alter_hook', '\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface');
105
106     $this->assertEmpty($plugin_manager->getDefinition('cherry', FALSE), 'Plugin information of a disabled module is not available');
107   }
108
109   /**
110    * Tests the plugin manager and object plugin definitions.
111    */
112   public function testDefaultPluginManagerWithObjects() {
113     $definitions = $this->expectedDefinitions;
114     $definitions['cherry'] = (object) [
115       'id' => 'cherry',
116       'label' => 'Cherry',
117       'color' => 'red',
118       'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Cherry',
119       'provider' => 'disabled_module',
120     ];
121
122     $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
123
124     $module_handler->expects($this->once())
125       ->method('moduleExists')
126       ->with('disabled_module')
127       ->will($this->returnValue(FALSE));
128
129     $plugin_manager = new TestPluginManager($this->namespaces, $definitions, $module_handler, 'test_alter_hook', '\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface');
130
131     $this->assertEmpty($plugin_manager->getDefinition('cherry', FALSE), 'Plugin information is available');
132   }
133
134   /**
135    * Tests the plugin manager with no cache and altering.
136    */
137   public function testDefaultPluginManager() {
138     $plugin_manager = new TestPluginManager($this->namespaces, $this->expectedDefinitions, NULL, NULL, '\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface');
139     $this->assertEquals($this->expectedDefinitions, $plugin_manager->getDefinitions());
140     $this->assertEquals($this->expectedDefinitions['banana'], $plugin_manager->getDefinition('banana'));
141   }
142
143   /**
144    * Tests the plugin manager with no cache and altering.
145    */
146   public function testDefaultPluginManagerWithAlter() {
147     $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
148       ->disableOriginalConstructor()
149       ->getMock();
150
151     // Configure the stub.
152     $alter_hook_name = $this->randomMachineName();
153     $module_handler->expects($this->once())
154       ->method('alter')
155       ->with($this->equalTo($alter_hook_name), $this->equalTo($this->expectedDefinitions));
156
157     $plugin_manager = new TestPluginManager($this->namespaces, $this->expectedDefinitions, $module_handler, $alter_hook_name, '\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface');
158
159     $this->assertEquals($this->expectedDefinitions, $plugin_manager->getDefinitions());
160     $this->assertEquals($this->expectedDefinitions['banana'], $plugin_manager->getDefinition('banana'));
161   }
162
163   /**
164    * Tests the plugin manager with caching and altering.
165    */
166   public function testDefaultPluginManagerWithEmptyCache() {
167     $cid = $this->randomMachineName();
168     $cache_backend = $this->getMockBuilder('Drupal\Core\Cache\MemoryBackend')
169       ->disableOriginalConstructor()
170       ->getMock();
171     $cache_backend
172       ->expects($this->once())
173       ->method('get')
174       ->with($cid)
175       ->will($this->returnValue(FALSE));
176     $cache_backend
177       ->expects($this->once())
178       ->method('set')
179       ->with($cid, $this->expectedDefinitions);
180
181     $plugin_manager = new TestPluginManager($this->namespaces, $this->expectedDefinitions, NULL, NULL, '\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface');
182     $plugin_manager->setCacheBackend($cache_backend, $cid);
183
184     $this->assertEquals($this->expectedDefinitions, $plugin_manager->getDefinitions());
185     $this->assertEquals($this->expectedDefinitions['banana'], $plugin_manager->getDefinition('banana'));
186   }
187
188   /**
189    * Tests the plugin manager with caching and altering.
190    */
191   public function testDefaultPluginManagerWithFilledCache() {
192     $cid = $this->randomMachineName();
193     $cache_backend = $this->getMockBuilder('Drupal\Core\Cache\MemoryBackend')
194       ->disableOriginalConstructor()
195       ->getMock();
196     $cache_backend
197       ->expects($this->once())
198       ->method('get')
199       ->with($cid)
200       ->will($this->returnValue((object) ['data' => $this->expectedDefinitions]));
201     $cache_backend
202       ->expects($this->never())
203       ->method('set');
204
205     $plugin_manager = new TestPluginManager($this->namespaces, $this->expectedDefinitions, NULL, NULL, '\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface');
206     $plugin_manager->setCacheBackend($cache_backend, $cid);
207
208     $this->assertEquals($this->expectedDefinitions, $plugin_manager->getDefinitions());
209   }
210
211   /**
212    * Tests the plugin manager with caching disabled.
213    */
214   public function testDefaultPluginManagerNoCache() {
215     $plugin_manager = new TestPluginManager($this->namespaces, $this->expectedDefinitions, NULL, NULL, '\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface');
216
217     $cid = $this->randomMachineName();
218     $cache_backend = $this->getMockBuilder('Drupal\Core\Cache\MemoryBackend')
219       ->disableOriginalConstructor()
220       ->getMock();
221     $cache_backend
222       ->expects($this->never())
223       ->method('get');
224     $cache_backend
225       ->expects($this->never())
226       ->method('set');
227     $plugin_manager->setCacheBackend($cache_backend, $cid);
228
229     $plugin_manager->useCaches(FALSE);
230
231     $this->assertEquals($this->expectedDefinitions, $plugin_manager->getDefinitions());
232     $this->assertEquals($this->expectedDefinitions['banana'], $plugin_manager->getDefinition('banana'));
233   }
234
235   /**
236    * Tests the plugin manager cache clear with tags.
237    */
238   public function testCacheClearWithTags() {
239     $cid = $this->randomMachineName();
240     $cache_backend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
241     $cache_tags_invalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface');
242     $cache_tags_invalidator
243       ->expects($this->once())
244       ->method('invalidateTags')
245       ->with(['tag']);
246     $cache_backend
247       ->expects($this->never())
248       ->method('deleteMultiple');
249
250     $this->getContainerWithCacheTagsInvalidator($cache_tags_invalidator);
251
252     $plugin_manager = new TestPluginManager($this->namespaces, $this->expectedDefinitions, NULL, NULL, '\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface');
253     $plugin_manager->setCacheBackend($cache_backend, $cid, ['tag']);
254
255     $plugin_manager->clearCachedDefinitions();
256   }
257
258   /**
259    * Tests plugins with the proper interface.
260    *
261    * @covers ::createInstance
262    */
263   public function testCreateInstanceWithJustValidInterfaces() {
264     $plugin_manager = new TestPluginManager($this->namespaces, $this->expectedDefinitions, NULL, NULL, '\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface');
265
266     foreach ($this->expectedDefinitions as $plugin_id => $definition) {
267       $this->assertNotNull($plugin_manager->createInstance($plugin_id));
268     }
269   }
270
271   /**
272    * Tests plugins without the proper interface.
273    *
274    * @covers ::createInstance
275    */
276   public function testCreateInstanceWithInvalidInterfaces() {
277     $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
278
279     $module_handler->expects($this->any())
280       ->method('moduleExists')
281       ->with('plugin_test')
282       ->willReturn(TRUE);
283
284     $this->expectedDefinitions['kale'] = [
285       'id' => 'kale',
286       'label' => 'Kale',
287       'color' => 'green',
288       'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Kale',
289       'provider' => 'plugin_test',
290     ];
291     $this->expectedDefinitions['apple']['provider'] = 'plugin_test';
292     $this->expectedDefinitions['banana']['provider'] = 'plugin_test';
293
294     $plugin_manager = new TestPluginManager($this->namespaces, $this->expectedDefinitions, $module_handler, NULL, '\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface');
295     $this->setExpectedException(PluginException::class, 'Plugin "kale" (Drupal\plugin_test\Plugin\plugin_test\fruit\Kale) must implement interface \Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface');
296     $plugin_manager->createInstance('kale');
297   }
298
299   /**
300    * Tests plugins without a required interface.
301    *
302    * @covers ::getDefinitions
303    */
304   public function testGetDefinitionsWithoutRequiredInterface() {
305     $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
306
307     $module_handler->expects($this->any())
308       ->method('moduleExists')
309       ->with('plugin_test')
310       ->willReturn(FALSE);
311
312     $this->expectedDefinitions['kale'] = [
313       'id' => 'kale',
314       'label' => 'Kale',
315       'color' => 'green',
316       'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Kale',
317       'provider' => 'plugin_test',
318     ];
319     $this->expectedDefinitions['apple']['provider'] = 'plugin_test';
320     $this->expectedDefinitions['banana']['provider'] = 'plugin_test';
321
322     $plugin_manager = new TestPluginManager($this->namespaces, $this->expectedDefinitions, $module_handler, NULL);
323     $this->assertInternalType('array', $plugin_manager->getDefinitions());
324   }
325
326   /**
327    * @covers ::getCacheContexts
328    */
329   public function testGetCacheContexts() {
330     $module_handler = $this->prophesize(ModuleHandlerInterface::class);
331     $plugin_manager = new TestPluginManager($this->namespaces, $this->expectedDefinitions, $module_handler->reveal(), NULL);
332     $cache_contexts = $plugin_manager->getCacheContexts();
333     $this->assertInternalType('array', $cache_contexts);
334     array_map(function ($cache_context) {
335       $this->assertInternalType('string', $cache_context);
336     }, $cache_contexts);
337   }
338
339   /**
340    * @covers ::getCacheTags
341    */
342   public function testGetCacheTags() {
343     $module_handler = $this->prophesize(ModuleHandlerInterface::class);
344     $plugin_manager = new TestPluginManager($this->namespaces, $this->expectedDefinitions, $module_handler->reveal(), NULL);
345     $cache_tags = $plugin_manager->getCacheTags();
346     $this->assertInternalType('array', $cache_tags);
347     array_map(function ($cache_tag) {
348       $this->assertInternalType('string', $cache_tag);
349     }, $cache_tags);
350   }
351
352   /**
353    * @covers ::getCacheMaxAge
354    */
355   public function testGetCacheMaxAge() {
356     $module_handler = $this->prophesize(ModuleHandlerInterface::class);
357     $plugin_manager = new TestPluginManager($this->namespaces, $this->expectedDefinitions, $module_handler->reveal(), NULL);
358     $cache_max_age = $plugin_manager->getCacheMaxAge();
359     $this->assertInternalType('int', $cache_max_age);
360   }
361
362   /**
363    * @covers ::findDefinitions
364    * @covers ::extractProviderFromDefinition
365    */
366   public function testProviderExists() {
367     $definitions = [];
368     $definitions['array_based_found'] = ['provider' => 'module_found'];
369     $definitions['array_based_missing'] = ['provider' => 'module_missing'];
370     $definitions['stdclass_based_found'] = (object) ['provider' => 'module_found'];
371     $definitions['stdclass_based_missing'] = (object) ['provider' => 'module_missing'];
372     $definitions['classed_object_found'] = new ObjectDefinition(['provider' => 'module_found']);
373     $definitions['classed_object_missing'] = new ObjectDefinition(['provider' => 'module_missing']);
374
375     $expected = [];
376     $expected['array_based_found'] = $definitions['array_based_found'];
377     $expected['stdclass_based_found'] = $definitions['stdclass_based_found'];
378     $expected['classed_object_found'] = $definitions['classed_object_found'];
379
380     $module_handler = $this->prophesize(ModuleHandlerInterface::class);
381     $module_handler->moduleExists('module_found')->willReturn(TRUE)->shouldBeCalled();
382     $module_handler->moduleExists('module_missing')->willReturn(FALSE)->shouldBeCalled();
383     $plugin_manager = new TestPluginManager($this->namespaces, $definitions, $module_handler->reveal());
384     $result = $plugin_manager->getDefinitions();
385     $this->assertEquals($expected, $result);
386   }
387
388   /**
389    * @covers ::processDefinition
390    * @dataProvider providerTestProcessDefinition
391    */
392   public function testProcessDefinition($definition, $expected) {
393     $module_handler = $this->prophesize(ModuleHandlerInterface::class);
394     $plugin_manager = new TestPluginManagerWithDefaults($this->namespaces, $this->expectedDefinitions, $module_handler->reveal(), NULL);
395
396     $plugin_manager->processDefinition($definition, 'the_plugin_id');
397     $this->assertEquals($expected, $definition);
398   }
399
400   public function providerTestProcessDefinition() {
401     $data = [];
402
403     $data['merge'][] = [
404       'foo' => [
405         'bar' => [
406           'asdf',
407         ],
408       ],
409     ];
410     $data['merge'][] = [
411       'foo' => [
412         'bar' => [
413           'baz',
414           'asdf',
415         ],
416       ],
417     ];
418
419     $object_definition = (object) [
420       'foo' => [
421         'bar' => [
422           'asdf',
423         ],
424       ],
425     ];
426     $data['object_definition'] = [$object_definition, clone $object_definition];
427
428     $data['no_form'][] = ['class' => TestPluginForm::class];
429     $data['no_form'][] = [
430       'class' => TestPluginForm::class,
431       'foo' => ['bar' => ['baz']],
432     ];
433
434     $data['default_form'][] = ['class' => TestPluginForm::class, 'forms' => ['configure' => 'stdClass']];
435     $data['default_form'][] = [
436       'class' => TestPluginForm::class,
437       'forms' => ['configure' => 'stdClass'],
438       'foo' => ['bar' => ['baz']],
439     ];
440
441     $data['class_with_slashes'][] = [
442       'class' => '\Drupal\Tests\Core\Plugin\TestPluginForm',
443     ];
444     $data['class_with_slashes'][] = [
445       'class' => 'Drupal\Tests\Core\Plugin\TestPluginForm',
446       'foo' => ['bar' => ['baz']],
447     ];
448
449     $data['object_with_class_with_slashes'][] = (new PluginDefinition())->setClass('\Drupal\Tests\Core\Plugin\TestPluginForm');
450     $data['object_with_class_with_slashes'][] = (new PluginDefinition())->setClass('Drupal\Tests\Core\Plugin\TestPluginForm');
451     return $data;
452   }
453
454 }
455
456 class TestPluginManagerWithDefaults extends TestPluginManager {
457
458   /**
459    * {@inheritdoc}
460    */
461   protected $defaults = [
462     'foo' => [
463       'bar' => [
464         'baz',
465       ],
466     ],
467   ];
468
469 }
470
471 class TestPluginForm implements PluginFormInterface {
472
473   /**
474    * {@inheritdoc}
475    */
476   public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
477     return [];
478   }
479
480   /**
481    * {@inheritdoc}
482    */
483   public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
484   }
485
486   /**
487    * {@inheritdoc}
488    */
489   public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
490   }
491
492 }
493 class ObjectDefinition extends PluginDefinition {
494
495   /**
496    * ObjectDefinition constructor.
497    *
498    * @param array $definition
499    */
500   public function __construct(array $definition) {
501     foreach ($definition as $property => $value) {
502       $this->{$property} = $value;
503     }
504   }
505
506 }