EntityCrudHookTest.php 17.7 KB
Newer Older
1 2
<?php

3
namespace Drupal\KernelTests\Core\Entity;
4

5
use Drupal\comment\Entity\Comment;
6
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
7
use Drupal\comment\Tests\CommentTestTrait;
8
use Drupal\Core\Database\Database;
9
use Drupal\Core\Language\LanguageInterface;
10
use Drupal\block\Entity\Block;
11
use Drupal\entity_test\Entity\EntityTest;
12
use Drupal\node\Entity\NodeType;
13
use Drupal\taxonomy\Entity\Term;
14
use Drupal\node\Entity\Node;
15
use Drupal\taxonomy\Entity\Vocabulary;
16
use Drupal\user\Entity\User;
17
use Drupal\file\Entity\File;
18

19
/**
20 21
 * Tests the invocation of hooks when creating, inserting, loading, updating or
 * deleting an entity.
22 23
 *
 * Tested hooks are:
24 25 26 27 28 29 30
 * - hook_entity_insert() and hook_ENTITY_TYPE_insert()
 * - hook_entity_load() and hook_ENTITY_TYPE_load()
 * - hook_entity_update() and hook_ENTITY_TYPE_update()
 * - hook_entity_predelete() and hook_ENTITY_TYPE_predelete()
 * - hook_entity_delete() and hook_ENTITY_TYPE_delete()
 *
 * These hooks are each tested for several entity types.
31 32
 *
 * @group Entity
33
 */
