Skip to content

事务工作单元

用事务工作单元更好地处理数据库相关工作。

Uses

php
<?php

use Leevel\Database\Ddd\Entity;
use Leevel\Database\Ddd\UnitOfWork;
use Leevel\Database\DuplicateKeyException;
use Leevel\Kernel\Utils\Api;
use Tests\Database\DatabaseTestCase as TestCase;
use Tests\Database\Ddd\Entity\CompositeId;
use Tests\Database\Ddd\Entity\Guestbook;
use Tests\Database\Ddd\Entity\GuestbookRepository;
use Tests\Database\Ddd\Entity\Relation\Post;

保存一个实体

php
public function testBaseUse(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $post = new Post([
        'title' => 'hello world',
        'user_id' => 1,
        'summary' => 'post summary',
    ]);

    self::assertNull($post->id);

    $work->persist($post);

    $work->flush();

    self::assertSame(1, $post->id);
    self::assertSame(1, $post['id']);
    self::assertSame(1, $post->getId());
    self::assertSame(1, $post->userId);
    self::assertSame('post summary', $post->summary);
}

TIP

通过 persist 方法保存一个实体,并通过 flush 将实体持久化到数据库。

保存多个实体

php
public function testPersist(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $post = new Post([
        'title' => 'hello world',
        'user_id' => 1,
        'summary' => 'post summary',
    ]);

    self::assertNull($post->id);

    $post2 = new Post([
        'title' => 'hello world',
        'user_id' => 2,
        'summary' => 'foo bar',
    ]);

    self::assertNull($post2->id);

    $work->persist($post);
    $work->persist($post2);
    $work->on($post2, static function (): void {
        $GLOBALS['unitofwork'][] = 1;
    });
    $work->on($post, static function (): void {
        $GLOBALS['unitofwork'][] = 2;
    });

    $work->flush();

    $data = <<<'eot'
        [
            2,
            1
        ]
        eot;

    self::assertSame(
        $data,
        $this->varJson(
            $GLOBALS['unitofwork']
        )
    );

    self::assertSame(1, $post->id);
    self::assertSame(1, $post['id']);
    self::assertSame(1, $post->getId());
    self::assertSame(1, $post->userId);
    self::assertSame('post summary', $post->summary);

    self::assertSame(2, $post2->id);
    self::assertSame(2, $post2['id']);
    self::assertSame(2, $post2->getId());
    self::assertSame(2, $post2->userId);
    self::assertSame('foo bar', $post2->summary);
}

TIP

底层会开启一个事务,只有全部保存成功才会真正持久化到数据库。

新增实体

php
public function testCreate(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $post = new Post([
        'title' => 'hello world',
        'user_id' => 1,
        'summary' => 'post summary',
    ]);

    $post2 = new Post([
        'title' => 'hello world',
        'user_id' => 2,
        'summary' => 'foo bar',
    ]);

    self::assertNull($post->id);
    self::assertNull($post2->id);
    self::assertFalse($work->created($post));
    self::assertFalse($work->created($post2));
    self::assertFalse($work->registered($post));
    self::assertFalse($work->registered($post2));

    $work->create($post);
    $work->create($post2);

    self::assertTrue($work->created($post));
    self::assertTrue($work->created($post2));
    self::assertTrue($work->registered($post));
    self::assertTrue($work->registered($post2));

    $work->on($post2, static function (): void {
        $GLOBALS['unitofwork'][] = 1;
    });
    $work->on($post, static function (): void {
        $GLOBALS['unitofwork'][] = 2;
    });

    $work->flush();

    $data = <<<'eot'
        [
            2,
            1
        ]
        eot;

    self::assertSame(
        $data,
        $this->varJson(
            $GLOBALS['unitofwork']
        )
    );

    self::assertTrue($work->created($post));
    self::assertTrue($work->created($post2));
    self::assertTrue($work->registered($post));
    self::assertTrue($work->registered($post2));

    self::assertSame(1, $post->id);
    self::assertSame(1, $post['id']);
    self::assertSame(1, $post->getId());
    self::assertSame(1, $post->userId);
    self::assertSame('post summary', $post->summary);

    self::assertSame(2, $post2->id);
    self::assertSame(2, $post2['id']);
    self::assertSame(2, $post2->getId());
    self::assertSame(2, $post2->userId);
    self::assertSame('foo bar', $post2->summary);
}

TIP

底层执行的是 insert 语句,只有全部保存成功才会真正持久化到数据库。

更新实体

