实体

Testing Is Documentation

tests/Database/Ddd/EntityTest.php实体 - 图1

实体是整个系统最为核心的基本单位,实体封装了一些常用的功能。

Uses

  1. <?php
  2. use I18nMock;
  3. use Leevel\Database\Condition;
  4. use Leevel\Di\Container;
  5. use Tests\Database\DatabaseTestCase as TestCase;
  6. use Tests\Database\Ddd\Entity\CompositeId;
  7. use Tests\Database\Ddd\Entity\DemoPropErrorEntity;
  8. use Tests\Database\Ddd\Entity\DemoVersion;
  9. use Tests\Database\Ddd\Entity\EntityWithEnum;
  10. use Tests\Database\Ddd\Entity\EntityWithEnum2;
  11. use Tests\Database\Ddd\Entity\EntityWithInvalidEnum;
  12. use Tests\Database\Ddd\Entity\EntityWithoutAnyField;
  13. use Tests\Database\Ddd\Entity\EntityWithoutPrimaryKey;
  14. use Tests\Database\Ddd\Entity\EntityWithoutPrimaryKeyNullInArray;
  15. use Tests\Database\Ddd\Entity\Relation\Post;
  16. use Tests\Database\Ddd\Entity\Relation\PostForReplace;
  17. use Tests\Database\Ddd\Entity\Relation\PostWithGetterSetterProp;
  18. use Tests\Database\Ddd\Entity\WithoutPrimarykey;
  19. use Tests\Database\Ddd\Entity\WithoutPrimarykeyAndAllAreKey;

withProps 批量设置属性数据

  1. public function testWithProps(): void
  2. {
  3. $entity = new Post();
  4. $entity->withProps([
  5. 'title' => 'foo',
  6. 'summary' => 'bar',
  7. ]);
  8. $this->assertSame('foo', $entity->title);
  9. $this->assertSame('bar', $entity->summary);
  10. $this->assertSame(['title', 'summary'], $entity->changed());
  11. }

enum 获取枚举

fixture 定义

Tests\Database\Ddd\Entity\EntityWithEnum

  1. namespace Tests\Database\Ddd\Entity;
  2. use Leevel\Database\Ddd\Entity;
  3. use Leevel\Database\Ddd\GetterSetter;
  4. class EntityWithEnum extends Entity
  5. {
  6. use GetterSetter;
  7. const TABLE = 'entity_with_enum';
  8. const ID = 'id';
  9. const AUTO = 'id';
  10. const STRUCT = [
  11. 'id' => [
  12. self::READONLY => true,
  13. ],
  14. 'title' => [],
  15. 'status' => [],
  16. ];
  17. const STATUS_ENUM = [
  18. 'disable' => [0, '禁用'],
  19. 'enable' => [1, '启用'],
  20. ];
  21. }
  1. public function testEntityWithEnum(): void
  2. {
  3. $this->initI18n();
  4. $entity = new EntityWithEnum([
  5. 'title' => 'foo',
  6. 'status' => '1',
  7. ]);
  8. $this->assertSame('foo', $entity->title);
  9. $this->assertSame('1', $entity->status);
  10. $data = <<<'eot'
  11. {
  12. "title": "foo"
  13. }
  14. eot;
  15. $this->assertSame(
  16. $data,
  17. $this->varJson(
  18. $entity->toArray(['title'])
  19. )
  20. );
  21. $data = <<<'eot'
  22. {
  23. "title": "foo",
  24. "status": "1",
  25. "status_enum": "启用"
  26. }
  27. eot;
  28. $this->assertSame(
  29. $data,
  30. $this->varJson(
  31. $entity->toArray(),
  32. 2
  33. )
  34. );
  35. $this->assertSame('启用', $entity->enum('status', '1'));
  36. $this->assertSame('禁用', $entity->enum('status', '0'));
  37. $this->assertFalse($entity->enum('not', '0'));
  38. $this->assertFalse($entity->enum('not'));
  39. $data = <<<'eot'
  40. [
  41. [
  42. 0,
  43. "禁用"
  44. ],
  45. [
  46. 1,
  47. "启用"
  48. ]
  49. ]
  50. eot;
  51. $this->assertSame(
  52. $data,
  53. $this->varJson(
  54. $entity->enum('status'),
  55. 3
  56. )
  57. );
  58. }

enum 获取枚举字符例子

fixture 定义

Tests\Database\Ddd\Entity\EntityWithEnum2

  1. namespace Tests\Database\Ddd\Entity;
  2. use Leevel\Database\Ddd\Entity;
  3. use Leevel\Database\Ddd\GetterSetter;
  4. class EntityWithEnum2 extends Entity
  5. {
  6. use GetterSetter;
  7. const TABLE = 'entity_with_enum';
  8. const ID = 'id';
  9. const AUTO = 'id';
  10. const STRUCT = [
  11. 'id' => [
  12. self::READONLY => true,
  13. ],
  14. 'title' => [],
  15. 'status' => [],
  16. ];
  17. const STATUS_ENUM = [
  18. 'disable' => ['f', '禁用'],
  19. 'enable' => ['t', '启用'],
  20. ];
  21. }
  1. public function testEntityWithEnum2(): void
  2. {
  3. $this->initI18n();
  4. $entity = new EntityWithEnum2([
  5. 'title' => 'foo',
  6. 'status' => 't',
  7. ]);
  8. $data = <<<'eot'
  9. [
  10. [
  11. "f",
  12. "禁用"
  13. ],
  14. [
  15. "t",
  16. "启用"
  17. ]
  18. ]
  19. eot;
  20. $this->assertSame(
  21. $data,
  22. $this->varJson(
  23. $entity->enum('status')
  24. )
  25. );
  26. }

hasChanged 检测属性是否已经改变

  1. public function testHasChanged(): void
  2. {
  3. $entity = new Post();
  4. $this->assertFalse($entity->hasChanged('title'));
  5. $entity->title = 'change';
  6. $this->assertTrue($entity->hasChanged('title'));
  7. }

