EntityCrudHookTest.php 18 KB

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