Skip to content

Commit

Permalink
Merge pull request #31 from spiral-packages/feature/transaction-strategy
Browse files Browse the repository at this point in the history
Add TransactionStrategy, add helper trait
  • Loading branch information
butschster authored Nov 9, 2023
2 parents 4312449 + e743526 commit b537345
Show file tree
Hide file tree
Showing 11 changed files with 458 additions and 28 deletions.
20 changes: 13 additions & 7 deletions src/Database/Strategy/MigrationStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@
*/
class MigrationStrategy
{
/**
* @param TestCase $testCase
* @param bool $createMigrations
*/
public function __construct(
protected TestCase $testCase,
protected bool $createMigrations = false
Expand All @@ -44,9 +40,7 @@ public function rollback(): void
$this->testCase->runCommand('migrate:rollback', ['--all' => true]);

if ($this->createMigrations) {
$dir = $this->getMigrationsDirectory();
$this->testCase->cleanupDirectories($dir);
$this->testCase->getContainer()->get(FilesInterface::class)->ensureDirectory($dir, 0666);
$this->deleteMigrations();
}

DatabaseState::$migrated = false;
Expand All @@ -62,6 +56,18 @@ public function enableCreationMigrations(): void
$this->createMigrations = true;
}

public function isCreateMigrations(): bool
{
return $this->createMigrations;
}

public function deleteMigrations(): void
{
$dir = $this->getMigrationsDirectory();
$this->testCase->cleanupDirectories($dir);
$this->testCase->getContainer()->get(FilesInterface::class)->ensureDirectory($dir, 0666);
}

protected function getMigrationsDirectory(): string
{
$config = $this->testCase->getConfig('migration');
Expand Down
41 changes: 41 additions & 0 deletions src/Database/Strategy/TransactionStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Spiral\DatabaseSeeder\Database\Strategy;

use Cycle\Database\DatabaseInterface;
use Spiral\Testing\TestCase;

/**
* This strategy utilizes MigrationStrategy without executing migration and rolling back migration for each test
* but performs migration prior to running the first test. It wraps the test execution in a transaction before each
* test.
*/
class TransactionStrategy
{
protected MigrationStrategy $migrationStrategy;

public function __construct(
protected TestCase $testCase,
?MigrationStrategy $migrationStrategy = null
) {
$this->migrationStrategy = $migrationStrategy ?? new MigrationStrategy($this->testCase);
}

public function begin(): void
{
$this->migrationStrategy->migrate();

if ($this->migrationStrategy->isCreateMigrations()) {
$this->migrationStrategy->deleteMigrations();
}

$this->testCase->getContainer()->get(DatabaseInterface::class)->getDriver()->beginTransaction();
}

public function rollback(): void
{
$this->testCase->getContainer()->get(DatabaseInterface::class)->getDriver()->rollbackTransaction();
}
}
72 changes: 72 additions & 0 deletions src/Database/Traits/Helper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

namespace Spiral\DatabaseSeeder\Database\Traits;

use Cycle\Database\DatabaseInterface;
use Cycle\Database\DatabaseProviderInterface;
use Cycle\Database\Driver\DriverInterface;
use Cycle\ORM\EntityManagerInterface;
use Cycle\ORM\ORMInterface;
use Cycle\ORM\RepositoryInterface;
use Spiral\DatabaseSeeder\Database\Cleaner;

trait Helper
{
private ?Cleaner $cleaner = null;

public function getDatabaseCleaner(): Cleaner
{
if ($this->cleaner === null) {
$this->cleaner = new Cleaner($this->getCurrentDatabaseProvider());
}

return $this->cleaner;
}

public function getCurrentDatabase(): DatabaseInterface
{
return $this->getContainer()->get(DatabaseInterface::class);
}

public function getCurrentDatabaseDriver(): DriverInterface
{
return $this->getCurrentDatabase()->getDriver();
}

public function getCurrentDatabaseProvider(): DatabaseProviderInterface
{
return $this->getContainer()->get(DatabaseProviderInterface::class);
}

public function getOrm(): ORMInterface
{
return $this->getContainer()->get(ORMInterface::class);
}

public function getEntityManager(): EntityManagerInterface
{
return $this->getContainer()->get(EntityManagerInterface::class);
}

public function detachEntityFromIdentityMap(object $entity): void
{
$this->getOrm()->getHeap()->detach($entity);
}

public function cleanIdentityMap(): void
{
$this->getOrm()->getHeap()->clean();
}

public function getRepositoryFor(object|string $entity): RepositoryInterface
{
return $this->getOrm()->getRepository($entity);
}

public function persist(object $entity): void
{
$this->getEntityManager()->persist($entity)->run();
}
}
82 changes: 82 additions & 0 deletions src/Database/Traits/Transactions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

declare(strict_types=1);

namespace Spiral\DatabaseSeeder\Database\Traits;

use Spiral\DatabaseSeeder\Database\Strategy\TransactionStrategy;

trait Transactions
{
private ?TransactionStrategy $transactionStrategy = null;


public function beginTransaction(): void
{
$this->beforeBeginTransaction();

$this->getTransactionStrategy()->begin();

$this->afterBeginTransaction();
}

public function rollbackTransaction(): void
{
$this->beforeRollbackTransaction();

$this->getTransactionStrategy()->rollback();

$this->afterRollbackTransaction();
}

protected function setUpTransactions(): void
{
$this->beginTransaction();
}

protected function tearDownTransactions(): void
{
$this->rollbackTransaction();
}

protected function getTransactionStrategy(): TransactionStrategy
{
if ($this->transactionStrategy === null) {
$this->transactionStrategy = new TransactionStrategy(testCase: $this);
}

return $this->transactionStrategy;
}

/**
* Perform any work before the database transaction has started
*/
protected function beforeBeginTransaction(): void
{
// ...
}

/**
* Perform any work after the database transaction has started
*/
protected function afterBeginTransaction(): void
{
// ...
}

/**
* Perform any work before rolling back the transaction
*/
protected function beforeRollbackTransaction(): void
{
// ...
}

/**
* Perform any work after rolling back the transaction
*/
protected function afterRollbackTransaction(): void
{
// ...
}
}
3 changes: 3 additions & 0 deletions src/Factory/AbstractFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Butschster\EntityFaker\EntityFactory\InstanceWithoutConstructorStrategy;
use Closure;
use Cycle\ORM\EntityManagerInterface;
use Cycle\ORM\ORMInterface;
use Faker\Factory as FakerFactory;
use Faker\Generator;
use Laminas\Hydrator\ReflectionHydrator;
Expand Down Expand Up @@ -185,6 +186,8 @@ private function storeEntities(array $entities): void
$em->persist($entity);
}
$em->run();

$container->get(ORMInterface::class)->getHeap()->clean();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,16 @@ public function testMigrateWithCreatingMigrations(): void
$this->assertTable('composite_pk')->assertColumnExists('content');
$this->assertTable('composite_pk')->assertEmpty();

$this->assertSame(0, $this->getCurrentDatabaseDriver()->getTransactionLevel());

$strategy->rollback();

$this->assertTable('comments')->assertMissing();
$this->assertTable('posts')->assertMissing();
$this->assertTable('users')->assertMissing();
$this->assertTable('composite_pk')->assertMissing();

$this->assertSame(0, $this->getCurrentDatabaseDriver()->getTransactionLevel());
}