addChanged 添加指定属性为已改变

  1. public function testAddChanged(): void
  2. {
  3. $entity = new Post();
  4. $data = <<<'eot'
  5. []
  6. eot;
  7. $this->assertSame(
  8. $data,
  9. $this->varJson(
  10. $entity->changed()
  11. )
  12. );
  13. $entity->addChanged(['user_id', 'title']);
  14. $data = <<<'eot'
  15. [
  16. "user_id",
  17. "title"
  18. ]
  19. eot;
  20. $this->assertSame(
  21. $data,
  22. $this->varJson(
  23. $entity->changed(),
  24. 1
  25. )
  26. );
  27. }

deleteChanged 删除已改变属性

  1. public function testDeleteChanged(): void
  2. {
  3. $entity = new Post();
  4. $data = <<<'eot'
  5. []
  6. eot;
  7. $this->assertSame(
  8. $data,
  9. $this->varJson(
  10. $entity->changed()
  11. )
  12. );
  13. $entity->addChanged(['user_id', 'title']);
  14. $data = <<<'eot'
  15. [
  16. "user_id",
  17. "title"
  18. ]
  19. eot;
  20. $this->assertSame(
  21. $data,
  22. $this->varJson(
  23. $entity->changed(),
  24. 1,
  25. )
  26. );
  27. $entity->deleteChanged(['user_id']);
  28. $data = <<<'eot'
  29. [
  30. "title"
  31. ]
  32. eot;
  33. $this->assertSame(
  34. $data,
  35. $this->varJson(
  36. $entity->changed(),
  37. 2,
  38. )
  39. );
  40. }

clearChanged 清空已改变属性

  1. public function testClearChanged(): void
  2. {
  3. $entity = new Post();
  4. $data = <<<'eot'
  5. []
  6. eot;
  7. $this->assertSame(
  8. $data,
  9. $this->varJson(
  10. $entity->changed()
  11. )
  12. );
  13. $entity->addChanged(['user_id', 'title']);
  14. $data = <<<'eot'
  15. [
  16. "user_id",
  17. "title"
  18. ]
  19. eot;
  20. $this->assertSame(
  21. $data,
  22. $this->varJson(
  23. $entity->changed(),
  24. 1,
  25. )
  26. );
  27. $entity->clearChanged(['user_id']);
  28. $data = <<<'eot'
  29. []
  30. eot;
  31. $this->assertSame(
  32. $data,
  33. $this->varJson(
  34. $entity->changed(),
  35. 2,
  36. )
  37. );
  38. }

singleId 返回供查询的主键字段值

  1. public function testSingleId(): void
  2. {
  3. $entity = new Post();
  4. $this->assertFalse($entity->singleId());
  5. $entity = new Post(['id' => 5]);
  6. $this->assertSame(5, $entity->singleId());
  7. }

idCondition 获取查询主键条件

  1. public function testIdCondition(): void
  2. {
  3. $entity = new Post(['id' => 5]);
  4. $this->assertSame(['id' => 5], $entity->idCondition());
  5. }

实体属性数组访问 ArrayAccess.offsetExists 支持

  1. public function testArrayAccessOffsetExists(): void
  2. {
  3. $entity = new Post(['id' => 5, 'title' => 'hello']);
  4. $this->assertTrue(isset($entity['title']));
  5. $this->assertFalse(isset($entity['user_id']));
  6. }

实体属性数组访问 ArrayAccess.offsetSet 支持

  1. public function testArrayAccessOffsetSet(): void
  2. {
  3. $entity = new Post(['id' => 5]);
  4. $this->assertFalse(isset($entity['title']));
  5. $this->assertNull($entity->title);
  6. $entity['title'] = 'world';
  7. $this->assertTrue(isset($entity['title']));
  8. $this->assertSame('world', $entity->title);
  9. }

实体属性数组访问 ArrayAccess.offsetGet 支持

  1. public function testArrayAccessOffsetGet(): void
  2. {
  3. $entity = new Post(['id' => 5]);
  4. $this->assertNull($entity['title']);
  5. $entity['title'] = 'world';
  6. $this->assertSame('world', $entity['title']);
  7. }

实体属性数组访问 ArrayAccess.offsetUnset 支持

  1. public function testArrayAccessOffsetUnset(): void
  2. {
  3. $entity = new Post(['id' => 5]);
  4. $this->assertNull($entity['title']);
  5. $entity['title'] = 'world';
  6. $this->assertSame('world', $entity['title']);
  7. unset($entity['title']);
  8. $this->assertNull($entity['title']);
  9. }

实体属性访问魔术方法 __isset 支持

  1. public function testMagicIsset(): void
  2. {
  3. $entity = new Post(['id' => 5, 'title' => 'hello']);
  4. $this->assertTrue(isset($entity->title));
  5. $this->assertFalse(isset($entity->userId));
  6. }

实体属性访问魔术方法 __set 支持

  1. public function testMagicSet(): void
  2. {
  3. $entity = new Post(['id' => 5]);
  4. $this->assertFalse(isset($entity->title));
  5. $this->assertNull($entity->title);
  6. $entity->title = 'world';
  7. $this->assertTrue(isset($entity->title));
  8. $this->assertSame('world', $entity->title);
  9. }

实体属性访问魔术方法 __get 支持

  1. public function testMagicGet(): void
  2. {
  3. $entity = new Post(['id' => 5]);
  4. $this->assertNull($entity->title);
  5. $entity->title = 'world';
  6. $this->assertSame('world', $entity->title);
  7. }

实体属性访问魔术方法 __unset 支持

  1. public function testMagicUnset(): void
  2. {
  3. $entity = new Post(['id' => 5]);
  4. $this->assertNull($entity->title);
  5. $entity->title = 'world';
  6. $this->assertSame('world', $entity->title);
  7. unset($entity->title);
  8. $this->assertNull($entity->title);
  9. }

setter 设置属性值

  1. public function testCallSetter(): void
  2. {
  3. $entity = new Post(['id' => 5]);
  4. $this->assertNull($entity->title);
  5. $this->assertNull($entity->userId);
  6. $entity->setTitle('hello');
  7. $entity->setUserId(5);
  8. $this->assertSame('hello', $entity->title);
  9. $this->assertSame(5, $entity->userId);
  10. }

getter 获取属性值

  1. public function testCallGetter(): void
  2. {
  3. $entity = new Post(['id' => 5]);
  4. $this->assertNull($entity->getTitle());
  5. $this->assertNull($entity->getUserId());
  6. $entity->setTitle('hello');
  7. $entity->setUserId(5);
  8. $this->assertSame('hello', $entity->getTitle());
  9. $this->assertSame(5, $entity->getUserId());
  10. }