php
public function testUpdate(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $connect = $this->createDatabaseConnect();

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

    self::assertSame(
        2,
        $connect
            ->table('post')
            ->insert([
                'title' => 'hello world',
                'user_id' => 2,
                'summary' => 'foo bar',
                'delete_at' => 0,
            ])
    );

    $post = Post::select()->findEntity(1);

    $post2 = Post::select()->findEntity(2);

    $this->assertInstanceof(Entity::class, $post);
    $this->assertInstanceof(Entity::class, $post2);
    $this->assertInstanceof(Post::class, $post);
    $this->assertInstanceof(Post::class, $post2);

    self::assertSame(1, $post->id);
    self::assertSame(1, $post['id']);
    self::assertSame(1, $post->getId());
    self::assertSame(1, $post->userId);
    self::assertSame('post summary', $post->summary);
    self::assertSame('hello world', $post->title);

    self::assertSame(2, $post2->id);
    self::assertSame(2, $post2['id']);
    self::assertSame(2, $post2->getId());
    self::assertSame(2, $post2->userId);
    self::assertSame('foo bar', $post2->summary);
    self::assertSame('hello world', $post2->title);

    self::assertFalse($work->updated($post));
    self::assertFalse($work->updated($post2));
    self::assertFalse($work->registered($post));
    self::assertFalse($work->registered($post2));

    $post->title = 'new post title';
    $post->summary = 'new post summary';

    $post2->title = 'new post2 title';
    $post2->summary = 'new post2 summary';

    $work->update($post);
    $work->update($post2);

    self::assertTrue($work->updated($post));
    self::assertTrue($work->updated($post2));
    self::assertTrue($work->registered($post));
    self::assertTrue($work->registered($post2));

    $work->on($post2, static function (): void {
        $GLOBALS['unitofwork'][] = 1;
    });
    $work->on($post, static function (): void {
        $GLOBALS['unitofwork'][] = 2;
    });

    $work->flush();

    $data = <<<'eot'
        [
            2,
            1
        ]
        eot;

    self::assertSame(
        $data,
        $this->varJson(
            $GLOBALS['unitofwork']
        )
    );

    self::assertTrue($work->updated($post));
    self::assertTrue($work->updated($post2));
    self::assertTrue($work->registered($post));
    self::assertTrue($work->registered($post2));

    self::assertSame(1, $post->id);
    self::assertSame(1, $post['id']);
    self::assertSame(1, $post->getId());
    self::assertSame(1, $post->userId);
    self::assertSame('new post title', $post->title);
    self::assertSame('new post summary', $post->summary);

    self::assertSame(2, $post2->id);
    self::assertSame(2, $post2['id']);
    self::assertSame(2, $post2->getId());
    self::assertSame(2, $post2->userId);
    self::assertSame('new post2 title', $post2->title);
    self::assertSame('new post2 summary', $post2->summary);
}

TIP

底层执行的是 update 语句,只有全部保存成功才会真正持久化到数据库。

删除实体

php
public function testDelete(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $connect = $this->createDatabaseConnect();

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

    self::assertSame(
        2,
        $connect
            ->table('post')
            ->insert([
                'title' => 'hello world',
                'user_id' => 2,
                'summary' => 'foo bar',
                'delete_at' => 0,
            ])
    );

    $post = Post::select()->findEntity(1);

    $post2 = Post::select()->findEntity(2);

    $this->assertInstanceof(Entity::class, $post);
    $this->assertInstanceof(Entity::class, $post2);
    $this->assertInstanceof(Post::class, $post);
    $this->assertInstanceof(Post::class, $post2);

    self::assertSame(1, $post->id);
    self::assertSame(1, $post['id']);
    self::assertSame(1, $post->getId());
    self::assertSame(1, $post->userId);
    self::assertSame('post summary', $post->summary);
    self::assertSame('hello world', $post->title);

    self::assertSame(2, $post2->id);
    self::assertSame(2, $post2['id']);
    self::assertSame(2, $post2->getId());
    self::assertSame(2, $post2->userId);
    self::assertSame('foo bar', $post2->summary);
    self::assertSame('hello world', $post2->title);

    self::assertFalse($work->deleted($post));
    self::assertFalse($work->deleted($post2));
    self::assertFalse($work->registered($post));
    self::assertFalse($work->registered($post2));

    $work->delete($post);
    $work->delete($post2);
    $work->on($post2, static function (): void {
        $GLOBALS['unitofwork'][] = 1;
    });
    $work->on($post, static function (): void {
        $GLOBALS['unitofwork'][] = 2;
    });

    self::assertTrue($work->deleted($post));
    self::assertTrue($work->deleted($post2));
    self::assertTrue($work->registered($post));
    self::assertTrue($work->registered($post2));

    $work->flush();

    $data = <<<'eot'
        [
            2,
            1
        ]
        eot;

    self::assertSame(
        $data,
        $this->varJson(
            $GLOBALS['unitofwork']
        )
    );

    self::assertTrue($work->deleted($post));
    self::assertTrue($work->deleted($post2));
    self::assertTrue($work->registered($post));
    self::assertTrue($work->registered($post2));

    $postAfter = Post::select()->findEntity(1);
    $post2After = Post::select()->findEntity(2);

    self::assertNull($postAfter->id);
    self::assertNull($postAfter['id']);
    self::assertNull($postAfter->getId());
    self::assertNull($postAfter->userId);
    self::assertNull($postAfter->title);
    self::assertNull($postAfter->summary);

    self::assertNull($post2After->id);
    self::assertNull($post2After['id']);
    self::assertNull($post2After->getId());
    self::assertNull($post2After->userId);
    self::assertNull($post2After->title);
    self::assertNull($post2After->summary);

    $postAfter = Post::withSoftDeleted()->findEntity(1);
    $post2After = Post::withSoftDeleted()->findEntity(2);

    self::assertSame(1, $postAfter->id);
    self::assertSame(1, $postAfter['id']);
    self::assertSame(1, $postAfter->getId());
    self::assertSame(1, $postAfter->userId);
    self::assertSame('post summary', $postAfter->summary);
    self::assertSame('hello world', $postAfter->title);

    self::assertSame(2, $post2After->id);
    self::assertSame(2, $post2After['id']);
    self::assertSame(2, $post2After->getId());
    self::assertSame(2, $post2After->userId);
    self::assertSame('foo bar', $post2After->summary);
    self::assertSame('hello world', $post2After->title);
}

TIP

底层执行的是 delete 语句,只有全部保存成功才会真正持久化到数据库。

刷新实体

php
public function testRefresh(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $connect = $this->createDatabaseConnect();

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

    $post = new Post([
        'id' => 1,
        'title' => 'old',
        'summary' => 'old',
    ], true);

    self::assertSame(1, $post->getId());
    self::assertSame('old', $post->getSummary());
    self::assertSame('old', $post->getTitle());

    $work->persist($post);
    $work->refresh($post);

    self::assertSame(1, $post->getId());
    self::assertSame('post summary', $post->getSummary());
    self::assertSame('hello world', $post->getTitle());
    $post->title = 'new title';

    $work->flush();

    $post = Post::select()->findEntity(1);

    $this->assertInstanceof(Entity::class, $post);
    $this->assertInstanceof(Post::class, $post);

    self::assertSame(1, $post->id);
    self::assertSame(1, $post['id']);
    self::assertSame(1, $post->getId());
    self::assertSame(1, $post->userId);
    self::assertSame('post summary', $post->summary);
    self::assertSame('new title', $post->title);
}