public function testMigrateWithoutCreatingMigrations(): void
Expand All @@ -68,9 +72,13 @@ public function testMigrateWithoutCreatingMigrations(): void
$strategy = new MigrationStrategy($this, false);
$strategy->migrate();

$this->assertSame(0, $this->getCurrentDatabaseDriver()->getTransactionLevel());

$this->assertTable('comments')->assertMissing();
$this->assertTable('posts')->assertMissing();
$this->assertTable('users')->assertMissing();
$this->assertTable('composite_pk')->assertMissing();

$this->assertSame(0, $this->getCurrentDatabaseDriver()->getTransactionLevel());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Tests\Functional\Database\Strategy;

use Cycle\Database\DatabaseInterface;
use Spiral\DatabaseSeeder\Database\Strategy\MigrationStrategy;
use Spiral\DatabaseSeeder\Database\Strategy\RefreshStrategy;
use Spiral\DatabaseSeeder\Database\Traits\DatabaseAsserts;
Expand All @@ -18,7 +17,7 @@ final class RefreshStrategyTest extends TestCase

public function testRefreshStrategy(): void
{
$db = $this->getContainer()->get(DatabaseInterface::class);
$db = $this->getCurrentDatabase();
$schema = $db->table('users')->getSchema();
$schema->primary('id');
$schema->datetime('birthday')->nullable();
Expand All @@ -36,12 +35,12 @@ public function testRefreshStrategy(): void

$this->assertTable('users')->assertCountRecords(5);

$strategy = new RefreshStrategy($this->cleaner);
$strategy = new RefreshStrategy($this->getDatabaseCleaner());
$strategy->refresh();

$this->assertTable('users')->assertEmpty();

$this->cleaner->dropTable('users');
$this->getDatabaseCleaner()->dropTable('users');
}

public function testRefreshStrategyWithExceptTable(): void
Expand All @@ -56,7 +55,7 @@ public function testRefreshStrategyWithExceptTable(): void
$this->assertTable('posts')->assertCountRecords(1);
$this->assertTable('comments')->assertCountRecords(3);

$strategy = new RefreshStrategy($this->cleaner, except: ['comments', 'migrations']);
$strategy = new RefreshStrategy($this->getDatabaseCleaner(), except: ['comments', 'migrations']);
$strategy->refresh();

$this->assertTable('users')->assertEmpty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Tests\Functional\Database\Strategy;

use Cycle\Database\DatabaseProviderInterface;
use Spiral\DatabaseSeeder\Database\Strategy\SqlFileStrategy;
use Spiral\DatabaseSeeder\Database\Traits\DatabaseAsserts;
use Tests\Functional\TestCase;
Expand All @@ -19,14 +18,14 @@ public function testExecute(): void

$strategy = new SqlFileStrategy(
\dirname(__DIR__, 4) . '/app/database/sql/execute.sql',
$this->getContainer()->get(DatabaseProviderInterface::class)
$this->getCurrentDatabaseProvider()
);
$strategy->execute();

$this->assertTable('users')->assertExists();
$this->assertTable('users')->assertCountRecords(5);

$this->cleaner->dropTable('users');
$this->getDatabaseCleaner()->dropTable('users');
}

public function testExecuteAndDrop(): void
Expand All @@ -35,7 +34,7 @@ public function testExecuteAndDrop(): void

$strategy = new SqlFileStrategy(
\dirname(__DIR__, 4) . '/app/database/sql/execute.sql',
$this->getContainer()->get(DatabaseProviderInterface::class),
$this->getCurrentDatabaseProvider(),
\dirname(__DIR__, 4) . '/app/database/sql/drop.sql',
);
$strategy->execute();
Expand Down
Loading

0 comments on commit b537345

Please sign in to comment.