find 获取实体查询对象

  1. public function testStaticFind(): 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' => 'post summary',
  12. 'delete_at' => 0,
  13. ])
  14. );
  15. $post = Post::find()->where('id', 1)->findOne();
  16. $this->assertSame('hello world', $post->title);
  17. $this->assertSame(1, $post->userId);
  18. $this->assertSame('post summary', $post->summary);
  19. }

connectSandbox 数据库连接沙盒

  1. public function testConnectSandbox(): 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' => 'post summary',
  12. 'delete_at' => 0,
  13. ])
  14. );
  15. $post = Post::connectSandbox('password_right', function () {
  16. return Post::find()->where('id', 1)->findOne();
  17. });
  18. $this->assertSame('hello world', $post->title);
  19. $this->assertSame(1, $post->userId);
  20. $this->assertSame('post summary', $post->summary);
  21. }

newed 确定对象是否对应数据库中的一条记录

  1. public function testNewed(): void
  2. {
  3. $entity = new Post();
  4. $this->assertTrue($entity->newed());
  5. $entity = new Post(['id' => 5]);
  6. $this->assertTrue($entity->newed());
  7. $entity = new Post(['id' => 5], true);
  8. $this->assertFalse($entity->newed());
  9. }

withNewed 设置确定对象是否对应数据库中的一条记录

  1. public function testWithNewed(): void
  2. {
  3. $entity = new Post();
  4. $this->assertTrue($entity->newed());
  5. $entity->withNewed(false);
  6. $this->assertFalse($entity->newed());
  7. $entity = new Post(['id' => 5]);
  8. $this->assertTrue($entity->newed());
  9. $entity->withNewed(false);
  10. $this->assertFalse($entity->newed());
  11. $entity = new Post(['id' => 5], true);
  12. $this->assertFalse($entity->newed());
  13. $entity->withNewed(true);
  14. $this->assertTrue($entity->newed());
  15. }

original 获取原始数据

  1. public function testOriginal(): void
  2. {
  3. $entity = new Post();
  4. $this->assertSame([], $entity->original());
  5. $entity = new Post($data = [
  6. 'title' => 'hello',
  7. 'summary' => 'world',
  8. 'foo' => 'bar',
  9. ], false, true);
  10. $this->assertSame($data, $entity->original());
  11. $this->assertSame('hello', $entity->title);
  12. $this->assertSame('world', $entity->summary);
  13. }

id 获取主键值

  1. public function testId(): void
  2. {
  3. $entity = new Post();
  4. $this->assertFalse($entity->id());
  5. $entity = new Post(['id' => 5]);
  6. $this->assertSame(5, $entity->id());
  7. }

id 获取复合主键值

fixture 定义

Tests\Database\Ddd\Entity\CompositeId

  1. namespace Tests\Database\Ddd\Entity;
  2. use Leevel\Database\Ddd\Entity;
  3. use Leevel\Database\Ddd\GetterSetter;
  4. class CompositeId extends Entity
  5. {
  6. use GetterSetter;
  7. const TABLE = 'composite_id';
  8. const ID = ['id1', 'id2'];
  9. const AUTO = null;
  10. const STRUCT = [
  11. 'id1' => [],
  12. 'id2' => [],
  13. 'name' => [],
  14. ];
  15. }
  1. public function testCompositeId(): void
  2. {
  3. $entity = new CompositeId();
  4. $this->assertFalse($entity->id(false));
  5. $this->assertFalse($entity->id());
  6. $entity = new CompositeId(['id1' => 5]);
  7. $this->assertFalse($entity->id(false));
  8. $this->assertFalse($entity->id());
  9. $entity = new CompositeId(['id1' => 5, 'id2' => 8]);
  10. $this->assertSame(['id1' => 5, 'id2' => 8], $entity->id(false));
  11. $this->assertSame(['id1' => 5, 'id2' => 8], $entity->id());
  12. }

refresh 从数据库重新读取当前对象的属性

  1. public function testRefresh(): void
  2. {
  3. $post1 = new Post();
  4. $post1->create()->flush();
  5. $this->assertInstanceof(Post::class, $post1);
  6. $this->assertSame(1, $post1->id);
  7. $this->assertNull($post1->userId);
  8. $this->assertNull($post1->title);
  9. $this->assertNull($post1->summary);
  10. $this->assertNull($post1->delete_at);
  11. $post1->refresh();
  12. $this->assertSame(1, $post1->id);
  13. $this->assertSame(0, $post1->userId);
  14. $this->assertSame('', $post1->title);
  15. $this->assertSame('', $post1->summary);
  16. $this->assertSame(0, $post1->delete_at);
  17. }

refresh 从数据库重新读取当前对象的属性支持复合主键

  1. public function testRefreshWithCompositeId(): void
  2. {
  3. $entity = new CompositeId(['id1' => 1, 'id2' => 3]);
  4. $entity->create()->flush();
  5. $this->assertInstanceof(CompositeId::class, $entity);
  6. $this->assertSame(1, $entity->id1);
  7. $this->assertSame(3, $entity->id2);
  8. $this->assertNull($entity->name);
  9. $entity->refresh();
  10. $this->assertSame(1, $entity->id1);
  11. $this->assertSame(3, $entity->id2);
  12. $this->assertSame('', $entity->name);
  13. }

构造器支持忽略未定义属性

$ignoreUndefinedProp 用于数据库添加了字段,但是我们的实体并没有更新字段,查询得到的实体对象将会忽略掉新增的字段而不报错。

  1. public function testIgnoreUndefinedProp(): void
  2. {
  3. $entity = new Post(['undefined_prop' => 5], true, true);
  4. $this->assertSame([], $entity->toArray());
  5. }

update 更新数据带上版本号

可以用于并发控制,例如商品库存,客户余额等。

fixture 定义

