hasMany 一对多关联

Testing Is Documentation

tests/Database/Ddd/Relation/HasManyTest.phphasMany 一对多关联 - 图1

一对多的关联是一种常用的关联,比如一篇文章与文章评论属于一对多的关系。

一对多关联支持类型关联项

关联项说明例子
\Leevel\Database\Ddd\Entity::HAS_MANY一对多关联实体\Tests\Database\Ddd\Entity\Relation\Comment::class
\Leevel\Database\Ddd\Entity::SOURCE_KEY关联查询源键字段id
\Leevel\Database\Ddd\Entity::TARGET_KEY关联目标键字段post_id
\Leevel\Database\Ddd\Entity::RELATION_SCOPE关联查询作用域comment

Uses

  1. <?php
  2. use Leevel\Collection\Collection;
  3. use Leevel\Database\Ddd\Relation\HasMany;
  4. use Leevel\Database\Ddd\Select;
  5. use Tests\Database\DatabaseTestCase as TestCase;
  6. use Tests\Database\Ddd\Entity\Relation\Comment;
  7. use Tests\Database\Ddd\Entity\Relation\Post;

基本使用方法

fixture 定义

Tests\Database\Ddd\Entity\Relation\Post

  1. namespace Tests\Database\Ddd\Entity\Relation;
  2. use Leevel\Database\Ddd\Entity;
  3. use Leevel\Database\Ddd\GetterSetter;
  4. use Leevel\Database\Ddd\Relation\Relation;
  5. class Post extends Entity
  6. {
  7. use GetterSetter;
  8. const TABLE = 'post';
  9. const ID = 'id';
  10. const AUTO = 'id';
  11. const STRUCT = [
  12. 'id' => [
  13. self::READONLY => true,
  14. ],
  15. 'title' => [],
  16. 'user_id' => [],
  17. 'summary' => [],
  18. 'create_at' => [],
  19. 'delete_at' => [
  20. self::CREATE_FILL => 0,
  21. ],
  22. 'user' => [
  23. self::BELONGS_TO => User::class,
  24. self::SOURCE_KEY => 'user_id',
  25. self::TARGET_KEY => 'id',
  26. ],
  27. 'comment' => [
  28. self::HAS_MANY => Comment::class,
  29. self::SOURCE_KEY => 'id',
  30. self::TARGET_KEY => 'post_id',
  31. self::RELATION_SCOPE => 'comment',
  32. ],
  33. 'post_content' => [
  34. self::HAS_ONE => PostContent::class,
  35. self::SOURCE_KEY => 'id',
  36. self::TARGET_KEY => 'post_id',
  37. ],
  38. 'user_not_defined_source_key' => [
  39. self::BELONGS_TO => User::class,
  40. self::TARGET_KEY => 'id',
  41. ],
  42. 'user_not_defined_target_key' => [
  43. self::BELONGS_TO => User::class,
  44. self::SOURCE_KEY => 'id',
  45. ],
  46. 'comment_not_defined_source_key' => [
  47. self::HAS_MANY => Comment::class,
  48. self::TARGET_KEY => 'post_id',
  49. self::RELATION_SCOPE => 'comment',
  50. ],
  51. 'comment_not_defined_target_key' => [
  52. self::HAS_MANY => Comment::class,
  53. self::SOURCE_KEY => 'id',
  54. self::RELATION_SCOPE => 'comment',
  55. ],
  56. 'post_content_not_defined_source_key' => [
  57. self::HAS_ONE => PostContent::class,
  58. self::TARGET_KEY => 'post_id',
  59. ],
  60. 'post_content_not_defined_target_key' => [
  61. self::HAS_ONE => PostContent::class,
  62. self::SOURCE_KEY => 'id',
  63. ],
  64. ];
  65. const DELETE_AT = 'delete_at';
  66. protected function relationScopeComment(Relation $relation): void
  67. {
  68. $relation->where('id', '>', 4);
  69. }
  70. }