TIP

底层执行的是 select 语句,这个操作会读取数据库最新信息并刷新实体的属性。

手工启动事务 beginTransaction

php
public function testBeginTransaction(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $connect = $this->createDatabaseConnect();

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

    $work->beginTransaction();

    $post = Post::select()->findEntity(1);
    $work->update($post);

    try {
        $post->title = 'new title';
        $work->flush();
        $work->commit();
    } catch (\Throwable) {
        $work->close();
        $work->rollBack();
    }

    self::assertSame(1, $post->getId());
    self::assertSame('new title', $post->getTitle());
}

TIP

通常来说事务工作单元会自动帮你处理事务,可以通过手工 beginTransaction,成功 commit 或者失败 rollBack,系统提供了 API 让你也手工开启事务处理。

执行失败事务回滚 rollBack

php
public function testFlushButRollBack(): void
{
    $this->expectException(DuplicateKeyException::class);
    $this->expectExceptionMessage(
        // MySQL8:SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '1' for key 'post.PRIMARY'
        // MySQL5.7:SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate '1' for key 'PRIMARY'
        'SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry \'1\' for key'
    );

    $work = UnitOfWork::make();

    $post = new Post([
        'id' => 1,
        'title' => 'old',
        'summary' => 'old',
        'user_id' => 0,
    ]);

    $post2 = new Post([
        'id' => 1,
        'title' => 'old',
        'summary' => 'old',
        'user_id' => 0,
    ]);

    $work->create($post);
    $work->create($post2);

    $work->flush();
}

TIP

底层会自动运行一个事务,如果执行失败自动回滚,不会更新数据库。

事务包裹在闭包中 transaction

php
public function testTransaction(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $connect = $this->createDatabaseConnect();

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

    $work->transaction(static function (UnitOfWork $w): void {
        $post = Post::select()->findEntity(1);
        $w->update($post);

        $post->title = 'new title';
    });

    $newPost = Post::select()->findEntity(1);

    self::assertSame(1, $newPost->getId());
    self::assertSame('new title', $newPost->getTitle());
}

TIP

可以将事务包裹在一个闭包中,如果执行失败自动回滚,不会更新数据库。

事务包裹在闭包中失败回滚 transaction

php
public function testTransactionAndRollBack(): void
{
    $this->expectException(DuplicateKeyException::class);
    $this->expectExceptionMessage(
        // MySQL8:SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '1' for key 'post.PRIMARY'
        // MySQL5.7:SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '1' for key 'PRIMARY'
        'SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry \'1\' for key'
    );

    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $connect = $this->createDatabaseConnect();

    $work->transaction(static function ($w): void {
        $post = new Post([
            'id' => 1,
            'title' => 'old',
            'summary' => 'old',
            'user_id' => 0,
        ]);

        $post2 = new Post([
            'id' => 1,
            'title' => 'old',
            'summary' => 'old',
            'user_id' => 0,
        ]);

        $w->create($post);
        $w->create($post2);
    });

    self::assertSame(0, $connect->table('post')->findCount());
}

TIP

可以将事务包裹在一个闭包中,执行失败自动回滚测试,不会更新数据库。

设置实体 setEntity

php
public function testSetRootEntity(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $connect = $this->createDatabaseConnect();

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

    $post = Post::select()->findEntity(1);
    $work->setEntity($post);

    $work->update($post);

    $post->title = 'new title';

    $work->flush();

    self::assertSame(1, $post->getId());
    self::assertSame('new title', $post->getTitle());

    $newPost = Post::select()->findEntity(1);

    self::assertSame(1, $newPost->getId());
    self::assertSame('new title', $newPost->getTitle());

    $work->setEntity($post);
}

TIP

系统默认读取基础的数据库配置来处理数据相关信息,设置跟实体还可以更改事务处理的数据库连接。

保持实体支持缓存

php
public function testPersistStageManagedEntityDoNothing(): void
{
    $work = UnitOfWork::make();

    $connect = $this->createDatabaseConnect();

    $post = new Post([
        'id' => 1,
        'title' => 'old',
        'summary' => 'old',
        'user_id' => 0,
    ]);

    $work->persist($post, 'create');
    $work->persist($post, 'create');

    $work->flush();

    self::assertSame(1, $connect->table('post')->findCount());
}

TIP

保存两个一样的实体,第二个实体并不会被添加。

重新保存已删除的实体实体

php
public function testPersistStageRemovedEntity(): void
{
    $work = UnitOfWork::make();

    $connect = $this->createDatabaseConnect();

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

    $post = Post::select()->findEntity(1);
    self::assertSame(1, $post->getId());
    self::assertSame('hello world', $post->getTitle());
    self::assertSame('post summary', $post->getSummary());
    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));
    $work->delete($post);
    self::assertSame(UnitOfWork::STATE_REMOVED, $work->getEntityState($post));
    $work->persist($post);
    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));
    $work->flush();
    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));
    self::assertSame(1, $connect->table('post')->findCount());
}

TIP

这样被删除的实体并不会被删除。

注册更新的实体不能重新被创建

php
public function testCreateButAlreadyInUpdates(): void
{
    $this->expectException(\InvalidArgumentException::class);
    $this->expectExceptionMessage(
        'Updated entity `Tests\\Database\\Ddd\\Entity\\Relation\\Post` cannot be added for create.'
    );

    $work = UnitOfWork::make();

    $post = new Post(['id' => 5, 'title' => 'foo']);

    $work->update($post);

    $work->create($post);
}