34
class EntityCrudHookTest extends EntityKernelTestBase {
35

36 37
  use CommentTestTrait;

38 39 40 41 42
  /**
   * Modules to enable.
   *
   * @var array
   */
43
  public static $modules = ['block', 'block_test', 'entity_crud_hook_test', 'file', 'taxonomy', 'node', 'comment'];
44

45
  protected $ids = [];
46

47
  protected function setUp() {
48
    parent::setUp();
49

50 51 52 53
    $this->installSchema('user', ['users_data']);
    $this->installSchema('file', ['file_usage']);
    $this->installSchema('node', ['node_access']);
    $this->installSchema('comment', ['comment_entity_statistics']);
54
    $this->installConfig(['node', 'comment']);
55 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 64
   *
   * @param $messages
   *   An array of plain-text messages in the order they should appear.
65
   */
66
  protected function assertHookMessageOrder($messages) {
67
    $positions = [];
68 69
    foreach ($messages as $message) {
      // Verify that each message is found and record its position.
70
      $position = array_search($message, $GLOBALS['entity_crud_hook_test']);
71 72 73
      if ($this->assertTrue($position !== FALSE, $message)) {
        $positions[] = $position;
      }
74
    }
75 76 77 78 79

    // Sort the positions and ensure they remain in the same order.
    $sorted = $positions;
    sort($sorted);
    $this->assertTrue($sorted == $positions, 'The hook messages appear in the correct order.');
80 81
  }

82 83 84 85
  /**
   * Tests hook invocations for CRUD operations on blocks.
   */
  public function testBlockHooks() {
86
    $entity = Block::create([
87
      'id' => 'stark_test_html',
88
      'plugin' => 'test_html',
89
      'theme' => 'stark',
90
    ]);
91

92
    $this->assertHookMessageOrder([
93 94
      'entity_crud_hook_test_block_create called',
      'entity_crud_hook_test_entity_create called for type block',
95
    ]);
96

97
    $GLOBALS['entity_crud_hook_test'] = [];
98 99
    $entity->save();

100
    $this->assertHookMessageOrder([
101 102 103 104
      'entity_crud_hook_test_block_presave called',
      'entity_crud_hook_test_entity_presave called for type block',
      'entity_crud_hook_test_block_insert called',
      'entity_crud_hook_test_entity_insert called for type block',
105
    ]);
106

107
    $GLOBALS['entity_crud_hook_test'] = [];
108
    $entity = Block::load($entity->id());
109

110
    $this->assertHookMessageOrder([
111 112
      'entity_crud_hook_test_entity_load called for type block',
      'entity_crud_hook_test_block_load called',
113
    ]);
114

115
    $GLOBALS['entity_crud_hook_test'] = [];
116 117 118
    $entity->label = 'New label';
    $entity->save();

119
    $this->assertHookMessageOrder([
120 121 122 123
      'entity_crud_hook_test_block_presave called',
      'entity_crud_hook_test_entity_presave called for type block',
      'entity_crud_hook_test_block_update called',
      'entity_crud_hook_test_entity_update called for type block',
124
    ]);
125

126
    $GLOBALS['entity_crud_hook_test'] = [];
127 128
    $entity->delete();

129
    $this->assertHookMessageOrder([
130 131 132 133
      'entity_crud_hook_test_block_predelete called',
      'entity_crud_hook_test_entity_predelete called for type block',
      'entity_crud_hook_test_block_delete called',
      'entity_crud_hook_test_entity_delete called for type block',
134
    ]);
135 136
  }

137
  /**
138
   * Tests hook invocations for CRUD operations on comments.
139 140
   */
  public function testCommentHooks() {
141
    $account = $this->createUser();
142
    NodeType::create([
143 144
      'type' => 'article',
      'name' => 'Article',
145
    ])->save();
146
    $this->addDefaultCommentField('node', 'article', 'comment', CommentItemInterface::OPEN);
147

148
    $node = Node::create([
149
      'uid' => $account->id(),
150 151 152 153 154
      'type' => 'article',
      'title' => 'Test node',
      'status' => 1,
      'promote' => 0,
      'sticky' => 0,
155
      'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
156 157
      'created' => REQUEST_TIME,
      'changed' => REQUEST_TIME,
158
    ]);
159
    $node->save();
160
    $nid = $node->id();
161
    $GLOBALS['entity_crud_hook_test'] = [];
162

163
    $comment = Comment::create([
164 165
      'cid' => NULL,
      'pid' => 0,
166 167 168
      'entity_id' => $nid,
      'entity_type' => 'node',
      'field_name' => 'comment',
169
      'uid' => $account->id(),
170 171 172 173
      'subject' => 'Test comment',
      'created' => REQUEST_TIME,
      'changed' => REQUEST_TIME,
      'status' => 1,
174
      'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
175
    ]);
176

177
    $this->assertHookMessageOrder([
178 179
      'entity_crud_hook_test_comment_create called',
      'entity_crud_hook_test_entity_create called for type comment',
180
    ]);
181

182
    $GLOBALS['entity_crud_hook_test'] = [];
183
    $comment->save();
184

185
    $this->assertHookMessageOrder([
186 187 188 189
      'entity_crud_hook_test_comment_presave called',
      'entity_crud_hook_test_entity_presave called for type comment',
      'entity_crud_hook_test_comment_insert called',
      'entity_crud_hook_test_entity_insert called for type comment',
190
    ]);
191

192
    $GLOBALS['entity_crud_hook_test'] = [];
193
    $comment = Comment::load($comment->id());
194

195
    $this->assertHookMessageOrder([
196 197
      'entity_crud_hook_test_entity_load called for type comment',
      'entity_crud_hook_test_comment_load called',
198
    ]);
199

200
    $GLOBALS['entity_crud_hook_test'] = [];
201
    $comment->setSubject('New subject');
202
    $comment->save();
203

204
    $this->assertHookMessageOrder([
205 206 207 208
      'entity_crud_hook_test_comment_presave called',
      'entity_crud_hook_test_entity_presave called for type comment',
      'entity_crud_hook_test_comment_update called',
      'entity_crud_hook_test_entity_update called for type comment',
209
    ]);
210

211
    $GLOBALS['entity_crud_hook_test'] = [];
212
    $comment->delete();
213

214
    $this->assertHookMessageOrder([
215 216 217 218
      'entity_crud_hook_test_comment_predelete called',
      'entity_crud_hook_test_entity_predelete called for type comment',
      'entity_crud_hook_test_comment_delete called',
      'entity_crud_hook_test_entity_delete called for type comment',
219
    ]);
220 221 222
  }

  /**
223
   * Tests hook invocations for CRUD operations on files.
224 225
   */
  public function testFileHooks() {
226 227
    $this->installEntitySchema('file');

228 229
    $url = 'public://entity_crud_hook_test.file';
    file_put_contents($url, 'Test test test');
230
    $file = File::create([
231 232 233 234 235 236 237
      'fid' => NULL,
      'uid' => 1,
      'filename' => 'entity_crud_hook_test.file',
      'uri' => $url,
      'filemime' => 'text/plain',
      'filesize' => filesize($url),
      'status' => 1,
238 239
      'created' => REQUEST_TIME,
      'changed' => REQUEST_TIME,
240
    ]);
241

242
    $this->assertHookMessageOrder([
243 244
      'entity_crud_hook_test_file_create called',
      'entity_crud_hook_test_entity_create called for type file',
245
    ]);
246

247
    $GLOBALS['entity_crud_hook_test'] = [];
248
    $file->save();
249

250
    $this->assertHookMessageOrder([
251 252 253 254
      'entity_crud_hook_test_file_presave called',
      'entity_crud_hook_test_entity_presave called for type file',
      'entity_crud_hook_test_file_insert called',
      'entity_crud_hook_test_entity_insert called for type file',
255
    ]);
256

257
    $GLOBALS['entity_crud_hook_test'] = [];
258
    $file = File::load($file->id());
259

260
    $this->assertHookMessageOrder([
261 262
      'entity_crud_hook_test_entity_load called for type file',
      'entity_crud_hook_test_file_load called',
263
    ]);
264

265
    $GLOBALS['entity_crud_hook_test'] = [];
266
    $file->setFilename('new.entity_crud_hook_test.file');
267
    $file->save();
268

269
    $this->assertHookMessageOrder([
270 271 272 273
      'entity_crud_hook_test_file_presave called',
      'entity_crud_hook_test_entity_presave called for type file',
      'entity_crud_hook_test_file_update called',
      'entity_crud_hook_test_entity_update called for type file',
274
    ]);
275

276
    $GLOBALS['entity_crud_hook_test'] = [];
277
    $file->delete();
278

279
    $this->assertHookMessageOrder([
280 281 282 283
      'entity_crud_hook_test_file_predelete called',
      'entity_crud_hook_test_entity_predelete called for type file',
      'entity_crud_hook_test_file_delete called',
      'entity_crud_hook_test_entity_delete called for type file',
284
    ]);
285 286 287
  }

  /**
288
   * Tests hook invocations for CRUD operations on nodes.
289 290
   */
  public function testNodeHooks() {
291 292
    $account = $this->createUser();

293
    $node = Node::create([
294
      'uid' => $account->id(),
295 296 297 298 299
      'type' => 'article',
      'title' => 'Test node',
      'status' => 1,
      'promote' => 0,
      'sticky' => 0,
300
      'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
301 302
      'created' => REQUEST_TIME,
      'changed' => REQUEST_TIME,
303
    ]);
304

305
    $this->assertHookMessageOrder([
306 307
      'entity_crud_hook_test_node_create called',
      'entity_crud_hook_test_entity_create called for type node',
308
    ]);
309

310
    $GLOBALS['entity_crud_hook_test'] = [];
311
    $node->save();
312

313
    $this->assertHookMessageOrder([
314 315 316 317
      'entity_crud_hook_test_node_presave called',
      'entity_crud_hook_test_entity_presave called for type node',
      'entity_crud_hook_test_node_insert called',
      'entity_crud_hook_test_entity_insert called for type node',
318
    ]);
319

320
    $GLOBALS['entity_crud_hook_test'] = [];
321
    $node = Node::load($node->id());
322

323
    $this->assertHookMessageOrder([
324 325
      'entity_crud_hook_test_entity_load called for type node',
      'entity_crud_hook_test_node_load called',
326
    ]);
327

328
    $GLOBALS['entity_crud_hook_test'] = [];
329
    $node->title = 'New title';
330
    $node->save();
331

332
    $this->assertHookMessageOrder([
333 334 335 336
      'entity_crud_hook_test_node_presave called',
      'entity_crud_hook_test_entity_presave called for type node',
      'entity_crud_hook_test_node_update called',
      'entity_crud_hook_test_entity_update called for type node',
337
    ]);
338

339
    $GLOBALS['entity_crud_hook_test'] = [];
340
    $node->delete();
341

342
    $this->assertHookMessageOrder([
343 344 345 346
      'entity_crud_hook_test_node_predelete called',
      'entity_crud_hook_test_entity_predelete called for type node',
      'entity_crud_hook_test_node_delete called',
      'entity_crud_hook_test_entity_delete called for type node',
347
    ]);
348 349 350
  }

  /**
351
   * Tests hook invocations for CRUD operations on taxonomy terms.
352 353
   */
  public function testTaxonomyTermHooks() {
354
    $this->installEntitySchema('taxonomy_term');
355

356
    $vocabulary = Vocabulary::create([
357
      'name' => 'Test vocabulary',
358
      'vid' => 'test',
359
      'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
360 361
      'description' => NULL,
      'module' => 'entity_crud_hook_test',
362
    ]);
363
    $vocabulary->save();
364
    $GLOBALS['entity_crud_hook_test'] = [];
365

366
    $term = Term::create([
367
      'vid' => $vocabulary->id(),
368
      'name' => 'Test term',
369
      'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
370 371
      'description' => NULL,
      'format' => 1,
372
    ]);
373

374
    $this->assertHookMessageOrder([
375 376
      'entity_crud_hook_test_taxonomy_term_create called',
      'entity_crud_hook_test_entity_create called for type taxonomy_term',
377
    ]);
378

379
    $GLOBALS['entity_crud_hook_test'] = [];
380
    $term->save();
381

382
    $this->assertHookMessageOrder([
383 384 385 386
      'entity_crud_hook_test_taxonomy_term_presave called',
      'entity_crud_hook_test_entity_presave called for type taxonomy_term',
      'entity_crud_hook_test_taxonomy_term_insert called',
      'entity_crud_hook_test_entity_insert called for type taxonomy_term',
387
    ]);
388

389
    $GLOBALS['entity_crud_hook_test'] = [];
390
    $term = Term::load($term->id());
391

392
    $this->assertHookMessageOrder([
393 394
      'entity_crud_hook_test_entity_load called for type taxonomy_term',
      'entity_crud_hook_test_taxonomy_term_load called',
395
    ]);
396

397
    $GLOBALS['entity_crud_hook_test'] = [];
398
    $term->setName('New name');
399
    $term->save();
400

401
    $this->assertHookMessageOrder([
402 403 404 405
      'entity_crud_hook_test_taxonomy_term_presave called',
      'entity_crud_hook_test_entity_presave called for type taxonomy_term',
      'entity_crud_hook_test_taxonomy_term_update called',
      'entity_crud_hook_test_entity_update called for type taxonomy_term',
406
    ]);
407

408
    $GLOBALS['entity_crud_hook_test'] = [];
409
    $term->delete();
410

411
    $this->assertHookMessageOrder([
412 413 414 415
      'entity_crud_hook_test_taxonomy_term_predelete called',
      'entity_crud_hook_test_entity_predelete called for type taxonomy_term',
      'entity_crud_hook_test_taxonomy_term_delete called',
      'entity_crud_hook_test_entity_delete called for type taxonomy_term',
416
    ]);
417 418 419
  }

  /**
420
   * Tests hook invocations for CRUD operations on taxonomy vocabularies.
421 422
   */
  public function testTaxonomyVocabularyHooks() {
423
    $this->installEntitySchema('taxonomy_term');
424

425
    $vocabulary = Vocabulary::create([
426
      'name' => 'Test vocabulary',
427
      'vid' => 'test',
428
      'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
429 430
      'description' => NULL,
      'module' => 'entity_crud_hook_test',
431
    ]);
432

433
    $this->assertHookMessageOrder([
434 435
      'entity_crud_hook_test_taxonomy_vocabulary_create called',
      'entity_crud_hook_test_entity_create called for type taxonomy_vocabulary',
436
    ]);
437

438
    $GLOBALS['entity_crud_hook_test'] = [];
439
    $vocabulary->save();
440

441
    $this->assertHookMessageOrder([
442 443 444 445
      'entity_crud_hook_test_taxonomy_vocabulary_presave called',
      'entity_crud_hook_test_entity_presave called for type taxonomy_vocabulary',
      'entity_crud_hook_test_taxonomy_vocabulary_insert called',
      'entity_crud_hook_test_entity_insert called for type taxonomy_vocabulary',
446
    ]);
447

448
    $GLOBALS['entity_crud_hook_test'] = [];
449
    $vocabulary = Vocabulary::load($vocabulary->id());
450

451
    $this->assertHookMessageOrder([
452 453
      'entity_crud_hook_test_entity_load called for type taxonomy_vocabulary',
      'entity_crud_hook_test_taxonomy_vocabulary_load called',
454
    ]);
455

456
    $GLOBALS['entity_crud_hook_test'] = [];
457
    $vocabulary->set('name', 'New name');
458
    $vocabulary->save();
459

460
    $this->assertHookMessageOrder([
461 462 463 464
      'entity_crud_hook_test_taxonomy_vocabulary_presave called',
      'entity_crud_hook_test_entity_presave called for type taxonomy_vocabulary',
      'entity_crud_hook_test_taxonomy_vocabulary_update called',
      'entity_crud_hook_test_entity_update called for type taxonomy_vocabulary',
465
    ]);
466

467
    $GLOBALS['entity_crud_hook_test'] = [];
468
    $vocabulary->delete();
469

470
    $this->assertHookMessageOrder([
471 472 473 474
      'entity_crud_hook_test_taxonomy_vocabulary_predelete called',
      'entity_crud_hook_test_entity_predelete called for type taxonomy_vocabulary',
      'entity_crud_hook_test_taxonomy_vocabulary_delete called',
      'entity_crud_hook_test_entity_delete called for type taxonomy_vocabulary',
475
    ]);
476 477 478
  }

  /**
479
   * Tests hook invocations for CRUD operations on users.
480 481
   */
  public function testUserHooks() {
482
    $account = User::create([
483 484 485 486 487
      'name' => 'Test user',
      'mail' => 'test@example.com',
      'created' => REQUEST_TIME,
      'status' => 1,
      'language' => 'en',
488
    ]);
489

490
    $this->assertHookMessageOrder([
491 492
      'entity_crud_hook_test_user_create called',
      'entity_crud_hook_test_entity_create called for type user',
493
    ]);
494

495
    $GLOBALS['entity_crud_hook_test'] = [];
496
    $account->save();
497

498
    $this->assertHookMessageOrder([
499 500 501 502
      'entity_crud_hook_test_user_presave called',
      'entity_crud_hook_test_entity_presave called for type user',
      'entity_crud_hook_test_user_insert called',
      'entity_crud_hook_test_entity_insert called for type user',
503
    ]);
504

505
    $GLOBALS['entity_crud_hook_test'] = [];
506
    User::load($account->id());
507

508
    $this->assertHookMessageOrder([
509 510
      'entity_crud_hook_test_entity_load called for type user',
      'entity_crud_hook_test_user_load called',
511
    ]);
512

513
    $GLOBALS['entity_crud_hook_test'] = [];
514 515
    $account->name = 'New name';
    $account->save();
516

517
    $this->assertHookMessageOrder([
518 519 520 521
      'entity_crud_hook_test_user_presave called',
      'entity_crud_hook_test_entity_presave called for type user',
      'entity_crud_hook_test_user_update called',
      'entity_crud_hook_test_entity_update called for type user',
522
    ]);
523

524
    $GLOBALS['entity_crud_hook_test'] = [];
525
    user_delete($account->id());
526

527
    $this->assertHookMessageOrder([
528 529 530 531
      'entity_crud_hook_test_user_predelete called',
      'entity_crud_hook_test_entity_predelete called for type user',
      'entity_crud_hook_test_user_delete called',
      'entity_crud_hook_test_entity_delete called for type user',
532
    ]);
533
  }
534 535

  /**
536
   * Tests rollback from failed entity save.
537
   */
538
  public function testEntityRollback() {
539 540
    // Create a block.
    try {
541
      EntityTest::create(['name' => 'fail_insert'])->save();
542 543 544 545 546 547 548 549
      $this->fail('Expected exception has not been thrown.');
    }
    catch (\Exception $e) {
      $this->pass('Expected exception has been thrown.');
    }

    if (Database::getConnection()->supportsTransactions()) {
      // Check that the block does not exist in the database.
550
      $ids = \Drupal::entityQuery('entity_test')->condition('name', 'fail_insert')->execute();
551 552 553 554
      $this->assertTrue(empty($ids), 'Transactions supported, and entity not found in database.');
    }
    else {
      // Check that the block exists in the database.
555
      $ids = \Drupal::entityQuery('entity_test')->condition('name', 'fail_insert')->execute();
556 557 558
      $this->assertFalse(empty($ids), 'Transactions not supported, and entity found in database.');
    }
  }
559

560
}