1<?php 2 3/** 4 * @file 5 * Contains \Drupal\Tests\Core\Entity\EntityTypeManagerTest. 6 */ 7 8namespace Drupal\Tests\Core\Entity; 9 10use Drupal\Component\Plugin\Discovery\DiscoveryInterface; 11use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException; 12use Drupal\Component\Plugin\Exception\PluginNotFoundException; 13use Drupal\Core\Cache\CacheBackendInterface; 14use Drupal\Core\DependencyInjection\ContainerInjectionInterface; 15use Drupal\Core\Entity\EntityHandlerBase; 16use Drupal\Core\Entity\EntityInterface; 17use Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface; 18use Drupal\Core\Entity\EntityManagerInterface; 19use Drupal\Core\Entity\EntityTypeInterface; 20use Drupal\Core\Entity\EntityTypeManager; 21use Drupal\Core\Entity\EntityTypeManagerInterface; 22use Drupal\Core\Entity\Exception\InvalidLinkTemplateException; 23use Drupal\Core\Extension\ModuleHandlerInterface; 24use Drupal\Core\StringTranslation\TranslationInterface; 25use Drupal\Tests\UnitTestCase; 26use Prophecy\Argument; 27use Symfony\Component\DependencyInjection\ContainerInterface; 28 29/** 30 * @coversDefaultClass \Drupal\Core\Entity\EntityTypeManager 31 * @group Entity 32 */ 33class EntityTypeManagerTest extends UnitTestCase { 34 35 /** 36 * The entity type manager under test. 37 * 38 * @var \Drupal\Core\Entity\EntityTypeManager 39 */ 40 protected $entityTypeManager; 41 42 /** 43 * The translation manager. 44 * 45 * @var \Drupal\Core\StringTranslation\TranslationInterface|\Prophecy\Prophecy\ProphecyInterface 46 */ 47 protected $translationManager; 48 49 /** 50 * The plugin discovery. 51 * 52 * @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface|\Prophecy\Prophecy\ProphecyInterface 53 */ 54 protected $discovery; 55 56 /** 57 * The module handler. 58 * 59 * @var \Drupal\Core\Extension\ModuleHandlerInterface|\Prophecy\Prophecy\ProphecyInterface 60 */ 61 protected $moduleHandler; 62 63 /** 64 * The cache backend. 65 * 66 * @var \Drupal\Core\Cache\CacheBackendInterface|\Prophecy\Prophecy\ProphecyInterface 67 */ 68 protected $cacheBackend; 69 70 /** 71 * The entity last installed schema repository. 72 * 73 * @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface|\Prophecy\Prophecy\ProphecyInterface 74 */ 75 protected $entityLastInstalledSchemaRepository; 76 77 /** 78 * {@inheritdoc} 79 */ 80 protected function setUp() { 81 parent::setUp(); 82 83 $this->moduleHandler = $this->prophesize(ModuleHandlerInterface::class); 84 $this->moduleHandler->getImplementations('entity_type_build')->willReturn([]); 85 $this->moduleHandler->alter('entity_type', Argument::type('array'))->willReturn(NULL); 86 87 $this->cacheBackend = $this->prophesize(CacheBackendInterface::class); 88 $this->translationManager = $this->prophesize(TranslationInterface::class); 89 $this->entityLastInstalledSchemaRepository = $this->prophesize(EntityLastInstalledSchemaRepositoryInterface::class); 90 91 $this->entityTypeManager = new TestEntityTypeManager(new \ArrayObject(), $this->moduleHandler->reveal(), $this->cacheBackend->reveal(), $this->translationManager->reveal(), $this->getClassResolverStub(), $this->entityLastInstalledSchemaRepository->reveal()); 92 $this->discovery = $this->prophesize(DiscoveryInterface::class); 93 $this->entityTypeManager->setDiscovery($this->discovery->reveal()); 94 } 95 96 /** 97 * Sets up the entity type manager to be tested. 98 * 99 * @param \Drupal\Core\Entity\EntityTypeInterface[]|\Prophecy\Prophecy\ProphecyInterface[] $definitions 100 * (optional) An array of entity type definitions. 101 */ 102 protected function setUpEntityTypeDefinitions($definitions = []) { 103 $class = $this->getMockClass(EntityInterface::class); 104 foreach ($definitions as $key => $entity_type) { 105 // \Drupal\Core\Entity\EntityTypeInterface::getLinkTemplates() is called 106 // by \Drupal\Core\Entity\EntityManager::processDefinition() so it must 107 // always be mocked. 108 $entity_type->getLinkTemplates()->willReturn([]); 109 110 // Give the entity type a legitimate class to return. 111 $entity_type->getClass()->willReturn($class); 112 $entity_type->setClass($class)->willReturn($entity_type->reveal()); 113 114 $definitions[$key] = $entity_type->reveal(); 115 } 116 117 $this->discovery->getDefinition(Argument::cetera()) 118 ->will(function ($args) use ($definitions) { 119 $entity_type_id = $args[0]; 120 $exception_on_invalid = $args[1]; 121 if (isset($definitions[$entity_type_id])) { 122 return $definitions[$entity_type_id]; 123 } 124 elseif (!$exception_on_invalid) { 125 return NULL; 126 } 127 else { 128 throw new PluginNotFoundException($entity_type_id); 129 } 130 }); 131 $this->discovery->getDefinitions()->willReturn($definitions); 132 133 } 134 135 /** 136 * Tests the hasHandler() method. 137 * 138 * @covers ::hasHandler 139 * 140 * @dataProvider providerTestHasHandler 141 */ 142 public function testHasHandler($entity_type_id, $expected) { 143 $apple = $this->prophesize(EntityTypeInterface::class); 144 $apple->hasHandlerClass('storage')->willReturn(TRUE); 145 146 $banana = $this->prophesize(EntityTypeInterface::class); 147 $banana->hasHandlerClass('storage')->willReturn(FALSE); 148 149 $this->setUpEntityTypeDefinitions([ 150 'apple' => $apple, 151 'banana' => $banana, 152 ]); 153 154 $entity_type = $this->entityTypeManager->hasHandler($entity_type_id, 'storage'); 155 $this->assertSame($expected, $entity_type); 156 } 157 158 /** 159 * Provides test data for testHasHandler(). 160 * 161 * @return array 162 * Test data. 163 */ 164 public function providerTestHasHandler() { 165 return [ 166 ['apple', TRUE], 167 ['banana', FALSE], 168 ['pear', FALSE], 169 ]; 170 } 171 172 /** 173 * Tests the getStorage() method. 174 * 175 * @covers ::getStorage 176 */ 177 public function testGetStorage() { 178 $class = $this->getTestHandlerClass(); 179 $entity = $this->prophesize(EntityTypeInterface::class); 180 $entity->getHandlerClass('storage')->willReturn($class); 181 $this->setUpEntityTypeDefinitions(['test_entity_type' => $entity]); 182 183 $this->assertInstanceOf($class, $this->entityTypeManager->getStorage('test_entity_type')); 184 } 185 186 /** 187 * Tests the getListBuilder() method. 188 * 189 * @covers ::getListBuilder 190 */ 191 public function testGetListBuilder() { 192 $class = $this->getTestHandlerClass(); 193 $entity = $this->prophesize(EntityTypeInterface::class); 194 $entity->getHandlerClass('list_builder')->willReturn($class); 195 $this->setUpEntityTypeDefinitions(['test_entity_type' => $entity]); 196 197 $this->assertInstanceOf($class, $this->entityTypeManager->getListBuilder('test_entity_type')); 198 } 199 200 /** 201 * Tests the getViewBuilder() method. 202 * 203 * @covers ::getViewBuilder 204 */ 205 public function testGetViewBuilder() { 206 $class = $this->getTestHandlerClass(); 207 $entity = $this->prophesize(EntityTypeInterface::class); 208 $entity->getHandlerClass('view_builder')->willReturn($class); 209 $this->setUpEntityTypeDefinitions(['test_entity_type' => $entity]); 210 211 $this->assertInstanceOf($class, $this->entityTypeManager->getViewBuilder('test_entity_type')); 212 } 213 214 /** 215 * Tests the getAccessControlHandler() method. 216 * 217 * @covers ::getAccessControlHandler 218 */ 219 public function testGetAccessControlHandler() { 220 $class = $this->getTestHandlerClass(); 221 $entity = $this->prophesize(EntityTypeInterface::class); 222 $entity->getHandlerClass('access')->willReturn($class); 223 $this->setUpEntityTypeDefinitions(['test_entity_type' => $entity]); 224 225 $this->assertInstanceOf($class, $this->entityTypeManager->getAccessControlHandler('test_entity_type')); 226 } 227 228 /** 229 * Tests the getFormObject() method. 230 * 231 * @covers ::getFormObject 232 */ 233 public function testGetFormObject() { 234 $apple = $this->prophesize(EntityTypeInterface::class); 235 $apple->getFormClass('default')->willReturn(TestEntityForm::class); 236 237 $banana = $this->prophesize(EntityTypeInterface::class); 238 $banana->getFormClass('default')->willReturn(TestEntityFormInjected::class); 239 240 $this->setUpEntityTypeDefinitions([ 241 'apple' => $apple, 242 'banana' => $banana, 243 ]); 244 245 $apple_form = $this->entityTypeManager->getFormObject('apple', 'default'); 246 $this->assertInstanceOf(TestEntityForm::class, $apple_form); 247 $this->assertAttributeInstanceOf(ModuleHandlerInterface::class, 'moduleHandler', $apple_form); 248 $this->assertAttributeInstanceOf(TranslationInterface::class, 'stringTranslation', $apple_form); 249 250 $banana_form = $this->entityTypeManager->getFormObject('banana', 'default'); 251 $this->assertInstanceOf(TestEntityFormInjected::class, $banana_form); 252 $this->assertAttributeEquals('yellow', 'color', $banana_form); 253 254 } 255 256 /** 257 * Tests the getFormObject() method with an invalid operation. 258 * 259 * @covers ::getFormObject 260 */ 261 public function testGetFormObjectInvalidOperation() { 262 $entity = $this->prophesize(EntityTypeInterface::class); 263 $entity->getFormClass('edit')->willReturn(''); 264 $this->setUpEntityTypeDefinitions(['test_entity_type' => $entity]); 265 266 $this->expectException(InvalidPluginDefinitionException::class); 267 $this->entityTypeManager->getFormObject('test_entity_type', 'edit'); 268 } 269 270 /** 271 * Tests the getHandler() method. 272 * 273 * @covers ::getHandler 274 */ 275 public function testGetHandler() { 276 $class = $this->getTestHandlerClass(); 277 $apple = $this->prophesize(EntityTypeInterface::class); 278 $apple->getHandlerClass('storage')->willReturn($class); 279 280 $this->setUpEntityTypeDefinitions([ 281 'apple' => $apple, 282 ]); 283 284 $apple_controller = $this->entityTypeManager->getHandler('apple', 'storage'); 285 $this->assertInstanceOf($class, $apple_controller); 286 $this->assertAttributeInstanceOf(ModuleHandlerInterface::class, 'moduleHandler', $apple_controller); 287 $this->assertAttributeInstanceOf(TranslationInterface::class, 'stringTranslation', $apple_controller); 288 } 289 290 /** 291 * Tests the getHandler() method when no controller is defined. 292 * 293 * @covers ::getHandler 294 */ 295 public function testGetHandlerMissingHandler() { 296 $entity = $this->prophesize(EntityTypeInterface::class); 297 $entity->getHandlerClass('storage')->willReturn(''); 298 $this->setUpEntityTypeDefinitions(['test_entity_type' => $entity]); 299 $this->expectException(InvalidPluginDefinitionException::class); 300 $this->entityTypeManager->getHandler('test_entity_type', 'storage'); 301 } 302 303 /** 304 * @covers ::getRouteProviders 305 */ 306 public function testGetRouteProviders() { 307 $apple = $this->prophesize(EntityTypeInterface::class); 308 $apple->getRouteProviderClasses()->willReturn(['default' => TestRouteProvider::class]); 309 310 $this->setUpEntityTypeDefinitions([ 311 'apple' => $apple, 312 ]); 313 314 $apple_route_provider = $this->entityTypeManager->getRouteProviders('apple'); 315 $this->assertInstanceOf(TestRouteProvider::class, $apple_route_provider['default']); 316 $this->assertAttributeInstanceOf(ModuleHandlerInterface::class, 'moduleHandler', $apple_route_provider['default']); 317 $this->assertAttributeInstanceOf(TranslationInterface::class, 'stringTranslation', $apple_route_provider['default']); 318 } 319 320 /** 321 * Tests the processDefinition() method. 322 * 323 * @covers ::processDefinition 324 */ 325 public function testProcessDefinition() { 326 $apple = $this->prophesize(EntityTypeInterface::class); 327 $this->setUpEntityTypeDefinitions(['apple' => $apple]); 328 329 $apple->getLinkTemplates()->willReturn(['canonical' => 'path/to/apple']); 330 331 $definition = $apple->reveal(); 332 $this->expectException(InvalidLinkTemplateException::class); 333 $this->expectExceptionMessage("Link template 'canonical' for entity type 'apple' must start with a leading slash, the current link template is 'path/to/apple'"); 334 $this->entityTypeManager->processDefinition($definition, 'apple'); 335 } 336 337 /** 338 * Tests the getDefinition() method. 339 * 340 * @covers ::getDefinition 341 * 342 * @dataProvider providerTestGetDefinition 343 */ 344 public function testGetDefinition($entity_type_id, $expected) { 345 $entity = $this->prophesize(EntityTypeInterface::class); 346 347 $this->setUpEntityTypeDefinitions([ 348 'apple' => $entity, 349 'banana' => $entity, 350 ]); 351 352 $entity_type = $this->entityTypeManager->getDefinition($entity_type_id, FALSE); 353 if ($expected) { 354 $this->assertInstanceOf(EntityTypeInterface::class, $entity_type); 355 } 356 else { 357 $this->assertNull($entity_type); 358 } 359 } 360 361 /** 362 * Provides test data for testGetDefinition(). 363 * 364 * @return array 365 * Test data. 366 */ 367 public function providerTestGetDefinition() { 368 return [ 369 ['apple', TRUE], 370 ['banana', TRUE], 371 ['pear', FALSE], 372 ]; 373 } 374 375 /** 376 * Tests the getDefinition() method with an invalid definition. 377 * 378 * @covers ::getDefinition 379 */ 380 public function testGetDefinitionInvalidException() { 381 $this->setUpEntityTypeDefinitions(); 382 383 $this->expectException(PluginNotFoundException::class); 384 $this->expectExceptionMessage('The "pear" entity type does not exist.'); 385 $this->entityTypeManager->getDefinition('pear', TRUE); 386 } 387 388 /** 389 * Gets a mock controller class name. 390 * 391 * @return string 392 * A mock controller class name. 393 */ 394 protected function getTestHandlerClass() { 395 return get_class($this->getMockForAbstractClass(EntityHandlerBase::class)); 396 } 397 398} 399 400class TestEntityTypeManager extends EntityTypeManager { 401 402 /** 403 * Sets the discovery for the manager. 404 * 405 * @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $discovery 406 * The discovery object. 407 */ 408 public function setDiscovery(DiscoveryInterface $discovery) { 409 $this->discovery = $discovery; 410 } 411 412} 413 414/** 415 * Provides a test entity form. 416 */ 417class TestEntityForm extends EntityHandlerBase { 418 419 /** 420 * The entity manager. 421 * 422 * @var \Drupal\Core\Entity\EntityManagerInterface 423 */ 424 protected $entityManager; 425 426 /** 427 * The entity type manager. 428 * 429 * @var \Drupal\Core\Entity\EntityTypeManagerInterface 430 */ 431 protected $entityTypeManager; 432 433 /** 434 * {@inheritdoc} 435 */ 436 public function getBaseFormId() { 437 return 'the_base_form_id'; 438 } 439 440 /** 441 * {@inheritdoc} 442 */ 443 public function getFormId() { 444 return 'the_form_id'; 445 } 446 447 /** 448 * {@inheritdoc} 449 */ 450 public function setEntity(EntityInterface $entity) { 451 return $this; 452 } 453 454 /** 455 * {@inheritdoc} 456 */ 457 public function setOperation($operation) { 458 return $this; 459 } 460 461 /** 462 * {@inheritdoc} 463 */ 464 public function setEntityManager(EntityManagerInterface $entity_manager) { 465 $this->entityManager = $entity_manager; 466 return $this; 467 } 468 469 /** 470 * {@inheritdoc} 471 */ 472 public function setEntityTypeManager(EntityTypeManagerInterface $entity_type_manager) { 473 $this->entityTypeManager = $entity_type_manager; 474 return $this; 475 } 476 477} 478 479/** 480 * Provides a test entity form that uses injection. 481 */ 482class TestEntityFormInjected extends TestEntityForm implements ContainerInjectionInterface { 483 484 /** 485 * The color of the entity type. 486 * 487 * @var string 488 */ 489 protected $color; 490 491 /** 492 * Constructs a new TestEntityFormInjected. 493 * 494 * @param string $color 495 * The color of the entity type. 496 */ 497 public function __construct($color) { 498 $this->color = $color; 499 } 500 501 /** 502 * {@inheritdoc} 503 */ 504 public static function create(ContainerInterface $container) { 505 return new static('yellow'); 506 } 507 508} 509 510/** 511 * Provides a test entity route provider. 512 */ 513class TestRouteProvider extends EntityHandlerBase { 514 515} 516