注册删除的实体不能重新被创建

php
public function testCreateButAlreadyInDeletes(): void
{
    $this->expectException(\InvalidArgumentException::class);
    $this->expectExceptionMessage(
        'Deleted entity `Tests\\Database\\Ddd\\Entity\\Relation\\Post` cannot be added for create.'
    );

    $work = UnitOfWork::make();

    $post = new Post(['id' => 5]);

    $work->delete($post);

    $work->create($post);
}

注册替换的实体不能重新被创建

php
public function testCreateButAlreadyInReplaces(): void
{
    $this->expectException(\InvalidArgumentException::class);
    $this->expectExceptionMessage(
        'Replaced entity `Tests\\Database\\Ddd\\Entity\\Relation\\Post` cannot be added for create.'
    );

    $work = UnitOfWork::make();

    $post = new Post(['id' => 5]);

    $work->replace($post);

    $work->create($post);
}

不能多次创建同一个实体

php
public function testCreateManyTimes(): void
{
    $this->expectException(\InvalidArgumentException::class);
    $this->expectExceptionMessage(
        'Entity `Tests\\Database\\Ddd\\Entity\\Relation\\Post` cannot be added for twice.'
    );

    $work = UnitOfWork::make();

    $connect = $this->createDatabaseConnect();

    $post = new Post(['title' => 'foo']);

    $work->create($post);
    $work->create($post);
}

已经删除的实体不能够被更新

php
public function testUpdateButAlreadyInDeletes(): void
{
    $this->expectException(\InvalidArgumentException::class);
    $this->expectExceptionMessage(
        'Deleted entity `Tests\\Database\\Ddd\\Entity\\Relation\\Post` cannot be added for update.'
    );

    $work = UnitOfWork::make();

    $post = new Post(['id' => 5, 'title' => 'new']);

    $work->delete($post);

    $work->update($post);
}

已经创建的实体不能够被更新

php
public function testUpdateButAlreadyInCreates(): void
{
    $this->expectException(\InvalidArgumentException::class);
    $this->expectExceptionMessage(
        'Created entity `Tests\\Database\\Ddd\\Entity\\Relation\\Post` cannot be added for update.'
    );

    $work = UnitOfWork::make();

    $post = new Post(['id' => 5, 'title' => 'new']);

    $work->create($post);

    $work->update($post);
}

已经替换的实体不能够被更新

php
public function testUpdateButAlreadyInReplaces(): void
{
    $this->expectException(\InvalidArgumentException::class);
    $this->expectExceptionMessage(
        'Replaced entity `Tests\\Database\\Ddd\\Entity\\Relation\\Post` cannot be added for update.'
    );

    $work = UnitOfWork::make();

    $post = new Post(['id' => 5, 'title' => 'new']);

    $work->replace($post);

    $work->update($post);
}

update 不能多次更新同一个实体

php
public function testUpdateManyTimes(): void
{
    $this->expectException(\InvalidArgumentException::class);
    $this->expectExceptionMessage(
        'Entity `Tests\\Database\\Ddd\\Entity\\Relation\\Post` cannot be updated for twice.'
    );

    $work = UnitOfWork::make();

    $connect = $this->createDatabaseConnect();

    $post = new Post(['id' => 1, 'title' => 'foo']);

    $work->update($post);
    $work->update($post);
}

delete.create 已创建的实体可以被删除

php
public function testDeleteCreated(): void
{
    $work = UnitOfWork::make();

    $connect = $this->createDatabaseConnect();

    $post = new Post(['title' => 'foo', 'id' => 5]);

    $work->create($post);
    $work->delete($post);

    $work->flush();

    self::assertSame(0, $connect->table('post')->findCount());
}

delete.update 删除已更新的实体

php
public function testDeleteUpdated(): void
{
    $work = UnitOfWork::make();

    $connect = $this->createDatabaseConnect();

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

    $post = Post::select()->findEntity(1);

    $work->update($post);
    $work->delete($post);

    $post->title = 'new';

    $work->flush();

    $postNew = Post::select()->findEntity(1);

    self::assertSame(1, $connect->table('post')->findCount());
    self::assertSame(0, $connect->table('post')->where('delete_at', 0)->findCount());
    self::assertNull($postNew->id);
    self::assertNull($postNew->title);
}

delete.replace 删除已替换的实体

php
public function testDeleteReplaced(): void
{
    $work = UnitOfWork::make();

    $connect = $this->createDatabaseConnect();

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

    $post = Post::select()->findEntity(1);

    $work->replace($post);
    $work->delete($post);

    $post->title = 'new';

    $work->flush();

    $postNew = Post::select()->findEntity(1);

    self::assertSame(1, $connect->table('post')->findCount());
    self::assertSame(0, $connect->table('post')->where('delete_at', 0)->findCount());
    self::assertNull($postNew->id);
    self::assertNull($postNew->title);
}

repository 取得实体仓储

php
public function testRepository(): void
{
    $work = UnitOfWork::make();

    $repository = $work->repository(Guestbook::class);

    $this->assertInstanceof(GuestbookRepository::class, $repository);
}

repository 取得实体仓储支持实体实例

php
public function testRepository2(): void
{
    $work = UnitOfWork::make();

    $repository = $work->repository(new Guestbook());

    $this->assertInstanceof(GuestbookRepository::class, $repository);
}

remove 移除未被管理的实体不做任何处理直接返回

php
public function testRemoveStageNewDoNothing(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $work->remove($post = new Post());

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));
}

remove 移除管理的新增实体直接删除

