Skip to content

hasMany 一对多关联

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

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

关联项说明例子
\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

php
<?php

use Leevel\Database\Ddd\EntityCollection as Collection;
use Leevel\Database\Ddd\Relation\HasMany;
use Leevel\Database\Ddd\Select;
use Leevel\Kernel\Utils\Api;
use Tests\Database\DatabaseTestCase as TestCase;
use Tests\Database\Ddd\Entity\Relation\Comment;
use Tests\Database\Ddd\Entity\Relation\Post;

基本使用方法

fixture 定义

Tests\Database\Ddd\Entity\Relation\Post

php
namespace Tests\Database\Ddd\Entity\Relation;

use Leevel\Database\Ddd\Entity;
use Leevel\Database\Ddd\EntityCollection as Collection;
use Leevel\Database\Ddd\Relation\Relation;
use Leevel\Database\Ddd\Struct;

class Post extends Entity
{
    public const string TABLE = 'post';

    public const string ID = 'id';

    public const string AUTO = 'id';

    public const string DELETE_AT = 'delete_at';

    #[Struct([
        self::READONLY => true,
        self::COLUMN_NAME => 'ID',
    ])]
    protected ?int $id = null;

    #[Struct([
        self::COLUMN_NAME => '标题',
    ])]
    protected ?string $title = null;

    #[Struct([
        self::COLUMN_NAME => '用户ID',
    ])]
    protected ?int $userId = null;

    #[Struct([
        self::COLUMN_NAME => '文章摘要',
    ])]
    protected ?string $summary = null;

    #[Struct([
        self::COLUMN_NAME => '创建时间',
    ])]
    protected ?string $createAt = null;

    #[Struct([
        self::CREATE_FILL => 0,
        self::COLUMN_NAME => '删除时间',
    ])]
    protected ?int $deleteAt = null;

    #[Struct([
        self::BELONGS_TO => User::class,
        self::SOURCE_KEY => 'user_id',
        self::TARGET_KEY => 'id',
    ])]
    protected ?User $user = null;

    #[Struct([
        self::HAS_MANY => Comment::class,
        self::SOURCE_KEY => 'id',
        self::TARGET_KEY => 'post_id',
        self::RELATION_SCOPE => 'comment',
    ])]
    protected ?Collection $comment = null;

    #[Struct([
        self::HAS_ONE => PostContent::class,
        self::SOURCE_KEY => 'id',
        self::TARGET_KEY => 'post_id',
    ])]
    protected ?PostContent $postContent = null;

    #[Struct([
        self::BELONGS_TO => User::class,
        self::TARGET_KEY => 'id',
    ])]
    protected ?int $userNotDefinedSourceKey = null;

    #[Struct([
        self::BELONGS_TO => User::class,
        self::SOURCE_KEY => 'id',
    ])]
    protected ?int $userNotDefinedTargetKey = null;

    #[Struct([
        self::HAS_MANY => Comment::class,
        self::TARGET_KEY => 'post_id',
        self::RELATION_SCOPE => 'comment',
    ])]
    protected ?int $commentNotDefinedSourceKey = null;

    #[Struct([
        self::HAS_MANY => Comment::class,
        self::SOURCE_KEY => 'id',
        self::RELATION_SCOPE => 'comment',
    ])]
    protected ?int $commentNotDefinedTargetKey = null;

    #[Struct([
        self::HAS_ONE => PostContent::class,
        self::TARGET_KEY => 'post_id',
    ])]
    protected ?int $postContentNotDefinedSourceKey = null;

    #[Struct([
        self::HAS_ONE => PostContent::class,
        self::SOURCE_KEY => 'id',
    ])]
    protected ?int $postContentNotDefinedTargetKey = null;

    protected function relationScopeComment(Relation $relation): void
    {
        $relation->where('id', '>', 4);
    }

    protected function relationScopeStringUserRelationScope(Relation $relation): void
    {
        $relation->where('id', '>', 4);
    }
}

Tests\Database\Ddd\Entity\Relation\Comment

php
namespace Tests\Database\Ddd\Entity\Relation;

use Leevel\Database\Ddd\Entity;
use Leevel\Database\Ddd\Struct;

class Comment extends Entity
{
    public const string TABLE = 'comment';

    public const string ID = 'id';

    public const string AUTO = 'id';