Tests\Database\Ddd\Entity\DemoVersion

  1. namespace Tests\Database\Ddd\Entity;
  2. use Leevel\Database\Ddd\Entity;
  3. use Leevel\Database\Ddd\GetterSetter;
  4. class DemoVersion extends Entity
  5. {
  6. use GetterSetter;
  7. const TABLE = 'test_version';
  8. const ID = 'id';
  9. const AUTO = 'id';
  10. const STRUCT = [
  11. 'id' => [
  12. self::READONLY => true,
  13. ],
  14. 'name' => [],
  15. 'available_number' => [],
  16. 'real_number' => [],
  17. 'version' => [],
  18. ];
  19. const VERSION = 'version';
  20. protected bool $version = true;
  21. }
  1. public function testUpdateWithVersion(): void
  2. {
  3. $connect = $this->createDatabaseConnect();
  4. $this->assertSame(
  5. 1,
  6. $connect
  7. ->table('test_version')
  8. ->insert([
  9. 'name' => 'xiaoniuge',
  10. ])
  11. );
  12. $testVersion = DemoVersion::select()->findEntity(1);
  13. $this->assertInstanceof(DemoVersion::class, $testVersion);
  14. $this->assertSame(1, $testVersion->id);
  15. $this->assertSame('xiaoniuge', $testVersion->name);
  16. $this->assertSame('0.0000', $testVersion->availableNumber);
  17. $this->assertSame('0.0000', $testVersion->realNumber);
  18. $condition = [
  19. 'available_number' => $testVersion->availableNumber,
  20. 'real_number' => $testVersion->realNumber,
  21. ];
  22. $testVersion->name = 'aniu';
  23. $testVersion->availableNumber = Condition::raw('[available_number]+1');
  24. $testVersion->realNumber = Condition::raw('[real_number]+3');
  25. $this->assertSame(
  26. 1,
  27. $testVersion
  28. ->condition($condition)
  29. ->update()
  30. ->flush()
  31. );
  32. $this->assertSame('SQL: [499] UPDATE `test_version` SET `test_version`.`name` = :pdonamedparameter_name,`test_version`.`available_number` = `test_version`.`available_number`+1,`test_version`.`real_number` = `test_version`.`real_number`+3,`test_version`.`version` = `test_version`.`version`+1 WHERE `test_version`.`available_number` = :test_version_available_number AND `test_version`.`real_number` = :test_version_real_number AND `test_version`.`id` = :test_version_id AND `test_version`.`version` = :test_version_version LIMIT 1 | Params: 5 | Key: Name: [23] :pdonamedparameter_name | paramno=0 | name=[23] ":pdonamedparameter_name" | is_param=1 | param_type=2 | Key: Name: [30] :test_version_available_number | paramno=1 | name=[30] ":test_version_available_number" | is_param=1 | param_type=2 | Key: Name: [25] :test_version_real_number | paramno=2 | name=[25] ":test_version_real_number" | is_param=1 | param_type=2 | Key: Name: [16] :test_version_id | paramno=3 | name=[16] ":test_version_id" | is_param=1 | param_type=1 | Key: Name: [21] :test_version_version | paramno=4 | name=[21] ":test_version_version" | is_param=1 | param_type=1 (UPDATE `test_version` SET `test_version`.`name` = \'aniu\',`test_version`.`available_number` = `test_version`.`available_number`+1,`test_version`.`real_number` = `test_version`.`real_number`+3,`test_version`.`version` = `test_version`.`version`+1 WHERE `test_version`.`available_number` = \'0.0000\' AND `test_version`.`real_number` = \'0.0000\' AND `test_version`.`id` = 1 AND `test_version`.`version` = 0 LIMIT 1)', $testVersion->select()->getLastSql());
  33. $testVersion->name = 'hello';
  34. $this->assertSame(1, $testVersion->update()->flush());
  35. $this->assertSame('SQL: [233] UPDATE `test_version` SET `test_version`.`version` = `test_version`.`version`+1,`test_version`.`name` = :pdonamedparameter_name WHERE `test_version`.`id` = :test_version_id AND `test_version`.`version` = :test_version_version LIMIT 1 | Params: 3 | Key: Name: [23] :pdonamedparameter_name | paramno=0 | name=[23] ":pdonamedparameter_name" | is_param=1 | param_type=2 | Key: Name: [16] :test_version_id | paramno=1 | name=[16] ":test_version_id" | is_param=1 | param_type=1 | Key: Name: [21] :test_version_version | paramno=2 | name=[21] ":test_version_version" | is_param=1 | param_type=1 (UPDATE `test_version` SET `test_version`.`version` = `test_version`.`version`+1,`test_version`.`name` = \'hello\' WHERE `test_version`.`id` = 1 AND `test_version`.`version` = 1 LIMIT 1)', $testVersion->select()->getLastSql());
  36. }

update 更新数据不含版本数据则不会带上版本号