php
public function testRemoveStageCreateManaged(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $work->create($post = new Post(['id' => 5]));
    self::assertSame(UnitOfWork::STATE_MANAGED, $work->getEntityState($post));
    $work->remove($post);
    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));
}

remove 移除管理的更新实体直接删除

php
public function testRemoveStageUpdateManaged(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $work->update($post = new Post(['id' => 5], true));
    self::assertSame(UnitOfWork::STATE_MANAGED, $work->getEntityState($post));
    $work->remove($post);
    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));
}

remove 移除未被管理的实体到前置区域不做任何处理直接返回

php
public function testRemoveBeforeStageNewDoNothing(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $work->removeBefore($post = new Post());

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));
}

remove 移除未被管理的实体到后置区域不做任何处理直接返回

php
public function testRemoveAfterBeforeStageNewDoNothing(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $work->removeAfter($post = new Post());

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));
}

forceRemove 强制移除未被管理的实体不做任何处理直接返回

php
public function testForceRemoveStageNewDoNothing(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $work->forceRemove($post = new Post());

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));
}

forceRemove 强制移除未被管理的实体到前置区域不做任何处理直接返回

php
public function testForceRemoveBeforeStageNewDoNothing(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $work->forceRemoveBefore($post = new Post());

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));
}

forceRemove 强制移除未被管理的实体到后置区域不做任何处理直接返回

php
public function testForceRemoveAfterStageNewDoNothing(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $work->forceRemoveAfter($post = new Post());

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));
}

remove 移除已删除的实体不做任何处理直接返回

php
public function testRemoveStageRemovedDoNothing(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $work->delete($post = new Post(['id' => 5]));
    $work->remove($post);

    self::assertSame(UnitOfWork::STATE_REMOVED, $work->getEntityState($post));
}

remove 移除已删除的实体到前置区域不做任何处理直接返回

php
public function testRemoveBeforeStageRemovedDoNothing(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $work->delete($post = new Post(['id' => 5]));
    $work->removeBefore($post);

    self::assertSame(UnitOfWork::STATE_REMOVED, $work->getEntityState($post));
}

remove 移除已删除的实体到后置区域不做任何处理直接返回

php
public function testRemoveAfterStageRemovedDoNothing(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $work->delete($post = new Post(['id' => 5]));
    $work->removeAfter($post);

    self::assertSame(UnitOfWork::STATE_REMOVED, $work->getEntityState($post));
}

forceRemove 强制移除已删除的实体不做任何处理直接返回

php
public function testForceRemoveStageRemovedDoNothing(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $work->delete($post = new Post(['id' => 5]));
    $work->forceRemove($post);

    self::assertSame(UnitOfWork::STATE_REMOVED, $work->getEntityState($post));
}

forceRemove 强制移除已删除的实体到前置区域不做任何处理直接返回

php
public function testForceRemoveBeforeStageRemovedDoNothing(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $work->delete($post = new Post(['id' => 5]));
    $work->forceRemoveBefore($post);

    self::assertSame(UnitOfWork::STATE_REMOVED, $work->getEntityState($post));
}

forceRemove 强制移除已删除的实体到后置区域不做任何处理直接返回

php
public function testForceRemoveAfterStageRemovedDoNothing(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $work->delete($post = new Post(['id' => 5]));
    $work->forceRemoveAfter($post);

    self::assertSame(UnitOfWork::STATE_REMOVED, $work->getEntityState($post));
}

remove 移除已经被管理的新增实体将会清理已管理状态,但是不做删除然后直接返回

php
public function testRemoveStageManagedWillDelete(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $post = new Post();

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));

    $work->persist($post);

    self::assertSame(UnitOfWork::STATE_MANAGED, $work->getEntityState($post));

    $work->remove($post);

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));
}

remove 移除已经被管理的新增实体到前置区域将会清理已管理状态,但是不做删除然后直接返回

php
public function testRemoveBeforeStageManagedWillDelete(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $post = new Post();

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));

    $work->persist($post);

    self::assertSame(UnitOfWork::STATE_MANAGED, $work->getEntityState($post));

    $work->removeBefore($post);

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));
}

remove 移除已经被管理的新增实体到后置区域将会清理已管理状态,但是不做删除然后直接返回

php
public function testRemoveAfterStageManagedWillDelete(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $post = new Post();

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));

    $work->persist($post);

    self::assertSame(UnitOfWork::STATE_MANAGED, $work->getEntityState($post));

    $work->removeAfter($post);

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));
}

forceRemove 强制移除已经被管理的新增实体将会清理已管理状态,但是不做删除然后直接返回

php
public function testForceRemoveStageManagedWillDelete(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $post = new Post();

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));

    $work->persist($post);

    self::assertSame(UnitOfWork::STATE_MANAGED, $work->getEntityState($post));

    $work->forceRemove($post);

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));

    $work->flush();

    $sql = '';
    self::assertSame(
        $sql,
        $post->select()->getLastSql(),
    );
}

forceRemove 强制移除已经被管理的新增实体到前置区域将会清理已管理状态,但是不做删除然后直接返回

php
public function testForceRemoveBeforeStageManagedWillDelete(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $post = new Post();

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));

    $work->persist($post);

    self::assertSame(UnitOfWork::STATE_MANAGED, $work->getEntityState($post));

    $work->forceRemoveBefore($post);

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));

    $work->flush();

    $sql = '';
    self::assertSame(
        $sql,
        $post->select()->getLastSql(),
    );
}

forceRemove 强制移除已经被管理的新增实体到后置区域将会清理已管理状态,但是不做删除然后直接返回

php
public function testForceRemoveAfterStageManagedWillDelete(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $post = new Post();

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));

    $work->persist($post);

    self::assertSame(UnitOfWork::STATE_MANAGED, $work->getEntityState($post));

    $work->forceRemoveAfter($post);

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));

    $work->flush();

    $sql = '';
    self::assertSame(
        $sql,
        $post->select()->getLastSql(),
    );
}

