From 36745eb1c5e732e0797a4f29f3f12468cb07c317 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 7 Jun 2022 12:05:49 +0300 Subject: [PATCH 01/10] Add data property to access raw data --- src/Factory/AbstractFactory.php | 22 ++++++++++++++++++++++ src/Factory/Exception/FactoryException.php | 9 +++++++++ tests/src/Unit/Factory/FactoryTest.php | 17 +++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 src/Factory/Exception/FactoryException.php diff --git a/src/Factory/AbstractFactory.php b/src/Factory/AbstractFactory.php index dc18ca6..a6baae4 100644 --- a/src/Factory/AbstractFactory.php +++ b/src/Factory/AbstractFactory.php @@ -9,7 +9,11 @@ use Laminas\Hydrator\ReflectionHydrator; use Butschster\EntityFaker\Factory; use Butschster\EntityFaker\LaminasEntityFactory; +use Spiral\DatabaseSeeder\Factory\Exception\FactoryException; +/** + * @property-read $data + */ abstract class AbstractFactory implements FactoryInterface { /** @internal */ @@ -67,6 +71,14 @@ public function create(): array return $entities; } + public function __get(string $name): array + { + return match ($name) { + 'data' => $this->raw([$this, 'definition']), + default => throw new FactoryException('Undefined magic property.') + }; + } + public function createOne(): object { $entity = $this->make([$this, 'definition']); @@ -87,6 +99,16 @@ private function make(callable $definition): object|array return $this->entityFactory->of($this->entity())->times($this->amount)->make($this->replaces); } + /** @internal */ + private function raw(callable $definition): array + { + $this->entityFactory->define($this->entity(), $definition); + + $data = $this->entityFactory->of($this->entity())->times($this->amount)->raw($this->replaces); + + return \array_is_list($data) ? $data[0] : $data; + } + /** @internal */ private function callAfterCreating(array $entities) { diff --git a/src/Factory/Exception/FactoryException.php b/src/Factory/Exception/FactoryException.php new file mode 100644 index 0000000..99d7460 --- /dev/null +++ b/src/Factory/Exception/FactoryException.php @@ -0,0 +1,9 @@ +assertSame('changed by replaces array', $post->content); } + + public function testRawData(): void + { + $post = PostFactory::new()->data; + $post2 = PostFactory::new()->data; + + $this->assertIsArray($post); + $this->assertIsArray($post2); + $this->assertNotSame($post['content'], $post2['content']); + } + + public function testUndefinedProperty(): void + { + $this->expectException(FactoryException::class); + PostFactory::new()->test; + } } From 215f09ad79faa2a973da668b899a82572de1040d Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 7 Jun 2022 12:26:22 +0300 Subject: [PATCH 02/10] Rename create and createOne to make and makeOne --- src/Factory/AbstractFactory.php | 34 ++++++++++++------- src/Factory/FactoryInterface.php | 4 +++ tests/app/database/Factory/CommentFactory.php | 2 +- tests/app/database/Factory/PostFactory.php | 4 +-- tests/app/database/Seeder/CommentSeeder.php | 4 +-- tests/app/database/Seeder/PostSeeder.php | 2 +- tests/app/database/Seeder/UserSeeder.php | 2 +- tests/src/Unit/Factory/FactoryTest.php | 14 ++++---- 8 files changed, 40 insertions(+), 26 deletions(-) diff --git a/src/Factory/AbstractFactory.php b/src/Factory/AbstractFactory.php index a6baae4..7e5ae8f 100644 --- a/src/Factory/AbstractFactory.php +++ b/src/Factory/AbstractFactory.php @@ -61,7 +61,17 @@ public function afterCreate(callable $afterCreate): self public function create(): array { - $entities = $this->make([$this, 'definition']); + + } + + public function createOne(): object + { + + } + + public function make(): array + { + $entities = $this->object([$this, 'definition']); if (!\is_array($entities)) { $entities = [$entities]; } @@ -71,17 +81,9 @@ public function create(): array return $entities; } - public function __get(string $name): array - { - return match ($name) { - 'data' => $this->raw([$this, 'definition']), - default => throw new FactoryException('Undefined magic property.') - }; - } - - public function createOne(): object + public function makeOne(): object { - $entity = $this->make([$this, 'definition']); + $entity = $this->object([$this, 'definition']); if (\is_array($entity)) { $entity = \array_shift($entity); } @@ -91,8 +93,16 @@ public function createOne(): object return $entity; } + public function __get(string $name): array + { + return match ($name) { + 'data' => $this->raw([$this, 'definition']), + default => throw new FactoryException('Undefined magic property.') + }; + } + /** @internal */ - private function make(callable $definition): object|array + private function object(callable $definition): object|array { $this->entityFactory->define($this->entity(), $definition); diff --git a/src/Factory/FactoryInterface.php b/src/Factory/FactoryInterface.php index 4521c9c..9092974 100644 --- a/src/Factory/FactoryInterface.php +++ b/src/Factory/FactoryInterface.php @@ -18,4 +18,8 @@ public function times(int $amount): self; public function create(): array; public function createOne(): object; + + public function make(): array; + + public function makeOne(): object; } diff --git a/tests/app/database/Factory/CommentFactory.php b/tests/app/database/Factory/CommentFactory.php index 06dae82..4f1a83b 100644 --- a/tests/app/database/Factory/CommentFactory.php +++ b/tests/app/database/Factory/CommentFactory.php @@ -18,7 +18,7 @@ public function definition(): array { return [ 'text' => $this->faker->randomHtml(), - 'author' => UserFactory::new()->createOne(), + 'author' => UserFactory::new()->makeOne(), 'postedAt' => \DateTimeImmutable::createFromMutable($this->faker->dateTime()), ]; } diff --git a/tests/app/database/Factory/PostFactory.php b/tests/app/database/Factory/PostFactory.php index bb54d07..b9027fb 100644 --- a/tests/app/database/Factory/PostFactory.php +++ b/tests/app/database/Factory/PostFactory.php @@ -18,9 +18,9 @@ public function definition(): array { return [ 'content' => $this->faker->randomHtml(), - 'author' => UserFactory::new()->createOne(), + 'author' => UserFactory::new()->makeOne(), 'publishedAt' => \DateTimeImmutable::createFromMutable($this->faker->dateTime()), - 'comments' => CommentFactory::new()->times(3)->create(), + 'comments' => CommentFactory::new()->times(3)->make(), ]; } } diff --git a/tests/app/database/Seeder/CommentSeeder.php b/tests/app/database/Seeder/CommentSeeder.php index 418d772..16be76f 100644 --- a/tests/app/database/Seeder/CommentSeeder.php +++ b/tests/app/database/Seeder/CommentSeeder.php @@ -18,9 +18,9 @@ class CommentSeeder extends AbstractSeeder public function run(): \Generator { /** @var Post $post */ - $post = PostFactory::new()->createOne(); + $post = PostFactory::new()->makeOne(); /** @var User $user */ - $user = UserFactory::new()->createOne(); + $user = UserFactory::new()->makeOne(); $comment = new Comment(); $comment->post = $post; diff --git a/tests/app/database/Seeder/PostSeeder.php b/tests/app/database/Seeder/PostSeeder.php index 9d22b85..8d6110c 100644 --- a/tests/app/database/Seeder/PostSeeder.php +++ b/tests/app/database/Seeder/PostSeeder.php @@ -13,6 +13,6 @@ class PostSeeder extends AbstractSeeder { public function run(): \Generator { - yield from PostFactory::new()->times(10)->create(); + yield from PostFactory::new()->times(10)->make(); } } diff --git a/tests/app/database/Seeder/UserSeeder.php b/tests/app/database/Seeder/UserSeeder.php index 9d24eb7..59133fc 100644 --- a/tests/app/database/Seeder/UserSeeder.php +++ b/tests/app/database/Seeder/UserSeeder.php @@ -13,6 +13,6 @@ class UserSeeder extends AbstractSeeder public function run(): \Generator { - yield UserFactory::new()->createOne(); + yield UserFactory::new()->makeOne(); } } diff --git a/tests/src/Unit/Factory/FactoryTest.php b/tests/src/Unit/Factory/FactoryTest.php index d74be06..7e94527 100644 --- a/tests/src/Unit/Factory/FactoryTest.php +++ b/tests/src/Unit/Factory/FactoryTest.php @@ -17,9 +17,9 @@ final class FactoryTest extends TestCase { public function testCreateEntity(): void { - $user = UserFactory::new()->createOne(); - $post = PostFactory::new()->createOne(); - $comment = CommentFactory::new()->createOne(); + $user = UserFactory::new()->makeOne(); + $post = PostFactory::new()->makeOne(); + $comment = CommentFactory::new()->makeOne(); $this->assertInstanceOf(User::class, $user); $this->assertIsString($user->firstName); @@ -42,7 +42,7 @@ public function testCreateEntity(): void public function testCreateMultiple(): void { - $users = UserFactory::new()->times(2)->create(); + $users = UserFactory::new()->times(2)->make(); $this->assertCount(2, $users); @@ -56,21 +56,21 @@ public function testCreateMultiple(): void public function testCreateNullableNotFilled(): void { - $user = UserFactory::new()->createOne(); + $user = UserFactory::new()->makeOne(); $this->assertNull($user->city); } public function testAfterCreateCallback(): void { - $post = PostFactory::new()->afterCreate(fn(Post $post) => $post->content = 'changed by callback')->createOne(); + $post = PostFactory::new()->afterCreate(fn(Post $post) => $post->content = 'changed by callback')->makeOne(); $this->assertSame('changed by callback', $post->content); } public function testCreateWithReplaces(): void { - $post = PostFactory::new(['content' => 'changed by replaces array'])->createOne(); + $post = PostFactory::new(['content' => 'changed by replaces array'])->makeOne(); $this->assertSame('changed by replaces array', $post->content); } From 1f1d4a45e687ec51b0d61304b0da56e5e267ce4a Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Tue, 7 Jun 2022 12:51:46 +0300 Subject: [PATCH 03/10] Update Changelog and Readme --- CHANGELOG.md | 21 ++++++++++++++++++++- README.md | 4 ++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0226d3a..062aeb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,25 @@ # Changelog -All notable changes to `database-seeder` will be documented in this file. +## 2.0.0 - Unreleased +- **High Impact Changes** + - Dropped support for Spiral Framework 2.x + - Method `create` in `Spiral\DatabaseSeeder\Factory\FactoryInterface` interface and + `Spiral\DatabaseSeeder\Factory\AbstractFactory` class renamed to `make` + - Method `createOne` in `Spiral\DatabaseSeeder\Factory\FactoryInterface` interface and + `Spiral\DatabaseSeeder\Factory\AbstractFactory` class renamed to `makeOne` + +- **Medium Impact Changes** + - Min PHP version increased to 8.1 + +- **Other Features** + - Added `Spiral\DatabaseSeeder\TestCase` class, which can use as a base class in tests that work with the database. + It adds the ability to use traits in test classes that add functionality for testing database applications. + To use this class, you must use the `spiral/testing` package + - Added `Spiral\DatabaseSeeder\Database\Traits\RefreshDatabase` trait. + This trait automatically creates the database structure the first time any test is run, and refresh the database before each test is run. + - Added `Spiral\DatabaseSeeder\Database\Traits\DatabaseMigrations` trait. + This trait creates a database structure using migrations before each test execution. After each test runs, + it refreshes the database and rollback migrations. ## 1.0.0 - 202X-XX-XX diff --git a/README.md b/README.md index 172c324..4e0f489 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ class UserFactory extends AbstractFactory 'firstName' => $this->faker->firstName(), 'lastName' => $this->faker->lastName(), 'birthday' => \DateTimeImmutable::createFromMutable($this->faker->dateTime()), - 'comments' => CommentFactory::new()->times(3)->create(), // Can use other factories. + 'comments' => CommentFactory::new()->times(3)->make(), // Can use other factories. // Be careful, circular dependencies are not allowed! ]; } @@ -89,7 +89,7 @@ class UserTableSeeder extends AbstractSeeder { public function run(): \Generator { - foreach (UserFactory::new()->times(100)->create() as $user) { + foreach (UserFactory::new()->times(100)->make() as $user) { yield $user; } } From 22aff487e949fd722162eb33d0beaf74998064ce Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Sat, 11 Jun 2022 11:08:02 +0300 Subject: [PATCH 04/10] Update test for new version spiral/testing --- .../Driver/Common/Database/Traits/RefreshDatabaseTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/Functional/Driver/Common/Database/Traits/RefreshDatabaseTest.php b/tests/src/Functional/Driver/Common/Database/Traits/RefreshDatabaseTest.php index 6f533f1..50e4905 100644 --- a/tests/src/Functional/Driver/Common/Database/Traits/RefreshDatabaseTest.php +++ b/tests/src/Functional/Driver/Common/Database/Traits/RefreshDatabaseTest.php @@ -14,6 +14,6 @@ abstract class RefreshDatabaseTest extends TestCase // disabling auto executing DB traits protected function setUp(): void { - $this->refreshApp(); + $this->initApp(static::ENV); } } From cefc586bf2ca0f38a2867cd18409d4fa4349ef9e Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Sun, 12 Jun 2022 13:29:29 +0300 Subject: [PATCH 05/10] Add create and createOne implementation, add DatabaseAsserts trait --- src/Database/Traits/DatabaseAsserts.php | 69 +++++++++++++++++++ src/Factory/AbstractFactory.php | 30 ++++++++ src/TestCase.php | 15 ++++ .../Common/Factory/AbstractFactoryTest.php | 34 +++++++++ .../Database/Factory/AbstractFactoryTest.php | 20 ++++++ .../SQLite/Factory/AbstractFactoryTest.php | 17 +++++ tests/src/Functional/TestCase.php | 17 ----- 7 files changed, 185 insertions(+), 17 deletions(-) create mode 100644 src/Database/Traits/DatabaseAsserts.php create mode 100644 tests/src/Functional/Driver/Common/Factory/AbstractFactoryTest.php create mode 100644 tests/src/Functional/Driver/MySQL/Database/Factory/AbstractFactoryTest.php create mode 100644 tests/src/Functional/Driver/SQLite/Factory/AbstractFactoryTest.php diff --git a/src/Database/Traits/DatabaseAsserts.php b/src/Database/Traits/DatabaseAsserts.php new file mode 100644 index 0000000..55fb130 --- /dev/null +++ b/src/Database/Traits/DatabaseAsserts.php @@ -0,0 +1,69 @@ +getContainer()->get(Database::class)->hasTable($table), + \sprintf('Table [%s] does not exist.', $table) + ); + } + + public function assertTableIsNotExists(string $table): void + { + static::assertFalse( + $this->getContainer()->get(Database::class)->hasTable($table), + \sprintf('Table [%s] exists.', $table) + ); + } + + public function assertTableCount(string $table, int $count): void + { + static::assertSame( + $count, + $this->getContainer()->get(Database::class)->table($table)->count() + ); + } + + public function assertTableHas(string $table, array $where = []): void + { + $select = $this->getContainer()->get(Database::class)->table($table)->select(); + + if ($where !== []) { + $select->where($where); + } + + static::assertTrue($select->count() >= 0); + } + + /** @param class-string $entity */ + public function assertEntitiesCount(string $entity, int $count): void + { + /** @var Repository $repository */ + $repository = $this->getContainer()->get(ORM::class)->getRepository($entity); + + static::assertSame($count, $repository->select()->count()); + } + + public function assertTableHasEntity(string $entity, array $where = []): void + { + /** @var Repository $repository */ + $repository = $this->getContainer()->get(ORM::class)->getRepository($entity); + $select = $repository->select(); + + if ($where !== []) { + $select->where($where); + } + + static::assertTrue($select->count() >= 0); + } +} diff --git a/src/Factory/AbstractFactory.php b/src/Factory/AbstractFactory.php index 7e5ae8f..bd49c18 100644 --- a/src/Factory/AbstractFactory.php +++ b/src/Factory/AbstractFactory.php @@ -4,11 +4,13 @@ namespace Spiral\DatabaseSeeder\Factory; +use Cycle\ORM\EntityManagerInterface; use Faker\Factory as FakerFactory; use Faker\Generator; use Laminas\Hydrator\ReflectionHydrator; use Butschster\EntityFaker\Factory; use Butschster\EntityFaker\LaminasEntityFactory; +use Spiral\Core\ContainerScope; use Spiral\DatabaseSeeder\Factory\Exception\FactoryException; /** @@ -61,12 +63,30 @@ public function afterCreate(callable $afterCreate): self public function create(): array { + $entities = $this->object([$this, 'definition']); + if (!\is_array($entities)) { + $entities = [$entities]; + } + + $this->storeEntities($entities); + $this->callAfterCreating($entities); + + return $entities; } public function createOne(): object { + $entity = $this->object([$this, 'definition']); + if (\is_array($entity)) { + $entity = \array_shift($entity); + } + + $this->storeEntities([$entity]); + $this->callAfterCreating([$entity]); + + return $entity; } public function make(): array @@ -101,6 +121,16 @@ public function __get(string $name): array }; } + private function storeEntities(array $entities): void + { + /** @var EntityManagerInterface $em */ + $em = ContainerScope::getContainer()->get(EntityManagerInterface::class); + foreach ($entities as $entity) { + $em->persist($entity); + } + $em->run(); + } + /** @internal */ private function object(callable $definition): object|array { diff --git a/src/TestCase.php b/src/TestCase.php index f3f4492..925ad33 100644 --- a/src/TestCase.php +++ b/src/TestCase.php @@ -4,15 +4,30 @@ namespace Spiral\DatabaseSeeder; +use Spiral\Core\ContainerScope; +use Spiral\DatabaseSeeder\Database\Traits\DatabaseAsserts; + abstract class TestCase extends \Spiral\Testing\TestCase { + use DatabaseAsserts; + protected function setUp(): void { parent::setUp(); + // Bind container to ContainerScope + (new \ReflectionClass(ContainerScope::class))->setStaticPropertyValue('container', $this->getContainer()); + $this->setUpTraits(); } + protected function tearDown(): void + { + parent::tearDown(); + + (new \ReflectionClass(ContainerScope::class))->setStaticPropertyValue('container', null); + } + private function setUpTraits(): void { /** @see \Spiral\DatabaseSeeder\Database\Traits\RefreshDatabase */ diff --git a/tests/src/Functional/Driver/Common/Factory/AbstractFactoryTest.php b/tests/src/Functional/Driver/Common/Factory/AbstractFactoryTest.php new file mode 100644 index 0000000..5faaae6 --- /dev/null +++ b/tests/src/Functional/Driver/Common/Factory/AbstractFactoryTest.php @@ -0,0 +1,34 @@ +times(5)->create(); + + $this->assertTableCount('users', 5); + $this->assertTableHas('users', ['id' => $users[0]->id]); + $this->assertTableHas('users', ['id' => $users[1]->id]); + $this->assertTableHas('users', ['id' => $users[2]->id]); + $this->assertTableHas('users', ['id' => $users[3]->id]); + $this->assertTableHas('users', ['id' => $users[4]->id]); + } + + public function testCreateOne(): void + { + $user = UserFactory::new()->createOne(); + + $this->assertTableCount('users', 1); + $this->assertTableHas('users', ['id' => $user->id]); + } +} diff --git a/tests/src/Functional/Driver/MySQL/Database/Factory/AbstractFactoryTest.php b/tests/src/Functional/Driver/MySQL/Database/Factory/AbstractFactoryTest.php new file mode 100644 index 0000000..bbc72b1 --- /dev/null +++ b/tests/src/Functional/Driver/MySQL/Database/Factory/AbstractFactoryTest.php @@ -0,0 +1,20 @@ + 'mysql' + ]; +} diff --git a/tests/src/Functional/Driver/SQLite/Factory/AbstractFactoryTest.php b/tests/src/Functional/Driver/SQLite/Factory/AbstractFactoryTest.php new file mode 100644 index 0000000..17351a9 --- /dev/null +++ b/tests/src/Functional/Driver/SQLite/Factory/AbstractFactoryTest.php @@ -0,0 +1,17 @@ +getContainer()->get(Database::class)->hasTable($table), - \sprintf('Table [%s] does not exist.', $table) - ); - } - - public function assertTableIsNotExists(string $table): void - { - static::assertFalse( - $this->getContainer()->get(Database::class)->hasTable($table), - \sprintf('Table [%s] exists.', $table) - ); - } } From c0c92b0e109078dcb7e529a46536c9bbaa972c7e Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Sun, 12 Jun 2022 13:43:28 +0300 Subject: [PATCH 06/10] Fix Psalm --- src/Database/Traits/DatabaseAsserts.php | 5 +++++ src/Factory/AbstractFactory.php | 12 +++++++++++- src/Factory/Exception/OutsideScopeException.php | 9 +++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 src/Factory/Exception/OutsideScopeException.php diff --git a/src/Database/Traits/DatabaseAsserts.php b/src/Database/Traits/DatabaseAsserts.php index 55fb130..23a0522 100644 --- a/src/Database/Traits/DatabaseAsserts.php +++ b/src/Database/Traits/DatabaseAsserts.php @@ -10,6 +10,7 @@ trait DatabaseAsserts { + /** @psalm-param non-empty-string $table */ public function assertTableExists(string $table): void { static::assertTrue( @@ -18,6 +19,7 @@ public function assertTableExists(string $table): void ); } + /** @psalm-param non-empty-string $table */ public function assertTableIsNotExists(string $table): void { static::assertFalse( @@ -26,6 +28,7 @@ public function assertTableIsNotExists(string $table): void ); } + /** @psalm-param non-empty-string $table */ public function assertTableCount(string $table, int $count): void { static::assertSame( @@ -34,6 +37,7 @@ public function assertTableCount(string $table, int $count): void ); } + /** @psalm-param non-empty-string $table */ public function assertTableHas(string $table, array $where = []): void { $select = $this->getContainer()->get(Database::class)->table($table)->select(); @@ -54,6 +58,7 @@ public function assertEntitiesCount(string $entity, int $count): void static::assertSame($count, $repository->select()->count()); } + /** @param class-string $entity */ public function assertTableHasEntity(string $entity, array $where = []): void { /** @var Repository $repository */ diff --git a/src/Factory/AbstractFactory.php b/src/Factory/AbstractFactory.php index bd49c18..c7f629f 100644 --- a/src/Factory/AbstractFactory.php +++ b/src/Factory/AbstractFactory.php @@ -12,6 +12,7 @@ use Butschster\EntityFaker\LaminasEntityFactory; use Spiral\Core\ContainerScope; use Spiral\DatabaseSeeder\Factory\Exception\FactoryException; +use Spiral\DatabaseSeeder\Factory\Exception\OutsideScopeException; /** * @property-read $data @@ -123,8 +124,17 @@ public function __get(string $name): array private function storeEntities(array $entities): void { + $container = ContainerScope::getContainer(); + if ($container === null) { + throw new OutsideScopeException(\sprintf( + 'The container is not available. Make sure [%s] method is running in the ContainerScope.', + __METHOD__ + )); + } + /** @var EntityManagerInterface $em */ - $em = ContainerScope::getContainer()->get(EntityManagerInterface::class); + $em = $container->get(EntityManagerInterface::class); + foreach ($entities as $entity) { $em->persist($entity); } diff --git a/src/Factory/Exception/OutsideScopeException.php b/src/Factory/Exception/OutsideScopeException.php new file mode 100644 index 0000000..05a5fd0 --- /dev/null +++ b/src/Factory/Exception/OutsideScopeException.php @@ -0,0 +1,9 @@ + Date: Sun, 12 Jun 2022 14:12:58 +0300 Subject: [PATCH 07/10] Add error messages --- src/Database/Traits/DatabaseAsserts.php | 16 +++++-- src/Database/Traits/DatabaseMigrations.php | 43 ++++++++++++++----- src/TestCase.php | 10 +++++ .../Database/Factory/AbstractFactoryTest.php | 20 --------- 4 files changed, 55 insertions(+), 34 deletions(-) delete mode 100644 tests/src/Functional/Driver/MySQL/Database/Factory/AbstractFactoryTest.php diff --git a/src/Database/Traits/DatabaseAsserts.php b/src/Database/Traits/DatabaseAsserts.php index 23a0522..a7453de 100644 --- a/src/Database/Traits/DatabaseAsserts.php +++ b/src/Database/Traits/DatabaseAsserts.php @@ -31,9 +31,12 @@ public function assertTableIsNotExists(string $table): void /** @psalm-param non-empty-string $table */ public function assertTableCount(string $table, int $count): void { + $actual = $this->getContainer()->get(Database::class)->table($table)->count(); + static::assertSame( $count, - $this->getContainer()->get(Database::class)->table($table)->count() + $actual, + \sprintf('Expected %s records in the table [%s], actual are %s.', $count, $table, $actual) ); } @@ -46,7 +49,7 @@ public function assertTableHas(string $table, array $where = []): void $select->where($where); } - static::assertTrue($select->count() >= 0); + static::assertTrue($select->count() >= 0, \sprintf('Record not found in the table [%s].', $table)); } /** @param class-string $entity */ @@ -54,8 +57,13 @@ public function assertEntitiesCount(string $entity, int $count): void { /** @var Repository $repository */ $repository = $this->getContainer()->get(ORM::class)->getRepository($entity); + $actual = $repository->select()->count(); - static::assertSame($count, $repository->select()->count()); + static::assertSame( + $count, + $actual, + \sprintf('Expected %s entities in the table, actual are %s.', $count, $actual) + ); } /** @param class-string $entity */ @@ -69,6 +77,6 @@ public function assertTableHasEntity(string $entity, array $where = []): void $select->where($where); } - static::assertTrue($select->count() >= 0); + static::assertTrue($select->count() >= 0, \sprintf('Entity [%s] not found.', $entity)); } } diff --git a/src/Database/Traits/DatabaseMigrations.php b/src/Database/Traits/DatabaseMigrations.php index 9581661..19c66d4 100644 --- a/src/Database/Traits/DatabaseMigrations.php +++ b/src/Database/Traits/DatabaseMigrations.php @@ -14,6 +14,38 @@ trait DatabaseMigrations * Migrate the database before and after each test. */ public function runDatabaseMigrations(): void + { + $this->runCommand('cycle:migrate', ['--run' => true]); + + /* TODO wait resolving bug with finalizers + $directory = $this->getMigrationsDirectory(); + $self = $this; + $this->getContainer()->get(FinalizerInterface::class)->addFinalizer(static function () use($self, $directory) { + $self->runCommand('migrate:rollback', ['--all' => true]); + + $self->cleanupDirectories($directory); + + DatabaseState::$migrated = false; + }); + */ + } + + public function runDatabaseRollback(): void + { + $directory = $this->getMigrationsDirectory(); + + $this->runCommand('migrate:rollback', ['--all' => true]); + + $this->cleanupDirectories($directory); + + DatabaseState::$migrated = false; + } + + /** + * @invisible + * @internal + */ + private function getMigrationsDirectory(): string { $config = $this->getConfig('migration'); if (empty($config['directory'])) { @@ -28,15 +60,6 @@ public function runDatabaseMigrations(): void ); } - $this->runCommand('cycle:migrate', ['--run' => true]); - - $self = $this; - $this->getContainer()->get(FinalizerInterface::class)->addFinalizer(static function () use($self, $config) { - $self->runCommand('migrate:rollback', ['--all' => true]); - - $self->cleanupDirectories($config['directory']); - - DatabaseState::$migrated = false; - }); + return $config['directory']; } } diff --git a/src/TestCase.php b/src/TestCase.php index 925ad33..5f70829 100644 --- a/src/TestCase.php +++ b/src/TestCase.php @@ -26,6 +26,8 @@ protected function tearDown(): void parent::tearDown(); (new \ReflectionClass(ContainerScope::class))->setStaticPropertyValue('container', null); + + $this->tearDownTraits(); } private function setUpTraits(): void @@ -40,4 +42,12 @@ private function setUpTraits(): void $this->runDatabaseMigrations(); } } + + private function tearDownTraits(): void + { + /** @see \Spiral\DatabaseSeeder\Database\Traits\DatabaseMigrations */ + if (\method_exists($this, 'runDatabaseRollback')) { + $this->runDatabaseRollback(); + } + } } diff --git a/tests/src/Functional/Driver/MySQL/Database/Factory/AbstractFactoryTest.php b/tests/src/Functional/Driver/MySQL/Database/Factory/AbstractFactoryTest.php deleted file mode 100644 index bbc72b1..0000000 --- a/tests/src/Functional/Driver/MySQL/Database/Factory/AbstractFactoryTest.php +++ /dev/null @@ -1,20 +0,0 @@ - 'mysql' - ]; -} From 6f35caa42e47d9b50a2b4979e9bd77e425735c07 Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Mon, 20 Jun 2022 13:18:50 +0300 Subject: [PATCH 08/10] Update readme --- README.md | 116 ++++++++++++++++++++++++++++++-- src/Factory/AbstractFactory.php | 19 +++--- 2 files changed, 120 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 4e0f489..2bd3005 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ The package provides the ability to seed your database with data using seed clas Make sure that your server is configured with following PHP version and extensions: -- PHP 8.0+ -- Spiral framework 2.9+ +- PHP 8.1+ +- Spiral framework ^3.0 ## Installation @@ -30,8 +30,8 @@ protected const LOAD = [ \Spiral\DatabaseSeeder\Bootloader\DatabaseSeederBootloader::class, ]; ``` - -> Note: if you are using [`spiral-packages/discoverer`](https://github.com/spiral-packages/discoverer), +> **Note** +> if you are using [`spiral-packages/discoverer`](https://github.com/spiral-packages/discoverer), > you don't need to register bootloader by yourself. ## Usage @@ -74,8 +74,42 @@ class UserFactory extends AbstractFactory } } ``` +A factory can be created using the method `new`. +```php +$factory = UserFactory::new(); +``` +A factory has several useful methods: +- `create` - Creates an array of entities, stores them in the database, and returns them for further use in code. +- `createOne` - Creates one entity, stores it in the database, and returns it for further use in code. +- `make` - Creates an array of entities and returns them for further use in code. +- `makeOne` - Creates one entity and returns it for further use in code. +- `raw` - or `data` property. Used to get an array of entity data (raw data, without entity creation). + +A few examples: +```php +// 10 users stored in the database +$users = UserFactory::new()->times(10)->create(); + +// one user stored in the database +$user = UserFactory::new()->createOne(); + +// 10 users. Entities will not be saved to the database. Only returned for future use +$users = UserFactory::new()->times(10)->make(); + +// one user. Will not be saved to the database +$user = UserFactory::new()->makeOne(); + +// array with raw user data +$data = UserFactory::new()->raw(); +// or +$data = UserFactory::new()->data; +``` + +### Seeding +The package provides the ability to seed the database with test data. To do this, create a Seeder class and extend it +from the `Spiral\DatabaseSeeder\Seeder\AbstractSeeder` class. Implement the `run` method. +This method should return a generator with entities to store in the database. -After that, you can use this factory in your code. For example: ```php **Note** +Important! Be sure to set up a test database in the test application. Never use a production database for testing! + +To use these features, your application's tests must be written using the `spiral/testing` package. + +First of all, inherit the base test class that is used in tests using the new functionality. +This will make it possible to use traits to simplify working with the database in tests and provide additional methods for testing. +Example: +```php +entityFactory->define($this->entity(), $definition); + + $data = $this->entityFactory->of($this->entity())->times($this->amount)->raw($this->replaces); + + return \array_is_list($data) ? $data[0] : $data; + } + public function __get(string $name): array { return match ($name) { @@ -149,16 +158,6 @@ private function object(callable $definition): object|array return $this->entityFactory->of($this->entity())->times($this->amount)->make($this->replaces); } - /** @internal */ - private function raw(callable $definition): array - { - $this->entityFactory->define($this->entity(), $definition); - - $data = $this->entityFactory->of($this->entity())->times($this->amount)->raw($this->replaces); - - return \array_is_list($data) ? $data[0] : $data; - } - /** @internal */ private function callAfterCreating(array $entities) { From 39c311009e05c3e37fde6cc46fd0ed68475db7fd Mon Sep 17 00:00:00 2001 From: Maxim Smakouz Date: Mon, 20 Jun 2022 14:57:31 +0300 Subject: [PATCH 09/10] Add checking existence entity in db --- src/Seeder/Executor.php | 25 ++++++- .../Factory/WithCompositePkFactory.php | 25 +++++++ .../database/Seeder/WithCompositePkSeeder.php | 18 +++++ tests/app/src/Database/User.php | 2 +- tests/app/src/Database/WithCompositePk.php | 23 +++++++ .../Driver/Common/Seeder/ExecutorTest.php | 66 +++++++++++++++++++ .../Driver/SQLite/Seeder/ExecutorTest.php | 17 +++++ 7 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 tests/app/database/Factory/WithCompositePkFactory.php create mode 100644 tests/app/database/Seeder/WithCompositePkSeeder.php create mode 100644 tests/app/src/Database/WithCompositePk.php create mode 100644 tests/src/Functional/Driver/Common/Seeder/ExecutorTest.php create mode 100644 tests/src/Functional/Driver/SQLite/Seeder/ExecutorTest.php diff --git a/src/Seeder/Executor.php b/src/Seeder/Executor.php index b150dbb..c3a4a4a 100644 --- a/src/Seeder/Executor.php +++ b/src/Seeder/Executor.php @@ -4,14 +4,18 @@ namespace Spiral\DatabaseSeeder\Seeder; +use Cycle\Database\Injection\Parameter; use Cycle\ORM\EntityManagerInterface; +use Cycle\ORM\ORM; +use Cycle\ORM\SchemaInterface; class Executor implements ExecutorInterface { private array $afterSeed = []; public function __construct( - private EntityManagerInterface $entityManager + private EntityManagerInterface $entityManager, + private ORM $orm ) { } @@ -38,7 +42,9 @@ public function execute(iterable $seeders): void private function seed(SeederInterface $seeder): void { foreach ($seeder->run() as $entity) { - $this->entityManager->persist($entity); + if ($this->isNotExists($entity)) { + $this->entityManager->persist($entity); + } } } @@ -46,4 +52,19 @@ private function callAfterSeed(SeederInterface $seeder): void { \array_map(static fn(callable $callable) => $callable($seeder), $this->afterSeed); } + + private function isNotExists(object $entity): bool + { + $keys = $this->orm->getSchema()->define($this->orm->resolveRole($entity), SchemaInterface::PRIMARY_KEY); + + $values = []; + foreach ($keys as $key) { + $ref = new \ReflectionProperty($entity, $key); + if ($ref->isInitialized($entity)) { + $values[$key] = $ref->getValue($entity); + } + } + + return $this->orm->getRepository($entity)->findByPK(new Parameter($values)) === null; + } } diff --git a/tests/app/database/Factory/WithCompositePkFactory.php b/tests/app/database/Factory/WithCompositePkFactory.php new file mode 100644 index 0000000..a41e054 --- /dev/null +++ b/tests/app/database/Factory/WithCompositePkFactory.php @@ -0,0 +1,25 @@ + $this->faker->randomDigit(), + 'otherId' => $this->faker->randomDigit(), + 'content' => $this->faker->sentence, + ]; + } +} diff --git a/tests/app/database/Seeder/WithCompositePkSeeder.php b/tests/app/database/Seeder/WithCompositePkSeeder.php new file mode 100644 index 0000000..dfb2e37 --- /dev/null +++ b/tests/app/database/Seeder/WithCompositePkSeeder.php @@ -0,0 +1,18 @@ +createOne(); + } +} diff --git a/tests/app/src/Database/User.php b/tests/app/src/Database/User.php index e00cf6a..fff6837 100644 --- a/tests/app/src/Database/User.php +++ b/tests/app/src/Database/User.php @@ -28,7 +28,7 @@ class User #[Column(type: 'int')] public int $age; - #[Column(type: 'bool')] + #[Column(type: 'boolean', typecast: 'bool')] public bool $active; #[Column(type: 'float')] diff --git a/tests/app/src/Database/WithCompositePk.php b/tests/app/src/Database/WithCompositePk.php new file mode 100644 index 0000000..a20cec4 --- /dev/null +++ b/tests/app/src/Database/WithCompositePk.php @@ -0,0 +1,23 @@ +getContainer()->get(Executor::class); + $method = new \ReflectionMethod($executor, 'isNotExists'); + + $compositePk = WithCompositePkFactory::new()->makeOne(); + $compositePk->id = 1; + $compositePk->otherId = 3; + + $simplePk = UserFactory::new()->makeOne(); + $simplePk->id = 1; + + $this->assertTrue($method->invoke($executor, $compositePk)); + $this->assertTrue($method->invoke($executor, $simplePk)); + } + + public function testIsExists(): void + { + $executor = $this->getContainer()->get(Executor::class); + $method = new \ReflectionMethod($executor, 'isNotExists'); + + $compositePk = WithCompositePkFactory::new()->createOne(); + $simplePk = UserFactory::new()->createOne(); + + $this->assertFalse($method->invoke($executor, $compositePk)); + $this->assertFalse($method->invoke($executor, $simplePk)); + } + + public function testSeed(): void + { + $this->assertTableCount('users', 0); + + $executor = $this->getContainer()->get(Executor::class); + $executor->execute([new UserSeeder()]); + + $this->assertTableCount('users', 1); + } + + public function testSeedWithMethodCreateAndCompositePk(): void + { + $this->assertTableCount('composite_pk', 0); + + $executor = $this->getContainer()->get(Executor::class); + $executor->execute([new WithCompositePkSeeder()]); + + $this->assertTableCount('composite_pk', 1); + } +} diff --git a/tests/src/Functional/Driver/SQLite/Seeder/ExecutorTest.php b/tests/src/Functional/Driver/SQLite/Seeder/ExecutorTest.php new file mode 100644 index 0000000..70681a1 --- /dev/null +++ b/tests/src/Functional/Driver/SQLite/Seeder/ExecutorTest.php @@ -0,0 +1,17 @@ + Date: Mon, 20 Jun 2022 19:30:26 +0300 Subject: [PATCH 10/10] Add info about cli commands --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 2bd3005..edd36f5 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,25 @@ class UserTableSeeder extends AbstractSeeder } } ``` +## Console commands +The package provides console commands to quickly create a factory, seeder, and perform seeding of a test database +using seeder classes. +- The `Spiral\DatabaseSeeder\Console\Command\FactoryCommand` console command is used to create a factory. + The name of the factory is passed as an argument. +```bash +php ./app.php create:factory UserFactory +``` +- The `Spiral\DatabaseSeeder\Console\Command\SeederCommand` console command is used to create a seeder. + The name of the seeder is passed as an argument. +```bash +php ./app.php create:seeder UserSeeder +``` +- The `Spiral\DatabaseSeeder\Console\Command\SeedCommand` console command is used to perform seeding of a test database + using seeder classes. +```bash +php ./app.php db:seed +``` + ## Testing applications with database The package provides several additional features for easier testing of applications with databases.