Tests\Database\Ddd\Entity\Relation\Comment

  1. namespace Tests\Database\Ddd\Entity\Relation;
  2. use Leevel\Database\Ddd\Entity;
  3. use Leevel\Database\Ddd\GetterSetter;
  4. class Comment extends Entity
  5. {
  6. use GetterSetter;
  7. const TABLE = 'comment';
  8. const ID = 'id';
  9. const AUTO = 'id';
  10. const STRUCT = [
  11. 'id' => [],
  12. 'title' => [],
  13. 'post_id' => [],
  14. 'content' => [],
  15. 'create_at' => [],
  16. ];
  17. }
  1. public function testBaseUse(): void
  2. {
  3. $post = Post::select()->where('id', 1)->findOne();
  4. $this->assertInstanceof(Post::class, $post);
  5. $this->assertNull($post->id);
  6. $connect = $this->createDatabaseConnect();
  7. $this->assertSame(
  8. 1,
  9. $connect
  10. ->table('post')
  11. ->insert([
  12. 'title' => 'hello world',
  13. 'user_id' => 1,
  14. 'summary' => 'Say hello to the world.',
  15. 'delete_at' => 0,
  16. ]),
  17. );
  18. for ($i = 0; $i < 10; $i++) {
  19. $connect
  20. ->table('comment')
  21. ->insert([
  22. 'title' => 'niu'.($i + 1),
  23. 'post_id' => 1,
  24. 'content' => 'Comment data.'.($i + 1),
  25. ]);
  26. }
  27. $post = Post::select()->where('id', 1)->findOne();
  28. $this->assertSame(1, $post->id);
  29. $this->assertSame(1, $post['id']);
  30. $this->assertSame(1, $post->getId());
  31. $this->assertSame(1, $post->user_id);
  32. $this->assertSame(1, $post->userId);
  33. $this->assertSame(1, $post['user_id']);
  34. $this->assertSame(1, $post->getUserId());
  35. $this->assertSame('hello world', $post->title);
  36. $this->assertSame('hello world', $post['title']);
  37. $this->assertSame('hello world', $post->getTitle());
  38. $this->assertSame('Say hello to the world.', $post->summary);
  39. $this->assertSame('Say hello to the world.', $post['summary']);
  40. $this->assertSame('Say hello to the world.', $post->getSummary());
  41. $comment = $post->comment;
  42. $this->assertInstanceof(Collection::class, $comment);
  43. $n = 0;
  44. foreach ($comment as $k => $v) {
  45. $id = (int) ($n + 5);
  46. $this->assertInstanceOf(Comment::class, $v);
  47. $this->assertSame($n, $k);
  48. $this->assertSame($id, (int) $v->id);
  49. $this->assertSame($id, (int) $v['id']);
  50. $this->assertSame($id, (int) $v->getId());
  51. $this->assertSame('niu'.$id, $v['title']);
  52. $this->assertSame('niu'.$id, $v->title);
  53. $this->assertSame('niu'.$id, $v->getTitle());
  54. $this->assertSame('Comment data.'.$id, $v['content']);
  55. $this->assertSame('Comment data.'.$id, $v->content);
  56. $this->assertSame('Comment data.'.$id, $v->getContent());
  57. $n++;
  58. }
  59. $this->assertCount(6, $comment);
  60. }

eager 预加载关联

  1. public function testEager(): void
  2. {
  3. $post = Post::select()->where('id', 1)->findOne();
  4. $this->assertInstanceof(Post::class, $post);
  5. $this->assertNull($post->id);
  6. $connect = $this->createDatabaseConnect();
  7. $this->assertSame(
  8. 1,
  9. $connect
  10. ->table('post')
  11. ->insert([
  12. 'title' => 'hello world',
  13. 'user_id' => 1,
  14. 'summary' => 'Say hello to the world.',
  15. 'delete_at' => 0,
  16. ]),
  17. );
  18. $this->assertSame(
  19. 2,
  20. $connect
  21. ->table('post')
  22. ->insert([
  23. 'title' => 'foo bar',
  24. 'user_id' => 1,
  25. 'summary' => 'Say foo to the bar.',
  26. 'delete_at' => 0,
  27. ]),
  28. );
  29. for ($i = 0; $i < 10; $i++) {
  30. $connect
  31. ->table('comment')
  32. ->insert([
  33. 'title' => 'niu'.($i + 1),
  34. 'post_id' => 1,
  35. 'content' => 'Comment data.'.($i + 1),
  36. ]);
  37. }
  38. for ($i = 0; $i < 10; $i++) {
  39. $connect
  40. ->table('comment')
  41. ->insert([
  42. 'title' => 'niu'.($i + 1),
  43. 'post_id' => 2,
  44. 'content' => 'Comment data.'.($i + 1),
  45. ]);
  46. }
  47. $posts = Post::eager(['comment'])->findAll();
  48. $this->assertInstanceof(Collection::class, $posts);
  49. $this->assertCount(2, $posts);
  50. $min = 5;
  51. foreach ($posts as $k => $value) {
  52. $comments = $value->comment;
  53. $this->assertInstanceof(Collection::class, $comments);
  54. $this->assertSame(0 === $k ? 6 : 10, count($comments));
  55. foreach ($comments as $comment) {
  56. $this->assertInstanceof(Comment::class, $comment);
  57. $this->assertSame($min, $comment->id);
  58. $min++;
  59. }
  60. }
  61. }