remove 移除已经被管理的替换实体将会清理已管理状态,但是不做删除然后直接返回

php
public function testRemoveStageManagedReplaceWillDelete(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $post = new Post();

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));

    $work->persist($post, 'replace');

    self::assertSame(UnitOfWork::STATE_MANAGED, $work->getEntityState($post));

    $work->remove($post);

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));
}

remove 移除已经被管理的替换实体到前置区域将会清理已管理状态,但是不做删除然后直接返回

php
public function testRemoveBeforeStageManagedReplaceWillDelete(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $post = new Post();

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));

    $work->persist($post, 'replace');

    self::assertSame(UnitOfWork::STATE_MANAGED, $work->getEntityState($post));

    $work->removeBefore($post);

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));
}

remove 移除已经被管理的替换实体到后置区域将会清理已管理状态,但是不做删除然后直接返回

php
public function testRemoveAfterStageManagedReplaceWillDelete(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $post = new Post();

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));

    $work->persist($post, 'replace');

    self::assertSame(UnitOfWork::STATE_MANAGED, $work->getEntityState($post));

    $work->removeAfter($post);

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));
}

forceRemove 强制移除已经被管理的替换实体将会清理已管理状态,但是不做删除然后直接返回

php
public function testForceRemoveStageManagedReplaceWillDelete(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $post = new Post();

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));

    $work->persist($post, 'replace');

    self::assertSame(UnitOfWork::STATE_MANAGED, $work->getEntityState($post));

    $work->forceRemove($post);

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));
}

forceRemove 强制移除已经被管理的替换实体到前置区域将会清理已管理状态,但是不做删除然后直接返回

php
public function testForceRemoveBeforeStageManagedReplaceWillDelete(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $post = new Post();

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));

    $work->persist($post, 'replace');

    self::assertSame(UnitOfWork::STATE_MANAGED, $work->getEntityState($post));

    $work->forceRemoveBefore($post);

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));
}

forceRemove 强制移除已经被管理的替换实体到后置区域将会清理已管理状态,但是不做删除然后直接返回

php
public function testForceRemoveAfterStageManagedReplaceWillDelete(): void
{
    $work = UnitOfWork::make();

    $this->assertInstanceof(UnitOfWork::class, $work);

    $post = new Post();

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));

    $work->persist($post, 'replace');

    self::assertSame(UnitOfWork::STATE_MANAGED, $work->getEntityState($post));

    $work->forceRemoveAfter($post);

    self::assertSame(UnitOfWork::STATE_NEW, $work->getEntityState($post));
}

persist 保持实体自动识别为更新状态

php
public function testPersistAsSaveUpdate(): void
{
    $work = UnitOfWork::make();

    $connect = $this->createDatabaseConnect();

    $post = new Post([
        'id' => 1,
        'title' => 'old',
        'summary' => 'old',
    ], true);

    $work->persist($post);

    $work->flush();

    self::assertSame(0, $connect->table('post')->findCount());
}

persist 保持实体为更新状态

php
public function testPersistAsUpdate(): void
{
    $work = UnitOfWork::make();

    $connect = $this->createDatabaseConnect();

    $post = new Post([
        'id' => 1,
        'title' => 'old',
        'summary' => 'old',
    ]);

    $work->persist($post, 'update');

    $work->flush();

    self::assertSame(0, $connect->table('post')->findCount());
}

persist 保持实体为替换状态

php
public function testPersistAsReplace(): void
{
    $work = UnitOfWork::make();

    $connect = $this->createDatabaseConnect();

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

    $post = new Post([
        'id' => 1,
        'title' => 'old',
        'summary' => 'old',
        'user_id' => 1,
    ]);

    $work->persist($post, 'replace');

    $work->flush();

    $updatedPost = Post::select()->findEntity(1);

    self::assertSame(1, $updatedPost->id);
    self::assertSame('old', $updatedPost->title);
    self::assertSame(1, $updatedPost->userId);
    self::assertSame('old', $updatedPost->summary);
}

persist 已经持久化并且脱离管理的实体状态不能被再次保持

php
public function testPersistStageDetachedEntity(): void
{
    $this->expectException(\InvalidArgumentException::class);
    $this->expectExceptionMessage(
        'Detached entity `Tests\\Database\\Ddd\\Entity\\Relation\\Post` cannot be persist.'
    );

    $work = UnitOfWork::make();
    $post = new Post(['id' => 5, 'title' => 'new']);
    $work->persist($post);
    $work->flush();
    $work->persist($post);
}

remove 已经持久化并且脱离管理的实体状态不能被再次移除

php
public function testRemoveStageDetachedEntity(): void
{
    $this->expectException(\InvalidArgumentException::class);
    $this->expectExceptionMessage(
        'Detached entity `Tests\\Database\\Ddd\\Entity\\Relation\\Post` cannot be remove.'
    );

    $work = UnitOfWork::make();

    $post = new Post(['id' => 5, 'title' => 'new']);

    $work->persist($post);

    $work->flush($post);

    $work->remove($post);
}

on 保持的实体回调

php
public function testOnCallbacks(): void
{
    $work = UnitOfWork::make();

    $post = new Post([
        'title' => 'new',
        'user_id' => 0,
    ]);
    $guestBook = new Guestbook(['name' => '']);

    $work->persist($post);
    $work->persist($guestBook);

    $work->on($post, static function ($p) use ($guestBook): void {
        $guestBook->content = 'guest_book content was post id is '.$p->id;
    });

    $work->flush($post);

    $newGuestbook = Guestbook::select()->findEntity(1);

    self::assertSame('guest_book content was post id is 1', $newGuestbook->content);

    $work->clear();
}