    #[Struct([
    ])]
    protected ?int $id = null;

    #[Struct([
    ])]
    protected ?string $title = null;

    #[Struct([
    ])]
    protected ?int $postId = null;

    #[Struct([
    ])]
    protected ?string $content = null;

    #[Struct([
    ])]
    protected ?string $createAt = null;
}
php
public function testBaseUse(): void
{
    $post = Post::select()->where('id', 1)->findOne();

    $this->assertInstanceof(Post::class, $post);
    self::assertNull($post->id);

    $connect = $this->createDatabaseConnect();

    self::assertSame(
        1,
        $connect
            ->table('post')
            ->insert([
                'title' => 'hello world',
                'user_id' => 1,
                'summary' => 'Say hello to the world.',
                'delete_at' => 0,
            ]),
    );

    for ($i = 0; $i < 10; ++$i) {
        $connect
            ->table('comment')
            ->insert([
                'title' => 'niu'.($i + 1),
                'post_id' => 1,
                'content' => 'Comment data.'.($i + 1),
            ])
        ;
    }

    $post = Post::select()->where('id', 1)->findOne();

    self::assertSame(1, $post->id);
    self::assertSame(1, $post['id']);
    self::assertSame(1, $post->getId());
    self::assertSame(1, $post->user_id);
    self::assertSame(1, $post->userId);
    self::assertSame(1, $post['user_id']);
    self::assertSame(1, $post->getUserId());
    self::assertSame('hello world', $post->title);
    self::assertSame('hello world', $post['title']);
    self::assertSame('hello world', $post->getTitle());
    self::assertSame('Say hello to the world.', $post->summary);
    self::assertSame('Say hello to the world.', $post['summary']);
    self::assertSame('Say hello to the world.', $post->getSummary());

    $comment = $post->comment;

    $this->assertInstanceof(Collection::class, $comment);

    $n = 0;

    foreach ($comment as $k => $v) {
        $id = (int) ($n + 5);

        self::assertInstanceOf(Comment::class, $v);
        self::assertSame($n, $k);
        self::assertSame($id, (int) $v->id);
        self::assertSame($id, (int) $v['id']);
        self::assertSame($id, (int) $v->getId());
        self::assertSame('niu'.$id, $v['title']);
        self::assertSame('niu'.$id, $v->title);
        self::assertSame('niu'.$id, $v->getTitle());
        self::assertSame('Comment data.'.$id, $v['content']);
        self::assertSame('Comment data.'.$id, $v->content);
        self::assertSame('Comment data.'.$id, $v->getContent());

        ++$n;
    }

    self::assertCount(6, $comment);
}

eager 预加载关联

php
public function testEager(): void
{
    $post = Post::select()->where('id', 1)->findOne();

    $this->assertInstanceof(Post::class, $post);
    self::assertNull($post->id);

    $connect = $this->createDatabaseConnect();

    self::assertSame(
        1,
        $connect
            ->table('post')
            ->insert([
                'title' => 'hello world',
                'user_id' => 1,
                'summary' => 'Say hello to the world.',
                'delete_at' => 0,
            ]),
    );

    self::assertSame(
        2,
        $connect
            ->table('post')
            ->insert([
                'title' => 'foo bar',
                'user_id' => 1,
                'summary' => 'Say foo to the bar.',
                'delete_at' => 0,
            ]),
    );

    for ($i = 0; $i < 10; ++$i) {
        $connect
            ->table('comment')
            ->insert([
                'title' => 'niu'.($i + 1),
                'post_id' => 1,
                'content' => 'Comment data.'.($i + 1),
            ])
        ;
    }

    for ($i = 0; $i < 10; ++$i) {
        $connect
            ->table('comment')
            ->insert([
                'title' => 'niu'.($i + 1),
                'post_id' => 2,
                'content' => 'Comment data.'.($i + 1),
            ])
        ;
    }

    $posts = Post::eager(['comment'])->findAll();

    $this->assertInstanceof(Collection::class, $posts);
    self::assertCount(2, $posts);

    $min = 5;

    foreach ($posts as $k => $value) {
        $comments = $value->comment;

        $this->assertInstanceof(Collection::class, $comments);
        self::assertSame(0 === $k ? 6 : 10, \count($comments));

        foreach ($comments as $comment) {
            $this->assertInstanceof(Comment::class, $comment);
            self::assertSame($min, $comment->id);
            ++$min;
        }
    }
}

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