version 对应的字段无数据,将会忽略版本号。

  1. public function testUpdateNoVersionDataWithoutVersion(): void
  2. {
  3. $connect = $this->createDatabaseConnect();
  4. $this->assertSame(
  5. 1,
  6. $connect
  7. ->table('test_version')
  8. ->insert([
  9. 'name' => 'xiaoniuge',
  10. ])
  11. );
  12. $testVersion = DemoVersion::select()
  13. ->findEntity(1, ['id,name,available_number,real_number']);
  14. $this->assertInstanceof(DemoVersion::class, $testVersion);
  15. $this->assertSame(1, $testVersion->id);
  16. $this->assertNull($testVersion->version);
  17. $this->assertSame('xiaoniuge', $testVersion->name);
  18. $this->assertSame('0.0000', $testVersion->availableNumber);
  19. $this->assertSame('0.0000', $testVersion->realNumber);
  20. $condition = [
  21. 'available_number' => $testVersion->availableNumber,
  22. 'real_number' => $testVersion->realNumber,
  23. ];
  24. $testVersion->name = 'aniu';
  25. $testVersion->availableNumber = Condition::raw('[available_number]+1');
  26. $testVersion->realNumber = Condition::raw('[real_number]+3');
  27. $this->assertSame(
  28. 1,
  29. $testVersion
  30. ->condition($condition)
  31. ->update()
  32. ->flush()
  33. );
  34. $this->assertSame('SQL: [392] UPDATE `test_version` SET `test_version`.`name` = :pdonamedparameter_name,`test_version`.`available_number` = `test_version`.`available_number`+1,`test_version`.`real_number` = `test_version`.`real_number`+3 WHERE `test_version`.`available_number` = :test_version_available_number AND `test_version`.`real_number` = :test_version_real_number AND `test_version`.`id` = :test_version_id LIMIT 1 | Params: 4 | Key: Name: [23] :pdonamedparameter_name | paramno=0 | name=[23] ":pdonamedparameter_name" | is_param=1 | param_type=2 | Key: Name: [30] :test_version_available_number | paramno=1 | name=[30] ":test_version_available_number" | is_param=1 | param_type=2 | Key: Name: [25] :test_version_real_number | paramno=2 | name=[25] ":test_version_real_number" | is_param=1 | param_type=2 | Key: Name: [16] :test_version_id | paramno=3 | name=[16] ":test_version_id" | is_param=1 | param_type=1 (UPDATE `test_version` SET `test_version`.`name` = \'aniu\',`test_version`.`available_number` = `test_version`.`available_number`+1,`test_version`.`real_number` = `test_version`.`real_number`+3 WHERE `test_version`.`available_number` = \'0.0000\' AND `test_version`.`real_number` = \'0.0000\' AND `test_version`.`id` = 1 LIMIT 1)', $testVersion->select()->getLastSql());
  35. $testVersion->name = 'hello';
  36. $this->assertSame(1, $testVersion->update()->flush());
  37. $this->assertSame('SQL: [126] UPDATE `test_version` SET `test_version`.`name` = :pdonamedparameter_name WHERE `test_version`.`id` = :test_version_id LIMIT 1 | Params: 2 | Key: Name: [23] :pdonamedparameter_name | paramno=0 | name=[23] ":pdonamedparameter_name" | is_param=1 | param_type=2 | Key: Name: [16] :test_version_id | paramno=1 | name=[16] ":test_version_id" | is_param=1 | param_type=1 (UPDATE `test_version` SET `test_version`.`name` = \'hello\' WHERE `test_version`.`id` = 1 LIMIT 1)', $testVersion->select()->getLastSql());
  38. }

version.condition 设置是否启用乐观锁版本字段配合设置扩展查询条件

  1. public function testUpdateWithVersionAndWithCondition(): void
  2. {
  3. $connect = $this->createDatabaseConnect();
  4. $this->assertSame(
  5. 1,
  6. $connect
  7. ->table('test_version')
  8. ->insert([
  9. 'name' => 'xiaoniuge',
  10. ])
  11. );
  12. $testVersion = DemoVersion::select()->findEntity(1);
  13. $this->assertInstanceof(DemoVersion::class, $testVersion);
  14. $this->assertSame(1, $testVersion->id);
  15. $this->assertSame('xiaoniuge', $testVersion->name);
  16. $this->assertSame('0.0000', $testVersion->availableNumber);
  17. $this->assertSame('0.0000', $testVersion->realNumber);
  18. $testVersion->name = 'aniu';
  19. $testVersion->availableNumber = Condition::raw('[available_number]+1');
  20. $testVersion->realNumber = Condition::raw('[real_number]+3');
  21. $this->assertSame(1, $testVersion->version(true)->update()->flush());
  22. $this->assertSame('SQL: [367] UPDATE `test_version` SET `test_version`.`name` = :pdonamedparameter_name,`test_version`.`available_number` = `test_version`.`available_number`+1,`test_version`.`real_number` = `test_version`.`real_number`+3,`test_version`.`version` = `test_version`.`version`+1 WHERE `test_version`.`id` = :test_version_id AND `test_version`.`version` = :test_version_version LIMIT 1 | Params: 3 | Key: Name: [23] :pdonamedparameter_name | paramno=0 | name=[23] ":pdonamedparameter_name" | is_param=1 | param_type=2 | Key: Name: [16] :test_version_id | paramno=1 | name=[16] ":test_version_id" | is_param=1 | param_type=1 | Key: Name: [21] :test_version_version | paramno=2 | name=[21] ":test_version_version" | is_param=1 | param_type=1 (UPDATE `test_version` SET `test_version`.`name` = \'aniu\',`test_version`.`available_number` = `test_version`.`available_number`+1,`test_version`.`real_number` = `test_version`.`real_number`+3,`test_version`.`version` = `test_version`.`version`+1 WHERE `test_version`.`id` = 1 AND `test_version`.`version` = 0 LIMIT 1)', $testVersion->select()->getLastSql());
  23. $testVersion->refresh();
  24. $condition = ['available_number' => $testVersion->availableNumber];
  25. $testVersion->name = 'hello';
  26. $testVersion->availableNumber = Condition::raw('[available_number]+8');
  27. $this->assertSame(1, $testVersion->condition($condition)->update()->flush());
  28. $this->assertSame('SQL: [376] UPDATE `test_version` SET `test_version`.`version` = `test_version`.`version`+1,`test_version`.`name` = :pdonamedparameter_name,`test_version`.`available_number` = `test_version`.`available_number`+8 WHERE `test_version`.`available_number` = :test_version_available_number AND `test_version`.`id` = :test_version_id AND `test_version`.`version` = :test_version_version LIMIT 1 | Params: 4 | Key: Name: [23] :pdonamedparameter_name | paramno=0 | name=[23] ":pdonamedparameter_name" | is_param=1 | param_type=2 | Key: Name: [30] :test_version_available_number | paramno=1 | name=[30] ":test_version_available_number" | is_param=1 | param_type=2 | Key: Name: [16] :test_version_id | paramno=2 | name=[16] ":test_version_id" | is_param=1 | param_type=1 | Key: Name: [21] :test_version_version | paramno=3 | name=[21] ":test_version_version" | is_param=1 | param_type=1 (UPDATE `test_version` SET `test_version`.`version` = `test_version`.`version`+1,`test_version`.`name` = \'hello\',`test_version`.`available_number` = `test_version`.`available_number`+8 WHERE `test_version`.`available_number` = \'1.0000\' AND `test_version`.`id` = 1 AND `test_version`.`version` = 1 LIMIT 1)', $testVersion->select()->getLastSql());
  29. }

