diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 68a71b7..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,18 +0,0 @@ -# CHANGELOG - -v1.1.0 (20.01.2022) -------------------- -- Add `SourceInterface $source` property to the `MapperEvent`by @msmakouz (#18) -- Add nullable parameter to the updatedAt behavior by @msmakouz (#23) - -v1.0.0 (22.12.2021) -------------------- -- Add behaviors: - - `CreatedAt` - - `OptimisticLock` - - `SoftDelete` - - `UpdatedAt` - - `EventListener` - - `Hook` -- Add `EventDrivenCommandGenerator` with custom event dispatcher. -- Supported events: `OnCreate`, `OnDelete` and `OnUpdate` diff --git a/src/OptimisticLock.php b/src/OptimisticLock.php index 1ed35b7..f9c181f 100644 --- a/src/OptimisticLock.php +++ b/src/OptimisticLock.php @@ -59,7 +59,7 @@ public function __construct( private string $field = 'version', ?string $column = null, /** @Enum({"microtime", "random-string", "increment", "datetime"}) */ - #[ExpectedValues(valuesFromClass: Listener::class)] + #[ExpectedValues(valuesFromClass: self::class)] private ?string $rule = null, ) { $this->column = $column; diff --git a/tests/Behavior/Functional/Driver/Common/BaseTest.php b/tests/Behavior/Functional/Driver/Common/BaseTest.php index 087c08d..87de2cc 100644 --- a/tests/Behavior/Functional/Driver/Common/BaseTest.php +++ b/tests/Behavior/Functional/Driver/Common/BaseTest.php @@ -37,7 +37,7 @@ public function getDriver(): DriverInterface return static::$driverCache[static::DRIVER] = $this->driver; } - public function setUp(): void + protected function setUp(): void { $this->setUpLogger($this->getDriver()); if (self::$config['debug'] ?? false) { diff --git a/tests/Behavior/Functional/Driver/Common/Integration/BaseTest.php b/tests/Behavior/Functional/Driver/Common/Integration/BaseTest.php new file mode 100644 index 0000000..b2c681a --- /dev/null +++ b/tests/Behavior/Functional/Driver/Common/Integration/BaseTest.php @@ -0,0 +1,109 @@ +disableProfiling(); + unset($this->orm, $this->schema); + parent::tearDown(); + } + + /** + * @param non-empty-array|non-empty-string $entitiesPath + */ + protected function prepareOrm(string|array $entitiesPath): void + { + $tokenizer = new Tokenizer(new TokenizerConfig([ + 'directories' => (array) $entitiesPath, + 'exclude' => [], + ])); + + $reader = new AttributeReader(); + + $classLocator = $tokenizer->classLocator(); + + $this->schema = new Schema((new Compiler())->compile(new Registry($this->dbal), [ + new ResetTables(), + new Embeddings($classLocator, $reader), + new Entities($classLocator, $reader), + new TableInheritance($reader), + new MergeColumns($reader), + new GenerateRelations(), + new GenerateModifiers(), + new ValidateEntities(), + new RenderTables(), + new RenderRelations(), + new RenderModifiers(), + new ForeignKeys(), + new MergeIndexes($reader), + new SyncTables(), + new GenerateTypecast(), + ])); + + $this->orm = new ORM( + new Factory( + $this->dbal, + RelationConfig::getDefault(), + null, + new ArrayCollectionFactory(), + ), + $this->schema, + new EventDrivenCommandGenerator($this->schema, new SimpleContainer()), + ); + } + + protected function save(object ...$entities): void + { + $uow = new UnitOfWork($this->orm); + + foreach ($entities as $entity) { + $uow->persistDeferred($entity); + } + $result = $uow->run(); + $result->isSuccess() or throw $uow->getLastError(); + } + + protected function cleanHeap(): void + { + $this->orm->getHeap()->clean(); + } +} diff --git a/tests/Behavior/Functional/Driver/Common/Integration/CaseTemplate/CaseTest.php b/tests/Behavior/Functional/Driver/Common/Integration/CaseTemplate/CaseTest.php new file mode 100644 index 0000000..e1ec1f7 --- /dev/null +++ b/tests/Behavior/Functional/Driver/Common/Integration/CaseTemplate/CaseTest.php @@ -0,0 +1,130 @@ +orm, Entity\User::class)) + ->load('posts') + ->wherePK(2) + ->fetchOne(); + \assert($user instanceof Entity\User); + + // Check results + self::assertNotNull($user->createdAt); + self::assertNotNull($user->updatedAt); + self::assertSame(0, $user->createdAt <=> $user->updatedAt); + } + + public function testCreatedAt(): void + { + // Get entity + $user = $this->orm->make(Entity\User::class, ['login' => 'new-user', 'passwordHash' => '123456789']); + + self::assertFalse(isset($user->createdAt)); + $this->save($user); + + self::assertNotNull($user->createdAt); + + // Reload entity + $id = $user->id; + $this->cleanHeap(); + $user = (new Select($this->orm, Entity\User::class)) + ->wherePK($id) + ->fetchOne(); + self::assertNotNull($user->createdAt); + } + + public function testUpdatedAt(): void + { + // Get entity + $user = (new Select($this->orm, Entity\User::class)) + ->wherePK(2) + ->fetchOne(); + \assert($user instanceof Entity\User); + $updatedAt = $user->updatedAt; + + // Change data + $user->passwordHash = 'new-password-hash'; + $this->save($user); + + // Check results + self::assertSame(1, $user->updatedAt <=> $updatedAt); + } + + protected function setUp(): void + { + // Init DB + parent::setUp(); + $this->prepareOrm(__DIR__ . '/Entity'); + $this->save(...$this->fillData()); + $this->orm->getHeap()->clean(); + } + + private function fillData(): iterable + { + /** + * @var callable(class-string, array): object $c + */ + $c = \Closure::fromCallable([$this->orm, 'make']); + + // Users + $u1 = $c(Entity\User::class, ['login' => 'user-1', 'passwordHash' => '123456789']); + $u2 = $c(Entity\User::class, ['login' => 'user-2', 'passwordHash' => '852741963']); + $u3 = $c(Entity\User::class, ['login' => 'user-3', 'passwordHash' => '321654987']); + $u4 = $c(Entity\User::class, ['login' => 'user-4', 'passwordHash' => '321456987']); + + // Posts + $p1 = $c(Entity\Post::class, ['slug' => 'slug-string-1', 'title' => 'Title 1', 'public' => true, 'content' => 'Foo-bar-baz content 1']); + $p2 = $c(Entity\Post::class, ['slug' => 'slug-string-2', 'title' => 'Title 2', 'public' => true, 'content' => 'Foo-bar-baz content 2']); + $p3 = $c(Entity\Post::class, ['slug' => 'slug-string-3', 'title' => 'Title 3', 'public' => false, 'content' => 'Foo-bar-baz content 3']); + $p4 = $c(Entity\Post::class, ['slug' => 'slug-string-4', 'title' => 'Title 4', 'public' => true, 'content' => 'Foo-bar-baz content 4']); + $p5 = $c(Entity\Post::class, ['slug' => 'slug-string-5', 'title' => 'Title 5', 'public' => true, 'content' => 'Foo-bar-baz content 5']); + $p6 = $c(Entity\Post::class, ['slug' => 'slug-string-6', 'title' => 'Title 6', 'public' => true, 'content' => 'Foo-bar-baz content 6']); + + // Link posts with users + $u1->posts = [$p1]; + $u2->posts = [$p2, $p3]; + $u3->posts = [$p4, $p5, $p6]; + + // Comments + $c1 = $c(Entity\Comment::class, ['post' => $p1, 'user' => $u2, 'content' => 'Foo-bar-baz comment 1', 'public' => true]); + $c2 = $c(Entity\Comment::class, ['post' => $p1, 'user' => $u1, 'content' => 'Reply to comment 1', 'public' => true]); + $c3 = $c(Entity\Comment::class, ['post' => $p2, 'user' => $u1, 'content' => 'Foo-bar-baz comment 2', 'public' => true]); + $c4 = $c(Entity\Comment::class, ['post' => $p2, 'user' => $u2, 'content' => 'Reply to comment 2', 'public' => true]); + $c5 = $c(Entity\Comment::class, ['post' => $p2, 'user' => $u3, 'content' => 'Foo-bar-baz comment 3', 'public' => true]); + $c6 = $c(Entity\Comment::class, ['post' => $p2, 'user' => $u4, 'content' => 'Hidden comment', 'public' => false]); + $c7 = $c(Entity\Comment::class, ['post' => $p2, 'user' => $u4, 'content' => 'Yet another comment', 'public' => true]); + + // Tags + $t1 = $c(Entity\Tag::class, ['label' => 'tag-1']); + $t2 = $c(Entity\Tag::class, ['label' => 'tag-2']); + $t3 = $c(Entity\Tag::class, ['label' => 'tag-3']); + $t4 = $c(Entity\Tag::class, ['label' => 'tag-4']); + $t5 = $c(Entity\Tag::class, ['label' => 'tag-5']); + + // Link tags with posts + $t1->posts = [$p1, $p2]; + $t2->posts = [$p2, $p3]; + $t3->posts = [$p3, $p4, $p5]; + $t4->posts = [$p4, $p5]; + $t5->posts = [$p5, $p6, $p1]; + + // yield all the entities + yield from [$u1, $u2, $u3, $u4]; + yield from [$p1, $p2, $p3, $p4, $p5, $p6]; + yield from [$c1, $c2, $c3, $c4, $c5, $c6, $c7]; + yield from [$t1, $t2, $t3, $t4, $t5]; + } +} diff --git a/tests/Behavior/Functional/Driver/Common/Integration/CaseTemplate/Entity/Comment.php b/tests/Behavior/Functional/Driver/Common/Integration/CaseTemplate/Entity/Comment.php new file mode 100644 index 0000000..95e547b --- /dev/null +++ b/tests/Behavior/Functional/Driver/Common/Integration/CaseTemplate/Entity/Comment.php @@ -0,0 +1,51 @@ + */ + #[ManyToMany(target: Tag::class, innerKey: 'id', outerKey: 'id', throughInnerKey: 'postId', throughOuterKey: 'tagId', through: PostTag::class)] + public iterable $tags = []; + + /** @var iterable */ + #[HasMany(target: Comment::class, innerKey: 'id', outerKey: 'postId', fkCreate: false)] + public iterable $comments = []; + + private function __construct() {} +} diff --git a/tests/Behavior/Functional/Driver/Common/Integration/CaseTemplate/Entity/PostTag.php b/tests/Behavior/Functional/Driver/Common/Integration/CaseTemplate/Entity/PostTag.php new file mode 100644 index 0000000..f26ca38 --- /dev/null +++ b/tests/Behavior/Functional/Driver/Common/Integration/CaseTemplate/Entity/PostTag.php @@ -0,0 +1,20 @@ + */ + #[ManyToMany(target: Post::class, innerKey: 'id', outerKey: 'id', throughInnerKey: 'tagId', throughOuterKey: 'postId', through: PostTag::class)] + public iterable $posts = []; + + private function __construct() {} +} diff --git a/tests/Behavior/Functional/Driver/Common/Integration/CaseTemplate/Entity/User.php b/tests/Behavior/Functional/Driver/Common/Integration/CaseTemplate/Entity/User.php new file mode 100644 index 0000000..736fc1d --- /dev/null +++ b/tests/Behavior/Functional/Driver/Common/Integration/CaseTemplate/Entity/User.php @@ -0,0 +1,41 @@ + */ + #[HasMany(target: Post::class, innerKey: 'id', outerKey: 'userId', fkCreate: false)] + public iterable $posts = []; + + /** @var iterable */ + #[HasMany(target: Comment::class, innerKey: 'id', outerKey: 'userId', fkCreate: false)] + public iterable $comments = []; + + private function __construct() {} +} diff --git a/tests/Behavior/Functional/Driver/MySQL/Integration/CaseTemplate/CaseTest.php b/tests/Behavior/Functional/Driver/MySQL/Integration/CaseTemplate/CaseTest.php new file mode 100644 index 0000000..2830edd --- /dev/null +++ b/tests/Behavior/Functional/Driver/MySQL/Integration/CaseTemplate/CaseTest.php @@ -0,0 +1,17 @@ +getRealPath(); + $target = \substr($filePath, \strlen($caseTemplateDir)); + + // creating directory... + $dirName = \dirname($copyDir . $target); + if (!\is_dir($dirName)) { + \mkdir($dirName, recursive: true); + } + + $contents = \str_replace($caseTemplateDirName, \basename($copyDir), \file_get_contents($filePath)); + \file_put_contents($copyDir . $target, $contents); + } +} + +$options = \getopt('', [ + 'case-name:', + 'template:', +]); + +$copyDir = isset($options['case-name']) + ? $integrationDir . DIRECTORY_SEPARATOR . $options['case-name'] + : defaultCaseName($integrationDir); + +if (\file_exists($copyDir)) { + echo "Error. Tests folder `$copyDir` already exists\n"; + exit(1); +} + +echo \sprintf("Generating new test case '%s'... \n", \basename($copyDir)); + +\mkdir($copyDir); +$copyDir = \realpath($copyDir); + +copyTemplateFiles($copyDir, $options['template'] ?? 'CaseTemplate'); + +require 'generate.php'; + +echo "Done. New test case is here:\n$copyDir\n";