EntityCrudHookTest.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. <?php
  2. namespace Drupal\KernelTests\Core\Entity;
  3. use Drupal\comment\Entity\Comment;
  4. use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
  5. use Drupal\comment\Tests\CommentTestTrait;
  6. use Drupal\Core\Database\Database;
  7. use Drupal\Core\Language\LanguageInterface;
  8. use Drupal\block\Entity\Block;
  9. use Drupal\entity_test\Entity\EntityTest;
  10. use Drupal\node\Entity\NodeType;
  11. use Drupal\taxonomy\Entity\Term;
  12. use Drupal\node\Entity\Node;
  13. use Drupal\taxonomy\Entity\Vocabulary;
  14. use Drupal\user\Entity\User;
  15. use Drupal\file\Entity\File;
  16. /**
  17. * Tests the invocation of hooks when creating, inserting, loading, updating or
  18. * deleting an entity.
  19. *
  20. * Tested hooks are:
  21. * - hook_entity_insert() and hook_ENTITY_TYPE_insert()
  22. * - hook_entity_preload()
  23. * - hook_entity_load() and hook_ENTITY_TYPE_load()
  24. * - hook_entity_update() and hook_ENTITY_TYPE_update()
  25. * - hook_entity_predelete() and hook_ENTITY_TYPE_predelete()
  26. * - hook_entity_delete() and hook_ENTITY_TYPE_delete()
  27. *
  28. * These hooks are each tested for several entity types.
  29. *
  30. * @group Entity
  31. */
  32. class EntityCrudHookTest extends EntityKernelTestBase {
  33. use CommentTestTrait;
  34. /**
  35. * Modules to enable.
  36. *
  37. * @var array
  38. */
  39. public static $modules = [
  40. 'block',
  41. 'block_test',
  42. 'entity_crud_hook_test',
  43. 'file',
  44. 'taxonomy',
  45. 'node',
  46. 'comment',
  47. ];
  48. protected $ids = [];
  49. protected function setUp() {
  50. parent::setUp();
  51. $this->installSchema('user', ['users_data']);
  52. $this->installSchema('file', ['file_usage']);
  53. $this->installSchema('node', ['node_access']);
  54. $this->installSchema('comment', ['comment_entity_statistics']);
  55. $this->installConfig(['node', 'comment']);
  56. }
  57. /**
  58. * Checks the order of CRUD hook execution messages.
  59. *
  60. * Module entity_crud_hook_test implements all core entity CRUD hooks and
  61. * stores a message for each in $GLOBALS['entity_crud_hook_test'].
  62. *
  63. * @param $messages
  64. * An array of plain-text messages in the order they should appear.
  65. */
  66. protected function assertHookMessageOrder($messages) {
  67. $positions = [];
  68. foreach ($messages as $message) {
  69. // Verify that each message is found and record its position.
  70. $position = array_search($message, $GLOBALS['entity_crud_hook_test']);
  71. $this->assertNotFalse($position, $message);
  72. $positions[] = $position;
  73. }
  74. // Sort the positions and ensure they remain in the same order.
  75. $sorted = $positions;
  76. sort($sorted);
  77. $this->assertTrue($sorted == $positions, 'The hook messages appear in the correct order.');
  78. }
  79. /**
  80. * Tests hook invocations for CRUD operations on blocks.
  81. */
  82. public function testBlockHooks() {
  83. $entity = Block::create([
  84. 'id' => 'stark_test_html',
  85. 'plugin' => 'test_html',
  86. 'theme' => 'stark',
  87. ]);
  88. $this->assertHookMessageOrder([
  89. 'entity_crud_hook_test_block_create called',
  90. 'entity_crud_hook_test_entity_create called for type block',
  91. ]);
  92. $GLOBALS['entity_crud_hook_test'] = [];
  93. $entity->save();
  94. $this->assertHookMessageOrder([
  95. 'entity_crud_hook_test_block_presave called',
  96. 'entity_crud_hook_test_entity_presave called for type block',
  97. 'entity_crud_hook_test_block_insert called',
  98. 'entity_crud_hook_test_entity_insert called for type block',
  99. ]);
  100. $GLOBALS['entity_crud_hook_test'] = [];
  101. $entity = Block::load($entity->id());
  102. $this->assertHookMessageOrder([
  103. 'entity_crud_hook_test_entity_load called for type block',
  104. 'entity_crud_hook_test_block_load called',
  105. ]);
  106. $GLOBALS['entity_crud_hook_test'] = [];
  107. $entity->label = 'New label';
  108. $entity->save();
  109. $this->assertHookMessageOrder([
  110. 'entity_crud_hook_test_block_presave called',
  111. 'entity_crud_hook_test_entity_presave called for type block',
  112. 'entity_crud_hook_test_block_update called',
  113. 'entity_crud_hook_test_entity_update called for type block',
  114. ]);
  115. $GLOBALS['entity_crud_hook_test'] = [];
  116. $entity->delete();
  117. $this->assertHookMessageOrder([
  118. 'entity_crud_hook_test_block_predelete called',
  119. 'entity_crud_hook_test_entity_predelete called for type block',
  120. 'entity_crud_hook_test_block_delete called',
  121. 'entity_crud_hook_test_entity_delete called for type block',
  122. ]);
  123. }
  124. /**
  125. * Tests hook invocations for CRUD operations on comments.
  126. */
  127. public function testCommentHooks() {
  128. $account = $this->createUser();
  129. NodeType::create([
  130. 'type' => 'article',
  131. 'name' => 'Article',
  132. ])->save();
  133. $this->addDefaultCommentField('node', 'article', 'comment', CommentItemInterface::OPEN);
  134. $node = Node::create([
  135. 'uid' => $account->id(),
  136. 'type' => 'article',
  137. 'title' => 'Test node',
  138. 'status' => 1,
  139. 'promote' => 0,
  140. 'sticky' => 0,
  141. 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
  142. 'created' => REQUEST_TIME,
  143. 'changed' => REQUEST_TIME,
  144. ]);
  145. $node->save();
  146. $nid = $node->id();
  147. $GLOBALS['entity_crud_hook_test'] = [];
  148. $comment = Comment::create([
  149. 'cid' => NULL,
  150. 'pid' => 0,
  151. 'entity_id' => $nid,
  152. 'entity_type' => 'node',
  153. 'field_name' => 'comment',
  154. 'uid' => $account->id(),
  155. 'subject' => 'Test comment',
  156. 'created' => REQUEST_TIME,
  157. 'changed' => REQUEST_TIME,
  158. 'status' => 1,
  159. 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
  160. ]);
  161. $this->assertHookMessageOrder([
  162. 'entity_crud_hook_test_comment_create called',
  163. 'entity_crud_hook_test_entity_create called for type comment',
  164. ]);
  165. $GLOBALS['entity_crud_hook_test'] = [];
  166. $comment->save();
  167. $this->assertHookMessageOrder([
  168. 'entity_crud_hook_test_comment_presave called',
  169. 'entity_crud_hook_test_entity_presave called for type comment',
  170. 'entity_crud_hook_test_comment_insert called',
  171. 'entity_crud_hook_test_entity_insert called for type comment',
  172. ]);
  173. $GLOBALS['entity_crud_hook_test'] = [];
  174. $comment = Comment::load($comment->id());
  175. $this->assertHookMessageOrder([
  176. 'entity_crud_hook_test_entity_load called for type comment',
  177. 'entity_crud_hook_test_comment_load called',
  178. ]);
  179. $GLOBALS['entity_crud_hook_test'] = [];
  180. $comment->setSubject('New subject');
  181. $comment->save();
  182. $this->assertHookMessageOrder([
  183. 'entity_crud_hook_test_comment_presave called',
  184. 'entity_crud_hook_test_entity_presave called for type comment',
  185. 'entity_crud_hook_test_comment_update called',
  186. 'entity_crud_hook_test_entity_update called for type comment',
  187. ]);
  188. $GLOBALS['entity_crud_hook_test'] = [];
  189. $comment->delete();
  190. $this->assertHookMessageOrder([
  191. 'entity_crud_hook_test_comment_predelete called',
  192. 'entity_crud_hook_test_entity_predelete called for type comment',
  193. 'entity_crud_hook_test_comment_delete called',
  194. 'entity_crud_hook_test_entity_delete called for type comment',
  195. ]);
  196. }
  197. /**
  198. * Tests hook invocations for CRUD operations on files.
  199. */
  200. public function testFileHooks() {
  201. $this->installEntitySchema('file');
  202. $url = 'public://entity_crud_hook_test.file';
  203. file_put_contents($url, 'Test test test');
  204. $file = File::create([
  205. 'fid' => NULL,
  206. 'uid' => 1,
  207. 'filename' => 'entity_crud_hook_test.file',
  208. 'uri' => $url,
  209. 'filemime' => 'text/plain',
  210. 'filesize' => filesize($url),
  211. 'status' => 1,
  212. 'created' => REQUEST_TIME,
  213. 'changed' => REQUEST_TIME,
  214. ]);
  215. $this->assertHookMessageOrder([
  216. 'entity_crud_hook_test_file_create called',
  217. 'entity_crud_hook_test_entity_create called for type file',
  218. ]);
  219. $GLOBALS['entity_crud_hook_test'] = [];
  220. $file->save();
  221. $this->assertHookMessageOrder([
  222. 'entity_crud_hook_test_file_presave called',
  223. 'entity_crud_hook_test_entity_presave called for type file',
  224. 'entity_crud_hook_test_file_insert called',
  225. 'entity_crud_hook_test_entity_insert called for type file',
  226. ]);
  227. $GLOBALS['entity_crud_hook_test'] = [];
  228. $file = File::load($file->id());
  229. $this->assertHookMessageOrder([
  230. 'entity_crud_hook_test_entity_load called for type file',
  231. 'entity_crud_hook_test_file_load called',
  232. ]);
  233. $GLOBALS['entity_crud_hook_test'] = [];
  234. $file->setFilename('new.entity_crud_hook_test.file');
  235. $file->save();
  236. $this->assertHookMessageOrder([
  237. 'entity_crud_hook_test_file_presave called',
  238. 'entity_crud_hook_test_entity_presave called for type file',
  239. 'entity_crud_hook_test_file_update called',
  240. 'entity_crud_hook_test_entity_update called for type file',
  241. ]);
  242. $GLOBALS['entity_crud_hook_test'] = [];
  243. $file->delete();
  244. $this->assertHookMessageOrder([
  245. 'entity_crud_hook_test_file_predelete called',
  246. 'entity_crud_hook_test_entity_predelete called for type file',
  247. 'entity_crud_hook_test_file_delete called',
  248. 'entity_crud_hook_test_entity_delete called for type file',
  249. ]);
  250. }
  251. /**
  252. * Tests hook invocations for CRUD operations on nodes.
  253. */
  254. public function testNodeHooks() {
  255. $account = $this->createUser();
  256. $node = Node::create([
  257. 'uid' => $account->id(),
  258. 'type' => 'article',
  259. 'title' => 'Test node',
  260. 'status' => 1,
  261. 'promote' => 0,
  262. 'sticky' => 0,
  263. 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
  264. 'created' => REQUEST_TIME,
  265. 'changed' => REQUEST_TIME,
  266. ]);
  267. $this->assertHookMessageOrder([
  268. 'entity_crud_hook_test_node_create called',
  269. 'entity_crud_hook_test_entity_create called for type node',
  270. ]);
  271. $GLOBALS['entity_crud_hook_test'] = [];
  272. $node->save();
  273. $this->assertHookMessageOrder([
  274. 'entity_crud_hook_test_node_presave called',
  275. 'entity_crud_hook_test_entity_presave called for type node',
  276. 'entity_crud_hook_test_node_insert called',
  277. 'entity_crud_hook_test_entity_insert called for type node',
  278. ]);
  279. $GLOBALS['entity_crud_hook_test'] = [];
  280. $node = Node::load($node->id());
  281. $this->assertHookMessageOrder([
  282. 'entity_crud_hook_test_entity_preload called for type node',
  283. 'entity_crud_hook_test_entity_load called for type node',
  284. 'entity_crud_hook_test_node_load called',
  285. ]);
  286. $GLOBALS['entity_crud_hook_test'] = [];
  287. $node->title = 'New title';
  288. $node->save();
  289. $this->assertHookMessageOrder([
  290. 'entity_crud_hook_test_node_presave called',
  291. 'entity_crud_hook_test_entity_presave called for type node',
  292. 'entity_crud_hook_test_node_update called',
  293. 'entity_crud_hook_test_entity_update called for type node',
  294. ]);
  295. $GLOBALS['entity_crud_hook_test'] = [];
  296. $node->delete();
  297. $this->assertHookMessageOrder([
  298. 'entity_crud_hook_test_node_predelete called',
  299. 'entity_crud_hook_test_entity_predelete called for type node',
  300. 'entity_crud_hook_test_node_delete called',
  301. 'entity_crud_hook_test_entity_delete called for type node',
  302. ]);
  303. }
  304. /**
  305. * Tests hook invocations for CRUD operations on taxonomy terms.
  306. */
  307. public function testTaxonomyTermHooks() {
  308. $this->installEntitySchema('taxonomy_term');
  309. $vocabulary = Vocabulary::create([
  310. 'name' => 'Test vocabulary',
  311. 'vid' => 'test',
  312. 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
  313. 'description' => NULL,
  314. 'module' => 'entity_crud_hook_test',
  315. ]);
  316. $vocabulary->save();
  317. $GLOBALS['entity_crud_hook_test'] = [];
  318. $term = Term::create([
  319. 'vid' => $vocabulary->id(),
  320. 'name' => 'Test term',
  321. 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
  322. 'description' => NULL,
  323. 'format' => 1,
  324. ]);
  325. $this->assertHookMessageOrder([
  326. 'entity_crud_hook_test_taxonomy_term_create called',
  327. 'entity_crud_hook_test_entity_create called for type taxonomy_term',
  328. ]);
  329. $GLOBALS['entity_crud_hook_test'] = [];
  330. $term->save();
  331. $this->assertHookMessageOrder([
  332. 'entity_crud_hook_test_taxonomy_term_presave called',
  333. 'entity_crud_hook_test_entity_presave called for type taxonomy_term',
  334. 'entity_crud_hook_test_taxonomy_term_insert called',
  335. 'entity_crud_hook_test_entity_insert called for type taxonomy_term',
  336. ]);
  337. $GLOBALS['entity_crud_hook_test'] = [];
  338. $term = Term::load($term->id());
  339. $this->assertHookMessageOrder([
  340. 'entity_crud_hook_test_entity_load called for type taxonomy_term',
  341. 'entity_crud_hook_test_taxonomy_term_load called',
  342. ]);
  343. $GLOBALS['entity_crud_hook_test'] = [];
  344. $term->setName('New name');
  345. $term->save();
  346. $this->assertHookMessageOrder([
  347. 'entity_crud_hook_test_taxonomy_term_presave called',
  348. 'entity_crud_hook_test_entity_presave called for type taxonomy_term',
  349. 'entity_crud_hook_test_taxonomy_term_update called',
  350. 'entity_crud_hook_test_entity_update called for type taxonomy_term',
  351. ]);
  352. $GLOBALS['entity_crud_hook_test'] = [];
  353. $term->delete();
  354. $this->assertHookMessageOrder([
  355. 'entity_crud_hook_test_taxonomy_term_predelete called',
  356. 'entity_crud_hook_test_entity_predelete called for type taxonomy_term',
  357. 'entity_crud_hook_test_taxonomy_term_delete called',
  358. 'entity_crud_hook_test_entity_delete called for type taxonomy_term',
  359. ]);
  360. }
  361. /**
  362. * Tests hook invocations for CRUD operations on taxonomy vocabularies.
  363. */
  364. public function testTaxonomyVocabularyHooks() {
  365. $this->installEntitySchema('taxonomy_term');
  366. $vocabulary = Vocabulary::create([
  367. 'name' => 'Test vocabulary',
  368. 'vid' => 'test',
  369. 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
  370. 'description' => NULL,
  371. 'module' => 'entity_crud_hook_test',
  372. ]);
  373. $this->assertHookMessageOrder([
  374. 'entity_crud_hook_test_taxonomy_vocabulary_create called',
  375. 'entity_crud_hook_test_entity_create called for type taxonomy_vocabulary',
  376. ]);
  377. $GLOBALS['entity_crud_hook_test'] = [];
  378. $vocabulary->save();
  379. $this->assertHookMessageOrder([
  380. 'entity_crud_hook_test_taxonomy_vocabulary_presave called',
  381. 'entity_crud_hook_test_entity_presave called for type taxonomy_vocabulary',
  382. 'entity_crud_hook_test_taxonomy_vocabulary_insert called',
  383. 'entity_crud_hook_test_entity_insert called for type taxonomy_vocabulary',
  384. ]);
  385. $GLOBALS['entity_crud_hook_test'] = [];
  386. $vocabulary = Vocabulary::load($vocabulary->id());
  387. $this->assertHookMessageOrder([
  388. 'entity_crud_hook_test_entity_load called for type taxonomy_vocabulary',
  389. 'entity_crud_hook_test_taxonomy_vocabulary_load called',
  390. ]);
  391. $GLOBALS['entity_crud_hook_test'] = [];
  392. $vocabulary->set('name', 'New name');
  393. $vocabulary->save();
  394. $this->assertHookMessageOrder([
  395. 'entity_crud_hook_test_taxonomy_vocabulary_presave called',
  396. 'entity_crud_hook_test_entity_presave called for type taxonomy_vocabulary',
  397. 'entity_crud_hook_test_taxonomy_vocabulary_update called',
  398. 'entity_crud_hook_test_entity_update called for type taxonomy_vocabulary',
  399. ]);
  400. $GLOBALS['entity_crud_hook_test'] = [];
  401. $vocabulary->delete();
  402. $this->assertHookMessageOrder([
  403. 'entity_crud_hook_test_taxonomy_vocabulary_predelete called',
  404. 'entity_crud_hook_test_entity_predelete called for type taxonomy_vocabulary',
  405. 'entity_crud_hook_test_taxonomy_vocabulary_delete called',
  406. 'entity_crud_hook_test_entity_delete called for type taxonomy_vocabulary',
  407. ]);
  408. }
  409. /**
  410. * Tests hook invocations for CRUD operations on users.
  411. */
  412. public function testUserHooks() {
  413. $account = User::create([
  414. 'name' => 'Test user',
  415. 'mail' => 'test@example.com',
  416. 'created' => REQUEST_TIME,
  417. 'status' => 1,
  418. 'language' => 'en',
  419. ]);
  420. $this->assertHookMessageOrder([
  421. 'entity_crud_hook_test_user_create called',
  422. 'entity_crud_hook_test_entity_create called for type user',
  423. ]);
  424. $GLOBALS['entity_crud_hook_test'] = [];
  425. $account->save();
  426. $this->assertHookMessageOrder([
  427. 'entity_crud_hook_test_user_presave called',
  428. 'entity_crud_hook_test_entity_presave called for type user',
  429. 'entity_crud_hook_test_user_insert called',
  430. 'entity_crud_hook_test_entity_insert called for type user',
  431. ]);
  432. $GLOBALS['entity_crud_hook_test'] = [];
  433. User::load($account->id());
  434. $this->assertHookMessageOrder([
  435. 'entity_crud_hook_test_entity_load called for type user',
  436. 'entity_crud_hook_test_user_load called',
  437. ]);
  438. $GLOBALS['entity_crud_hook_test'] = [];
  439. $account->name = 'New name';
  440. $account->save();
  441. $this->assertHookMessageOrder([
  442. 'entity_crud_hook_test_user_presave called',
  443. 'entity_crud_hook_test_entity_presave called for type user',
  444. 'entity_crud_hook_test_user_update called',
  445. 'entity_crud_hook_test_entity_update called for type user',
  446. ]);
  447. $GLOBALS['entity_crud_hook_test'] = [];
  448. $account->delete();
  449. $this->assertHookMessageOrder([
  450. 'entity_crud_hook_test_user_predelete called',
  451. 'entity_crud_hook_test_entity_predelete called for type user',
  452. 'entity_crud_hook_test_user_delete called',
  453. 'entity_crud_hook_test_entity_delete called for type user',
  454. ]);
  455. }
  456. /**
  457. * Tests rollback from failed entity save.
  458. */
  459. public function testEntityRollback() {
  460. // Create a block.
  461. try {
  462. EntityTest::create(['name' => 'fail_insert'])->save();
  463. $this->fail('Expected exception has not been thrown.');
  464. }
  465. catch (\Exception $e) {
  466. // Expected exception; just continue testing.
  467. }
  468. if (Database::getConnection()->supportsTransactions()) {
  469. // Check that the block does not exist in the database.
  470. $ids = \Drupal::entityQuery('entity_test')->condition('name', 'fail_insert')->execute();
  471. $this->assertTrue(empty($ids), 'Transactions supported, and entity not found in database.');
  472. }
  473. else {
  474. // Check that the block exists in the database.
  475. $ids = \Drupal::entityQuery('entity_test')->condition('name', 'fail_insert')->execute();
  476. $this->assertFalse(empty($ids), 'Transactions not supported, and entity found in database.');
  477. }
  478. }
  479. }