version 设置是否启用乐观锁版本字段支持取消

  1. public function testUpdateWithVersionAndWithoutVersionCondition(): void
  2. {
  3. $connect = $this->createDatabaseConnect();
  4. $this->assertSame(
  5. 1,
  6. $connect
  7. ->table('test_version')
  8. ->insert([
  9. 'name' => 'xiaoniuge',
  10. ])
  11. );
  12. $testVersion = DemoVersion::select()->findEntity(1);
  13. $this->assertInstanceof(DemoVersion::class, $testVersion);
  14. $this->assertSame(1, $testVersion->id);
  15. $this->assertSame('xiaoniuge', $testVersion->name);
  16. $this->assertSame('0.0000', $testVersion->availableNumber);
  17. $this->assertSame('0.0000', $testVersion->realNumber);
  18. $testVersion->name = 'aniu';
  19. $testVersion->availableNumber = Condition::raw('[available_number]+1');
  20. $testVersion->realNumber = Condition::raw('[real_number]+3');
  21. $this->assertSame(1, $testVersion->version(false)->update()->flush());
  22. $this->assertSame('SQL: [260] UPDATE `test_version` SET `test_version`.`name` = :pdonamedparameter_name,`test_version`.`available_number` = `test_version`.`available_number`+1,`test_version`.`real_number` = `test_version`.`real_number`+3 WHERE `test_version`.`id` = :test_version_id LIMIT 1 | Params: 2 | Key: Name: [23] :pdonamedparameter_name | paramno=0 | name=[23] ":pdonamedparameter_name" | is_param=1 | param_type=2 | Key: Name: [16] :test_version_id | paramno=1 | name=[16] ":test_version_id" | is_param=1 | param_type=1 (UPDATE `test_version` SET `test_version`.`name` = \'aniu\',`test_version`.`available_number` = `test_version`.`available_number`+1,`test_version`.`real_number` = `test_version`.`real_number`+3 WHERE `test_version`.`id` = 1 LIMIT 1)', $testVersion->select()->getLastSql());
  23. $testVersion->name = 'hello';
  24. $this->assertSame(1, $testVersion->update()->flush());
  25. $this->assertSame('SQL: [126] UPDATE `test_version` SET `test_version`.`name` = :pdonamedparameter_name WHERE `test_version`.`id` = :test_version_id LIMIT 1 | Params: 2 | Key: Name: [23] :pdonamedparameter_name | paramno=0 | name=[23] ":pdonamedparameter_name" | is_param=1 | param_type=2 | Key: Name: [16] :test_version_id | paramno=1 | name=[16] ":test_version_id" | is_param=1 | param_type=1 (UPDATE `test_version` SET `test_version`.`name` = \'hello\' WHERE `test_version`.`id` = 1 LIMIT 1)', $testVersion->select()->getLastSql());
  26. }

condition 设置扩展查询条件支持直接设置版本查询条件

  1. public function testUpdateWithCondition(): void
  2. {
  3. $connect = $this->createDatabaseConnect();
  4. $this->assertSame(
  5. 1,
  6. $connect
  7. ->table('test_version')
  8. ->insert([
  9. 'name' => 'xiaoniuge',
  10. ])
  11. );
  12. $testVersion = DemoVersion::select()->findEntity(1);
  13. $this->assertInstanceof(DemoVersion::class, $testVersion);
  14. $this->assertSame(1, $testVersion->id);
  15. $this->assertSame('xiaoniuge', $testVersion->name);
  16. $this->assertSame('0.0000', $testVersion->availableNumber);
  17. $this->assertSame('0.0000', $testVersion->realNumber);
  18. $testVersion->name = 'aniu';
  19. $testVersion->availableNumber = Condition::raw('[available_number]+1');
  20. $testVersion->realNumber = Condition::raw('[real_number]+3');
  21. $this->assertSame(1, $testVersion->version(true)->update()->flush());
  22. $this->assertSame('SQL: [367] UPDATE `test_version` SET `test_version`.`name` = :pdonamedparameter_name,`test_version`.`available_number` = `test_version`.`available_number`+1,`test_version`.`real_number` = `test_version`.`real_number`+3,`test_version`.`version` = `test_version`.`version`+1 WHERE `test_version`.`id` = :test_version_id AND `test_version`.`version` = :test_version_version LIMIT 1 | Params: 3 | Key: Name: [23] :pdonamedparameter_name | paramno=0 | name=[23] ":pdonamedparameter_name" | is_param=1 | param_type=2 | Key: Name: [16] :test_version_id | paramno=1 | name=[16] ":test_version_id" | is_param=1 | param_type=1 | Key: Name: [21] :test_version_version | paramno=2 | name=[21] ":test_version_version" | is_param=1 | param_type=1 (UPDATE `test_version` SET `test_version`.`name` = \'aniu\',`test_version`.`available_number` = `test_version`.`available_number`+1,`test_version`.`real_number` = `test_version`.`real_number`+3,`test_version`.`version` = `test_version`.`version`+1 WHERE `test_version`.`id` = 1 AND `test_version`.`version` = 0 LIMIT 1)', $testVersion->select()->getLastSql());
  23. $testVersion->refresh();
  24. $condition = ['available_number' => $testVersion->availableNumber, DemoVersion::VERSION => 9999];
  25. $testVersion->name = 'hello';
  26. $testVersion->availableNumber = Condition::raw('[available_number]+8');
  27. $this->assertSame(0, $testVersion->condition($condition)->update()->flush());
  28. $this->assertSame('SQL: [376] UPDATE `test_version` SET `test_version`.`version` = `test_version`.`version`+1,`test_version`.`name` = :pdonamedparameter_name,`test_version`.`available_number` = `test_version`.`available_number`+8 WHERE `test_version`.`available_number` = :test_version_available_number AND `test_version`.`version` = :test_version_version AND `test_version`.`id` = :test_version_id LIMIT 1 | Params: 4 | Key: Name: [23] :pdonamedparameter_name | paramno=0 | name=[23] ":pdonamedparameter_name" | is_param=1 | param_type=2 | Key: Name: [30] :test_version_available_number | paramno=1 | name=[30] ":test_version_available_number" | is_param=1 | param_type=2 | Key: Name: [21] :test_version_version | paramno=2 | name=[21] ":test_version_version" | is_param=1 | param_type=1 | Key: Name: [16] :test_version_id | paramno=3 | name=[16] ":test_version_id" | is_param=1 | param_type=1 (UPDATE `test_version` SET `test_version`.`version` = `test_version`.`version`+1,`test_version`.`name` = \'hello\',`test_version`.`available_number` = `test_version`.`available_number`+8 WHERE `test_version`.`available_number` = \'1.0000\' AND `test_version`.`version` = 9999 AND `test_version`.`id` = 1 LIMIT 1)', $testVersion->select()->getLastSql());
  29. }