on 替换的实体回调

php
public function testOnCallbacksForReplace(): void
{
    $work = UnitOfWork::make();

    $post = new Post([
        'title' => 'new',
        'user_id' => 0,
    ]);
    $guestBook = new Guestbook(['name' => '']);

    $work->replace($post);
    $work->replace($guestBook);

    $work->on($post, static function ($p) use ($guestBook): void {
        $guestBook->content = 'guest_book content was post id is '.$p->id;
    });

    $work->flush($post);

    $newGuestbook = Guestbook::select()->findEntity(1);

    self::assertSame('guest_book content was post id is 1', $newGuestbook->content);

    $work->clear();
}

on 更新的实体回调

php
public function testOnCallbacksForUpdate(): void
{
    $work = UnitOfWork::make();

    $connect = $this->createDatabaseConnect();

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

    self::assertSame(
        1,
        $connect
            ->table('guest_book')
            ->insert([
                'name' => '',
                'content' => 'hello world',
            ])
    );

    $post = new Post(['id' => 1, 'title' => 'new'], true);
    $guestBook = new Guestbook(['id' => 1], true);

    $work->update($post);
    $work->update($guestBook);

    $work->on($post, static function ($p) use ($guestBook): void {
        $guestBook->content = 'guest_book content was post id is '.$p->id;
    });

    $post->title = 'new new';

    $work->flush($post);

    $newGuestbook = Guestbook::select()->findEntity(1);

    self::assertSame('guest_book content was post id is 1', $newGuestbook->content);

    $work->clear();
}

on 删除的实体回调

php
public function testOnCallbacksForDelete(): void
{
    $work = UnitOfWork::make();

    $connect = $this->createDatabaseConnect();

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

    $post = Post::select()->findEntity(1);
    $work->persist($post)->remove($post);

    $work->on($post, static function ($p): void {
        // post has already removed,do nothing
    });

    $work->flush($post);

    $newPost = Post::select()->findEntity(1);
    self::assertSame(1, $newPost->id);
    $work->clear();
}

replace 注册替换实体

php
public function testReplace(): void
{
    $work = UnitOfWork::make();

    $post = new Post([
        'id' => 1,
        'title' => 'new',
        'user_id' => 0,
    ]);
    $post2 = new Post([
        'id' => 2,
        'title' => 'new2',
        'user_id' => 2,
    ]);

    self::assertFalse($work->replaced($post));
    self::assertFalse($work->replaced($post2));
    $work->replace($post);
    $work->replace($post2);
    self::assertTrue($work->replaced($post));
    self::assertTrue($work->replaced($post2));
    $work->on($post2, static function (): void {
        $GLOBALS['unitofwork'][] = 1;
    });
    $work->on($post, static function (): void {
        $GLOBALS['unitofwork'][] = 2;
    });

    $work->flush();

    $data = <<<'eot'
        [
            2,
            1
        ]
        eot;

    self::assertSame(
        $data,
        $this->varJson(
            $GLOBALS['unitofwork']
        )
    );

    self::assertTrue($work->replaced($post));
    self::assertTrue($work->replaced($post2));

    $createPost = Post::select()->findEntity(1);
    $this->assertInstanceof(Post::class, $createPost);
    self::assertSame(1, $createPost->id);
    self::assertSame('new', $createPost->title);

    $createPost = Post::select()->findEntity(2);
    $this->assertInstanceof(Post::class, $createPost);
    self::assertSame(2, $createPost->id);
    self::assertSame('new2', $createPost->title);
}

replace 注册替换实体到前置区域

php
public function testReplaceBefore(): void
{
    $work = UnitOfWork::make();

    $post = new Post([
        'id' => 1,
        'title' => 'new',
        'user_id' => 0,
    ]);
    $post2 = new Post([
        'id' => 2,
        'title' => 'new2',
        'user_id' => 2,
    ]);

    self::assertFalse($work->replaced($post));
    self::assertFalse($work->replaced($post2));
    $work->replace($post);
    $work->replaceBefore($post2);
    self::assertTrue($work->replaced($post));
    self::assertTrue($work->replaced($post2));
    $work->on($post2, static function (): void {
        $GLOBALS['unitofwork'][] = 1;
    });
    $work->on($post, static function (): void {
        $GLOBALS['unitofwork'][] = 2;
    });

    $work->flush();

    $data = <<<'eot'
        [
            1,
            2
        ]
        eot;

    self::assertSame(
        $data,
        $this->varJson(
            $GLOBALS['unitofwork']
        )
    );

    self::assertTrue($work->replaced($post));
    self::assertTrue($work->replaced($post2));

    $createPost = Post::select()->findEntity(1);
    $this->assertInstanceof(Post::class, $createPost);
    self::assertSame(1, $createPost->id);
    self::assertSame('new', $createPost->title);

    $createPost = Post::select()->findEntity(2);
    $this->assertInstanceof(Post::class, $createPost);
    self::assertSame(2, $createPost->id);
    self::assertSame('new2', $createPost->title);
}

replace 注册替换实体到后置区域