php
public function testEagerWithCondition(): void
{
    $post = Post::select()->where('id', 1)->findOne();

    $this->assertInstanceof(Post::class, $post);
    self::assertNull($post->id);

    $connect = $this->createDatabaseConnect();

    self::assertSame(
        1,
        $connect
            ->table('post')
            ->insert([
                'title' => 'hello world',
                'user_id' => 1,
                'summary' => 'Say hello to the world.',
                'delete_at' => 0,
            ]),
    );

    self::assertSame(
        2,
        $connect
            ->table('post')
            ->insert([
                'title' => 'foo bar',
                'user_id' => 1,
                'summary' => 'Say foo to the bar.',
                'delete_at' => 0,
            ]),
    );

    for ($i = 0; $i < 10; ++$i) {
        $connect
            ->table('comment')
            ->insert([
                'title' => 'niu'.($i + 1),
                'post_id' => 1,
                'content' => 'Comment data.'.($i + 1),
            ])
        ;
    }

    for ($i = 0; $i < 10; ++$i) {
        $connect
            ->table('comment')
            ->insert([
                'title' => 'niu'.($i + 1),
                'post_id' => 2,
                'content' => 'Comment data.'.($i + 1),
            ])
        ;
    }

    $posts = Post::eager(['comment' => static function ($select): void {
        $select->where('id', '>', 99999);
    }])->findAll();

    $this->assertInstanceof(Collection::class, $posts);
    self::assertCount(2, $posts);

    foreach ($posts as $k => $value) {
        $comments = $value->comment;
        $this->assertInstanceof(Collection::class, $comments);
        self::assertCount(0, $comments);
    }
}

relation 读取关联

php
public function testRelationAsMethod(): void
{
    $connect = $this->createDatabaseConnect();

    self::assertSame(
        1,
        $connect
            ->table('post')
            ->insert([
                'title' => 'hello world',
                'user_id' => 1,
                'summary' => 'Say hello to the world.',
                'delete_at' => 0,
            ]),
    );

    for ($i = 0; $i < 10; ++$i) {
        $connect
            ->table('comment')
            ->insert([
                'title' => 'niu'.($i + 1),
                'post_id' => 1,
                'content' => 'Comment data.'.($i + 1),
            ])
        ;
    }

    $commentRelation = Post::make()->relation('comment');

    $this->assertInstanceof(HasMany::class, $commentRelation);
    self::assertSame('id', $commentRelation->getSourceKey());
    self::assertSame('post_id', $commentRelation->getTargetKey());
    $this->assertInstanceof(Post::class, $commentRelation->getSourceEntity());
    $this->assertInstanceof(Comment::class, $commentRelation->getTargetEntity());
    $this->assertInstanceof(Select::class, $commentRelation->getSelect());
}

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

php
public function testRelationDataWasNotFound(): void
{
    $post = Post::select()->where('id', 1)->findOne();

    $this->assertInstanceof(Post::class, $post);
    self::assertNull($post->id);

    $connect = $this->createDatabaseConnect();

    self::assertSame(
        1,
        $connect
            ->table('post')
            ->insert([
                'title' => 'hello world',
                'user_id' => 1,
                'summary' => 'Say hello to the world.',
                'delete_at' => 0,
            ]),
    );

    for ($i = 0; $i < 10; ++$i) {
        $connect
            ->table('comment')
            ->insert([
                'title' => 'niu'.($i + 1),
                'post_id' => 2,
                'content' => 'Comment data.'.($i + 1),
            ])
        ;
    }

    $post = Post::select()->where('id', 1)->findOne();

    self::assertSame(1, $post->id);
    self::assertSame(1, $post['id']);
    self::assertSame(1, $post->getId());
    self::assertSame(1, $post->user_id);
    self::assertSame(1, $post->userId);
    self::assertSame(1, $post['user_id']);
    self::assertSame(1, $post->getUserId());
    self::assertSame('hello world', $post->title);
    self::assertSame('hello world', $post['title']);
    self::assertSame('hello world', $post->getTitle());
    self::assertSame('Say hello to the world.', $post->summary);
    self::assertSame('Say hello to the world.', $post['summary']);
    self::assertSame('Say hello to the world.', $post->getSummary());

    $comment = $post->comment;

    $this->assertInstanceof(Collection::class, $comment);
    self::assertCount(0, $comment);
}