实体设置虚拟主键可以解决没有主键的表数据更新问题

fixture 定义

without_primarykey

  1. CREATE TABLE `without_primarykey` (
  2. `goods_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '商品 ID',
  3. `description` varchar(255) NOT NULL DEFAULT '' COMMENT '商品描述',
  4. `name` varchar(100) NOT NULL DEFAULT '' COMMENT '商品名称'
  5. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='没有主键的表';

Tests\Database\Ddd\Entity\WithoutPrimarykey

  1. namespace Tests\Database\Ddd\Entity;
  2. use Leevel\Database\Ddd\Entity;
  3. use Leevel\Database\Ddd\GetterSetter;
  4. class WithoutPrimarykey extends Entity
  5. {
  6. use GetterSetter;
  7. const TABLE = 'without_primarykey';
  8. const ID = 'goods_id';
  9. const AUTO = null;
  10. const STRUCT = [
  11. 'goods_id' => [
  12. self::READONLY => true,
  13. ],
  14. 'description' => [],
  15. 'name' => [],
  16. ];
  17. }
  1. public function testUpdateWithoutPrimarykey(): void
  2. {
  3. $connect = $this->createDatabaseConnect();
  4. $this->assertSame(
  5. 1,
  6. $connect
  7. ->table('without_primarykey')
  8. ->insert([
  9. 'goods_id' => 1,
  10. 'description' => 'hello',
  11. ])
  12. );
  13. $withoutPrimarykey = WithoutPrimarykey::select()->findEntity(1);
  14. $this->assertSame('goods_id', WithoutPrimarykey::primaryKey());
  15. $this->assertInstanceof(WithoutPrimarykey::class, $withoutPrimarykey);
  16. $this->assertSame(1, $withoutPrimarykey->goodsId);
  17. $this->assertSame('hello', $withoutPrimarykey->description);
  18. $withoutPrimarykey->description = 'world';
  19. $this->assertSame(1, $withoutPrimarykey->update()->flush());
  20. $this->assertSame('SQL: [176] UPDATE `without_primarykey` SET `without_primarykey`.`description` = :pdonamedparameter_description WHERE `without_primarykey`.`goods_id` = :without_primarykey_goods_id LIMIT 1 | Params: 2 | Key: Name: [30] :pdonamedparameter_description | paramno=0 | name=[30] ":pdonamedparameter_description" | is_param=1 | param_type=2 | Key: Name: [28] :without_primarykey_goods_id | paramno=1 | name=[28] ":without_primarykey_goods_id" | is_param=1 | param_type=1 (UPDATE `without_primarykey` SET `without_primarykey`.`description` = \'world\' WHERE `without_primarykey`.`goods_id` = 1 LIMIT 1)', $withoutPrimarykey->select()->getLastSql());
  21. }

实体未设置主键所有非关联字段将变为虚拟主键

fixture 定义

without_primarykey

  1. CREATE TABLE `without_primarykey` (
  2. `goods_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '商品 ID',
  3. `description` varchar(255) NOT NULL DEFAULT '' COMMENT '商品描述',
  4. `name` varchar(100) NOT NULL DEFAULT '' COMMENT '商品名称'
  5. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='没有主键的表';

Tests\Database\Ddd\Entity\WithoutPrimarykeyAndAllAreKey

  1. namespace Tests\Database\Ddd\Entity;
  2. use Leevel\Database\Ddd\Entity;
  3. use Leevel\Database\Ddd\GetterSetter;
  4. class WithoutPrimarykeyAndAllAreKey extends Entity
  5. {
  6. use GetterSetter;
  7. const TABLE = 'without_primarykey';
  8. const ID = null;
  9. const AUTO = null;
  10. const STRUCT = [
  11. 'goods_id' => [],
  12. 'description' => [],
  13. 'name' => [],
  14. ];
  15. }
  1. public function testUpdateWithoutPrimarykeyAndAllAreKey(): void
  2. {
  3. $connect = $this->createDatabaseConnect();
  4. $this->assertSame(
  5. 1,
  6. $connect
  7. ->table('without_primarykey')
  8. ->insert([
  9. 'goods_id' => 1,
  10. 'description' => 'hello',
  11. 'name' => 'world',
  12. ])
  13. );
  14. $withoutPrimarykey = WithoutPrimarykeyAndAllAreKey::select()->findOne();
  15. $this->assertSame(['goods_id', 'description', 'name'], WithoutPrimarykeyAndAllAreKey::primaryKey());
  16. $this->assertInstanceof(WithoutPrimarykeyAndAllAreKey::class, $withoutPrimarykey);
  17. $this->assertSame(1, $withoutPrimarykey->goodsId);
  18. $this->assertSame('hello', $withoutPrimarykey->description);
  19. $this->assertSame('world', $withoutPrimarykey->name);
  20. $withoutPrimarykey->description = 'my';
  21. $withoutPrimarykey->name = 'php';
  22. $this->assertSame(1, $withoutPrimarykey->update()->flush());
  23. $this->assertSame('SQL: [362] UPDATE `without_primarykey` SET `without_primarykey`.`description` = :pdonamedparameter_description,`without_primarykey`.`name` = :pdonamedparameter_name WHERE `without_primarykey`.`goods_id` = :without_primarykey_goods_id AND `without_primarykey`.`description` = :without_primarykey_description AND `without_primarykey`.`name` = :without_primarykey_name LIMIT 1 | Params: 5 | Key: Name: [30] :pdonamedparameter_description | paramno=0 | name=[30] ":pdonamedparameter_description" | is_param=1 | param_type=2 | Key: Name: [23] :pdonamedparameter_name | paramno=1 | name=[23] ":pdonamedparameter_name" | is_param=1 | param_type=2 | Key: Name: [28] :without_primarykey_goods_id | paramno=2 | name=[28] ":without_primarykey_goods_id" | is_param=1 | param_type=1 | Key: Name: [31] :without_primarykey_description | paramno=3 | name=[31] ":without_primarykey_description" | is_param=1 | param_type=2 | Key: Name: [24] :without_primarykey_name | paramno=4 | name=[24] ":without_primarykey_name" | is_param=1 | param_type=2 (UPDATE `without_primarykey` SET `without_primarykey`.`description` = \'my\',`without_primarykey`.`name` = \'php\' WHERE `without_primarykey`.`goods_id` = 1 AND `without_primarykey`.`description` = \'hello\' AND `without_primarykey`.`name` = \'world\' LIMIT 1)', $withoutPrimarykey->select()->getLastSql());
  24. $withoutPrimarykey->name = 'new name';
  25. $this->assertSame(1, $withoutPrimarykey->update()->flush());
  26. $this->assertSame('SQL: [294] UPDATE `without_primarykey` SET `without_primarykey`.`name` = :pdonamedparameter_name WHERE `without_primarykey`.`goods_id` = :without_primarykey_goods_id AND `without_primarykey`.`description` = :without_primarykey_description AND `without_primarykey`.`name` = :without_primarykey_name LIMIT 1 | Params: 4 | Key: Name: [23] :pdonamedparameter_name | paramno=0 | name=[23] ":pdonamedparameter_name" | is_param=1 | param_type=2 | Key: Name: [28] :without_primarykey_goods_id | paramno=1 | name=[28] ":without_primarykey_goods_id" | is_param=1 | param_type=1 | Key: Name: [31] :without_primarykey_description | paramno=2 | name=[31] ":without_primarykey_description" | is_param=1 | param_type=2 | Key: Name: [24] :without_primarykey_name | paramno=3 | name=[24] ":without_primarykey_name" | is_param=1 | param_type=2 (UPDATE `without_primarykey` SET `without_primarykey`.`name` = \'new name\' WHERE `without_primarykey`.`goods_id` = 1 AND `without_primarykey`.`description` = \'my\' AND `without_primarykey`.`name` = \'php\' LIMIT 1)', $withoutPrimarykey->select()->getLastSql());
  27. $withoutPrimarykey->name = 'new and new';
  28. $withoutPrimarykey->update();
  29. $withoutPrimarykey->name = 'new and new2';
  30. $this->assertSame(1, $withoutPrimarykey->update()->flush());
  31. $this->assertSame('SQL: [294] UPDATE `without_primarykey` SET `without_primarykey`.`name` = :pdonamedparameter_name WHERE `without_primarykey`.`goods_id` = :without_primarykey_goods_id AND `without_primarykey`.`description` = :without_primarykey_description AND `without_primarykey`.`name` = :without_primarykey_name LIMIT 1 | Params: 4 | Key: Name: [23] :pdonamedparameter_name | paramno=0 | name=[23] ":pdonamedparameter_name" | is_param=1 | param_type=2 | Key: Name: [28] :without_primarykey_goods_id | paramno=1 | name=[28] ":without_primarykey_goods_id" | is_param=1 | param_type=1 | Key: Name: [31] :without_primarykey_description | paramno=2 | name=[31] ":without_primarykey_description" | is_param=1 | param_type=2 | Key: Name: [24] :without_primarykey_name | paramno=3 | name=[24] ":without_primarykey_name" | is_param=1 | param_type=2 (UPDATE `without_primarykey` SET `without_primarykey`.`name` = \'new and new2\' WHERE `without_primarykey`.`goods_id` = 1 AND `without_primarykey`.`description` = \'my\' AND `without_primarykey`.`name` = \'new name\' LIMIT 1)', $withoutPrimarykey->select()->getLastSql());
  32. }

__clone 实体克隆

复制的实体没有主键值,保存数据时将会在数据库新增一条记录。

  1. public function testEntityClone(): 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' => 'post summary',
  12. 'delete_at' => 0,
  13. ])
  14. );
  15. $post = Post::find()->where('id', 1)->findOne();
  16. $this->assertSame(1, $post->id);
  17. $this->assertSame('hello world', $post->title);
  18. $this->assertSame(1, $post->userId);
  19. $this->assertSame('post summary', $post->summary);
  20. $postClone = clone $post;
  21. $this->assertNull($postClone->id);
  22. $this->assertSame('hello world', $postClone->title);
  23. $this->assertSame(1, $postClone->userId);
  24. $this->assertSame('post summary', $postClone->summary);
  25. $post->title = 'world';
  26. $this->assertSame('hello world', $postClone->title);
  27. $postClone->title = 'goods';
  28. $this->assertSame('world', $post->title);
  29. }