eager 预加载关联支持查询条件过滤

  1. public function testEagerWithCondition(): void
  2. {
  3. $post = Post::select()->where('id', 1)->findOne();
  4. $this->assertInstanceof(Post::class, $post);
  5. $this->assertNull($post->id);
  6. $connect = $this->createDatabaseConnect();
  7. $this->assertSame(
  8. 1,
  9. $connect
  10. ->table('post')
  11. ->insert([
  12. 'title' => 'hello world',
  13. 'user_id' => 1,
  14. 'summary' => 'Say hello to the world.',
  15. 'delete_at' => 0,
  16. ]),
  17. );
  18. $this->assertSame(
  19. 2,
  20. $connect
  21. ->table('post')
  22. ->insert([
  23. 'title' => 'foo bar',
  24. 'user_id' => 1,
  25. 'summary' => 'Say foo to the bar.',
  26. 'delete_at' => 0,
  27. ]),
  28. );
  29. for ($i = 0; $i < 10; $i++) {
  30. $connect
  31. ->table('comment')
  32. ->insert([
  33. 'title' => 'niu'.($i + 1),
  34. 'post_id' => 1,
  35. 'content' => 'Comment data.'.($i + 1),
  36. ]);
  37. }
  38. for ($i = 0; $i < 10; $i++) {
  39. $connect
  40. ->table('comment')
  41. ->insert([
  42. 'title' => 'niu'.($i + 1),
  43. 'post_id' => 2,
  44. 'content' => 'Comment data.'.($i + 1),
  45. ]);
  46. }
  47. $posts = Post::eager(['comment' => function ($select) {
  48. $select->where('id', '>', 99999);
  49. }])->findAll();
  50. $this->assertInstanceof(Collection::class, $posts);
  51. $this->assertCount(2, $posts);
  52. foreach ($posts as $k => $value) {
  53. $comments = $value->comment;
  54. $this->assertInstanceof(Collection::class, $comments);
  55. $this->assertCount(0, $comments);
  56. }
  57. }

relation 读取关联

  1. public function testRelationAsMethod(): void
  2. {
  3. $connect = $this->createDatabaseConnect();
  4. $this->assertSame(
  5. 1,
  6. $connect
  7. ->table('post')
  8. ->insert([
  9. 'title' => 'hello world',
  10. 'user_id' => 1,
  11. 'summary' => 'Say hello to the world.',
  12. 'delete_at' => 0,
  13. ]),
  14. );
  15. for ($i = 0; $i < 10; $i++) {
  16. $connect
  17. ->table('comment')
  18. ->insert([
  19. 'title' => 'niu'.($i + 1),
  20. 'post_id' => 1,
  21. 'content' => 'Comment data.'.($i + 1),
  22. ]);
  23. }
  24. $commentRelation = Post::make()->relation('comment');
  25. $this->assertInstanceof(HasMany::class, $commentRelation);
  26. $this->assertSame('id', $commentRelation->getSourceKey());
  27. $this->assertSame('post_id', $commentRelation->getTargetKey());
  28. $this->assertInstanceof(Post::class, $commentRelation->getSourceEntity());
  29. $this->assertInstanceof(Comment::class, $commentRelation->getTargetEntity());
  30. $this->assertInstanceof(Select::class, $commentRelation->getSelect());
  31. }

relation 关联模型数据不存在返回空集合

  1. public function testRelationDataWasNotFound(): void
  2. {
  3. $post = Post::select()->where('id', 1)->findOne();
  4. $this->assertInstanceof(Post::class, $post);
  5. $this->assertNull($post->id);
  6. $connect = $this->createDatabaseConnect();
  7. $this->assertSame(
  8. 1,
  9. $connect
  10. ->table('post')
  11. ->insert([
  12. 'title' => 'hello world',
  13. 'user_id' => 1,
  14. 'summary' => 'Say hello to the world.',
  15. 'delete_at' => 0,
  16. ]),
  17. );
  18. for ($i = 0; $i < 10; $i++) {
  19. $connect
  20. ->table('comment')
  21. ->insert([
  22. 'title' => 'niu'.($i + 1),
  23. 'post_id' => 2,
  24. 'content' => 'Comment data.'.($i + 1),
  25. ]);
  26. }
  27. $post = Post::select()->where('id', 1)->findOne();
  28. $this->assertSame(1, $post->id);
  29. $this->assertSame(1, $post['id']);
  30. $this->assertSame(1, $post->getId());
  31. $this->assertSame(1, $post->user_id);
  32. $this->assertSame(1, $post->userId);
  33. $this->assertSame(1, $post['user_id']);
  34. $this->assertSame(1, $post->getUserId());
  35. $this->assertSame('hello world', $post->title);
  36. $this->assertSame('hello world', $post['title']);
  37. $this->assertSame('hello world', $post->getTitle());
  38. $this->assertSame('Say hello to the world.', $post->summary);
  39. $this->assertSame('Say hello to the world.', $post['summary']);
  40. $this->assertSame('Say hello to the world.', $post->getSummary());
  41. $comment = $post->comment;
  42. $this->assertInstanceof(Collection::class, $comment);
  43. $this->assertCount(0, $comment);
  44. }