php
public function testReplaceAfter(): void
{
    $work = UnitOfWork::make();

    $post = new Post([
        'id' => 1,
        'title' => 'new',
        'user_id' => 0,
    ]);
    $post2 = new Post([
        'id' => 2,
        'title' => 'new2',
        'user_id' => 2,
    ]);

    self::assertFalse($work->replaced($post));
    self::assertFalse($work->replaced($post2));
    $work->replaceAfter($post);
    $work->replace($post2);
    self::assertTrue($work->replaced($post));
    self::assertTrue($work->replaced($post2));
    $work->on($post2, static function (): void {
        $GLOBALS['unitofwork'][] = 1;
    });
    $work->on($post, static function (): void {
        $GLOBALS['unitofwork'][] = 2;
    });

    $work->flush();

    $data = <<<'eot'
        [
            1,
            2
        ]
        eot;

    self::assertSame(
        $data,
        $this->varJson(
            $GLOBALS['unitofwork']
        )
    );

    self::assertTrue($work->replaced($post));
    self::assertTrue($work->replaced($post2));

    $createPost = Post::select()->findEntity(1);
    $this->assertInstanceof(Post::class, $createPost);
    self::assertSame(1, $createPost->id);
    self::assertSame('new', $createPost->title);

    $createPost = Post::select()->findEntity(2);
    $this->assertInstanceof(Post::class, $createPost);
    self::assertSame(2, $createPost->id);
    self::assertSame('new2', $createPost->title);
}

replace 注册替换实体更新例子

php
public function testReplaceAsUpdate(): void
{
    $work = UnitOfWork::make();

    $connect = $this->createDatabaseConnect();

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

    $post = new Post([
        'id' => 1,
        'title' => 'new',
        'summary' => 'new',
        'user_id' => 1,
    ]);

    $work->replace($post);

    $work->flush();

    $updatedPost = Post::select()->findEntity(1);

    self::assertSame(1, $updatedPost->id);
    self::assertSame('new', $updatedPost->title);
    self::assertSame(1, $updatedPost->userId);
    self::assertSame('new', $updatedPost->summary);
}

已创建的实体不能够被替换

php
public function testReplaceButAlreadyInCreates(): void
{
    $this->expectException(\InvalidArgumentException::class);
    $this->expectExceptionMessage(
        'Created entity `Tests\\Database\\Ddd\\Entity\\Relation\\Post` cannot be added for replace.'
    );

    $work = UnitOfWork::make();

    $post = new Post(['id' => 5, 'title' => 'new']);

    $work->create($post);

    $work->replace($post);
}

已更新的实体不能够被替换

php
public function testReplaceButAlreadyInUpdates(): void
{
    $this->expectException(\InvalidArgumentException::class);
    $this->expectExceptionMessage(
        'Updated entity `Tests\\Database\\Ddd\\Entity\\Relation\\Post` cannot be added for replace.'
    );

    $work = UnitOfWork::make();

    $post = new Post(['id' => 5, 'title' => 'new']);

    $work->update($post);

    $work->replace($post);
}

同一个实体不能被替换多次

php
public function testReplaceManyTimes(): void
{
    $this->expectException(\InvalidArgumentException::class);
    $this->expectExceptionMessage(
        'Entity `Tests\\Database\\Ddd\\Entity\\Relation\\Post` cannot be replaced for twice.'
    );

    $work = UnitOfWork::make();

    $connect = $this->createDatabaseConnect();

    $post = new Post(['title' => 'foo']);

    $work->replace($post);
    $work->replace($post);
}

已删除的实体不能够被替换

php
public function testReplaceButAlreadyInDeletes(): void
{
    $this->expectException(\InvalidArgumentException::class);
    $this->expectExceptionMessage(
        'Deleted entity `Tests\\Database\\Ddd\\Entity\\Relation\\Post` cannot be added for replace.'
    );

    $work = UnitOfWork::make();

    $post = new Post(['id' => 5, 'title' => 'new']);

    $work->delete($post);

    $work->replace($post);
}

同一个实体不能够被删除多次

php
public function testDeleteManyTimes(): void
{
    $this->expectException(\InvalidArgumentException::class);
    $this->expectExceptionMessage(
        'Entity `Tests\\Database\\Ddd\\Entity\\Relation\\Post` cannot be deleted for twice.'
    );

    $work = UnitOfWork::make();

    $connect = $this->createDatabaseConnect();

    $post = new Post(['id' => 1, 'title' => 'foo']);

    $work->delete($post);
    $work->delete($post);
}

不能多次创建同一个实体

php
public function testPersistAsCompositeIdReplace2(): void
{
    $work = UnitOfWork::make();

    $connect = $this->createDatabaseConnect();

    $compositeId = new CompositeId([
        'id1' => 1,
        'id2' => 2,
        'name' => 'old',
    ]);

    $work->persist($compositeId);

    $work->flush();

    self::assertSame(1, $connect->table('composite_id')->findCount());
}

persist 保持实体为替换支持复合主键

php
public function testPersistAsCompositeIdReplace(): void
{
    $work = UnitOfWork::make();

    $connect = $this->createDatabaseConnect();

    $compositeId = new CompositeId([
        'id1' => 1,
        'id2' => 2,
        'name' => 'old',
    ]);

    $work->persist($compositeId, 'replace');

    $work->flush();

    self::assertSame(1, $connect->table('composite_id')->findCount());
}

获取事务执行结果

php
public function testGetFlushResult(): void
{
    $work = UnitOfWork::make();

    $connect = $this->createDatabaseConnect();

    $compositeId = new CompositeId([
        'id1' => 1,
        'id2' => 2,
        'name' => 'old',
    ]);

    $work->persist($compositeId);

    $work->flush();

    self::assertSame(1, $connect->table('composite_id')->findCount());
    self::assertSame(0, $work->getFlushResult($compositeId));
}

实体可以为虚拟闭包

php
public function test1(): void
{
    $work = UnitOfWork::make();

    $connect = $this->createDatabaseConnect();

    $compositeId = new CompositeId([
        'id1' => 1,
        'id2' => 2,
        'name' => 'old',
    ]);
    $compositeCall = static function () use ($compositeId): mixed {
        return $compositeId->save()->flush();
    };

    $work->persist($compositeCall);

    $work->flush();

    self::assertSame(1, $connect->table('composite_id')->findCount());
    self::assertSame(0, $work->getFlushResult($compositeCall));
}