make 创建实例

  1. public function testEntityMake(): void
  2. {
  3. $post = Post::make([
  4. 'title' => 'hello world',
  5. 'user_id' => 1,
  6. 'summary' => 'post summary',
  7. 'delete_at' => 0,
  8. ]);
  9. $this->assertTrue($post->newed());
  10. $this->assertNull($post->id);
  11. $this->assertSame('hello world', $post->title);
  12. $this->assertSame(1, $post->userId);
  13. $this->assertSame('post summary', $post->summary);
  14. }

createAssign 新增批量赋值

  1. public function testEntityCreateAssign(): void
  2. {
  3. $post = Post::createAssign([
  4. 'title' => 'hello world',
  5. 'user_id' => 1,
  6. 'summary' => 'post summary',
  7. 'delete_at' => 0,
  8. ]);
  9. $this->assertTrue($post->newed());
  10. $this->assertNull($post->id);
  11. $this->assertSame('hello world', $post->title);
  12. $this->assertSame(1, $post->userId);
  13. $this->assertSame('post summary', $post->summary);
  14. }

updateAssign 更新批量赋值

  1. public function testEntityUpdateAssign(): void
  2. {
  3. $post = Post::updateAssign([
  4. 'id' => 1,
  5. 'title' => 'hello world',
  6. 'user_id' => 1,
  7. 'summary' => 'post summary',
  8. 'delete_at' => 0,
  9. ]);
  10. $this->assertFalse($post->newed());
  11. $this->assertSame(1, $post->id);
  12. $this->assertSame('hello world', $post->title);
  13. $this->assertSame(1, $post->userId);
  14. $this->assertSame('post summary', $post->summary);
  15. }