Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: make "database reset" mechanism extendable #690

Merged
merged 4 commits into from
Oct 23, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor(reset database): add dama decorator
  • Loading branch information
nikophil committed Oct 22, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit 91ac05d644bb649086d79a40a430398c9dbfdd3f
24 changes: 22 additions & 2 deletions config/orm.php
Original file line number Diff line number Diff line change
@@ -2,11 +2,14 @@

namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use DAMA\DoctrineTestBundle\Doctrine\DBAL\StaticDriver;
use Zenstruck\Foundry\ORM\DoctrineOrmVersionGuesser;
use Zenstruck\Foundry\ORM\OrmDatabaseResetter;
use Zenstruck\Foundry\ORM\OrmSchemaResetter;
use Zenstruck\Foundry\ORM\OrmV2PersistenceStrategy;
use Zenstruck\Foundry\ORM\OrmV3PersistenceStrategy;
use Zenstruck\Foundry\ORM\ResetDatabase\DamaDatabaseResetter;
use Zenstruck\Foundry\ORM\ResetDatabase\DamaSchemaResetter;
use Zenstruck\Foundry\ORM\ResetDatabase\OrmDatabaseResetter;
use Zenstruck\Foundry\ORM\ResetDatabase\OrmSchemaResetter;

return static function (ContainerConfigurator $container): void {
$container->services()
@@ -31,4 +34,21 @@
])
->tag('.foundry.persistence.schema_resetter')
;

if (\class_exists(StaticDriver::class)) {
$container->services()
->set('.zenstruck_foundry.persistence.database_resetter.orm.dama', DamaDatabaseResetter::class)
->decorate('.zenstruck_foundry.persistence.database_resetter.orm')
->args([
service('.inner'),
])
->tag('.foundry.persistence.database_resetter')
->set('.zenstruck_foundry.persistence.schema_resetter.orm.dama', DamaSchemaResetter::class)
->decorate('.zenstruck_foundry.persistence.schema_resetter.orm')
->args([
service('.inner'),
])
->tag('.foundry.persistence.schema_resetter')
;
}
};
5 changes: 5 additions & 0 deletions config/persistence.php
Original file line number Diff line number Diff line change
@@ -3,12 +3,17 @@
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use Zenstruck\Foundry\Persistence\PersistenceManager;
use Zenstruck\Foundry\Persistence\ResetDatabase\ResetDatabaseManager;

return static function (ContainerConfigurator $container): void {
$container->services()
->set('.zenstruck_foundry.persistence_manager', PersistenceManager::class)
->args([
tagged_iterator('.foundry.persistence_strategy'),
service('.zenstruck_foundry.persistence.reset_database_manager'),
])
->set('.zenstruck_foundry.persistence.reset_database_manager', ResetDatabaseManager::class)
->args([
tagged_iterator('.foundry.persistence.database_resetter'),
tagged_iterator('.foundry.persistence.schema_resetter'),
])
3 changes: 3 additions & 0 deletions src/Configuration.php
Original file line number Diff line number Diff line change
@@ -49,6 +49,9 @@ public function __construct(
$this->instantiator = $instantiator;
}

/**
* @throws PersistenceNotAvailable
*/
public function persistence(): PersistenceManager
{
return $this->persistence ?? throw new PersistenceNotAvailable('No persistence managers configured. Note: persistence cannot be used in unit tests.');
39 changes: 0 additions & 39 deletions src/Mongo/MongoPersistenceStrategy.php
Original file line number Diff line number Diff line change
@@ -89,43 +89,4 @@ public function isEmbeddable(object $object): bool
{
return $this->objectManagerFor($object::class)->getClassMetadata($object::class)->isEmbeddedDocument;
}

public function resetDatabase(KernelInterface $kernel): void
{
// noop
}

public function resetSchema(KernelInterface $kernel): void
{
$application = self::application($kernel);

foreach ($this->managers() as $manager) {
try {
self::runCommand(
$application,
'doctrine:mongodb:schema:drop',
[
'--dm' => $manager,
]
);
} catch (\Exception) {
}

self::runCommand(
$application,
'doctrine:mongodb:schema:create',
[
'--dm' => $manager,
]
);
}
}

/**
* @return string[]
*/
private function managers(): array
{
return $this->config['reset']['document_managers'];
}
}
117 changes: 1 addition & 116 deletions src/ORM/AbstractORMPersistenceStrategy.php
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@
use Symfony\Component\HttpKernel\KernelInterface;
use Zenstruck\Foundry\Persistence\PersistenceManager;
use Zenstruck\Foundry\Persistence\PersistenceStrategy;
use Zenstruck\Foundry\Persistence\ResetDatabase\ResetDatabaseHandler;
use Zenstruck\Foundry\Persistence\ResetDatabase\ResetDatabaseManager;

/**
* @author Kevin Bond <kevinbond@gmail.com>
@@ -87,27 +87,6 @@ final public function isEmbeddable(object $object): bool
return $this->objectManagerFor($object::class)->getClassMetadata($object::class)->isEmbeddedClass;
}

final public function resetDatabase(KernelInterface $kernel): void
{
$application = self::application($kernel);

$this->dropAndResetDatabase($application);
$this->createSchema($application);
}

final public function resetSchema(KernelInterface $kernel): void
{
if (ResetDatabaseHandler::isDAMADoctrineTestBundleEnabled()) {
// not required as the DAMADoctrineTestBundle wraps each test in a transaction
return;
}

$application = self::application($kernel);

$this->dropSchema($application);
$this->createSchema($application);
}

final public function managedNamespaces(): array
{
$namespaces = [];
@@ -118,98 +97,4 @@ final public function managedNamespaces(): array

return \array_values(\array_merge(...$namespaces));
}

private function dropAndResetDatabase(Application $application): void
{
foreach ($this->connections() as $connection) {
$databasePlatform = $this->registry->getConnection($connection)->getDatabasePlatform(); // @phpstan-ignore method.notFound

if ($databasePlatform instanceof SQLitePlatform) {
// we don't need to create the sqlite database - it's created when the schema is created
continue;
}

if ($databasePlatform instanceof PostgreSQLPlatform) {
// let's drop all connections to the database to be able to drop it
self::runCommand(
$application,
'dbal:run-sql',
[
'--connection' => $connection,
'sql' => 'SELECT pid, pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = current_database() AND pid <> pg_backend_pid()',
],
canFail: true,
);
}

self::runCommand($application, 'doctrine:database:drop', [
'--connection' => $connection,
'--force' => true,
'--if-exists' => true,
]);
self::runCommand($application, 'doctrine:database:create', ['--connection' => $connection]);
}
}

private function createSchema(Application $application): void
{
if (self::RESET_MODE_SCHEMA === $this->config['reset']['mode']) {
foreach ($this->managers() as $manager) {
self::runCommand($application, 'doctrine:schema:update', [
'--em' => $manager,
'--force' => true,
]);
}

return;
}

if (!$migrationsConfigurations = $this->config['reset']['migrations']['configurations']) {
self::runCommand($application, 'doctrine:migrations:migrate', [
'--no-interaction' => true,
]);

return;
}

foreach ($migrationsConfigurations as $migrationsConfiguration) {
self::runCommand($application, 'doctrine:migrations:migrate', [
'--configuration' => $migrationsConfiguration,
'--no-interaction' => true,
]);
}
}

private function dropSchema(Application $application): void
{
if (self::RESET_MODE_MIGRATE === $this->config['reset']['mode']) {
$this->dropAndResetDatabase($application);

return;
}

foreach ($this->managers() as $manager) {
self::runCommand($application, 'doctrine:schema:drop', [
'--em' => $manager,
'--force' => true,
'--full-database' => true,
]);
}
}

/**
* @return string[]
*/
private function managers(): array
{
return $this->config['reset']['entity_managers'];
}

/**
* @return string[]
*/
private function connections(): array
{
return $this->config['reset']['connections'];
}
}
52 changes: 52 additions & 0 deletions src/ORM/ResetDatabase/DamaDatabaseResetter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace Zenstruck\Foundry\ORM\ResetDatabase;

use DAMA\DoctrineTestBundle\Doctrine\DBAL\StaticDriver;
use Symfony\Component\HttpKernel\KernelInterface;
use Zenstruck\Foundry\Configuration;
use Zenstruck\Foundry\Persistence\PersistenceManager;
use Zenstruck\Foundry\Persistence\ResetDatabase\DatabaseResetterInterface;
use Zenstruck\Foundry\Persistence\ResetDatabase\ResetDatabaseManager;

/**
* @internal
* @author Nicolas PHILIPPE <nikophil@gmail.com>
*/
final class DamaDatabaseResetter implements DatabaseResetterInterface
{
public function __construct(
private DatabaseResetterInterface $decorated,
) {
}

public function resetDatabase(KernelInterface $kernel): void
{
$isDAMADoctrineTestBundleEnabled = ResetDatabaseManager::isDAMADoctrineTestBundleEnabled();

if (!$isDAMADoctrineTestBundleEnabled) {
$this->decorated->resetDatabase($kernel);

return;
}

// disable static connections for this operation
StaticDriver::setKeepStaticConnections(false);

$this->decorated->resetDatabase($kernel);

if (PersistenceManager::isOrmOnly()) {
// add global stories so they are available after transaction rollback
Configuration::instance()->stories->loadGlobalStories();
}

// shutdown kernel before re-enabling static connections
// this would prevent any error if any ResetInterface execute sql queries (example: symfony/doctrine-messenger)
$kernel->shutdown();
nikophil marked this conversation as resolved.
Show resolved Hide resolved

// re-enable static connections
StaticDriver::setKeepStaticConnections(true);
}
}
31 changes: 31 additions & 0 deletions src/ORM/ResetDatabase/DamaSchemaResetter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace Zenstruck\Foundry\ORM\ResetDatabase;

use Symfony\Component\HttpKernel\KernelInterface;
use Zenstruck\Foundry\Persistence\ResetDatabase\ResetDatabaseManager;
use Zenstruck\Foundry\Persistence\ResetDatabase\SchemaResetterInterface;

/**
* @internal
* @author Nicolas PHILIPPE <nikophil@gmail.com>
*/
final class DamaSchemaResetter implements SchemaResetterInterface
{
public function __construct(
private SchemaResetterInterface $decorated,
) {
}

public function resetSchema(KernelInterface $kernel): void
{
if (ResetDatabaseManager::isDAMADoctrineTestBundleEnabled()) {
// not required as the DAMADoctrineTestBundle wraps each test in a transaction
return;
}

$this->decorated->resetSchema($kernel);
}
}
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Zenstruck\Foundry\ORM;
namespace Zenstruck\Foundry\ORM\ResetDatabase;

use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpKernel\KernelInterface;
@@ -42,11 +42,17 @@ private function registry(): ManagerRegistry
return $this->registry;
}

/**
* @return list<string>
*/
private function managers(): array
{
return $this->managers;
}

/**
* @return list<string>
*/
private function connections(): array
{
return $this->connections;
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Zenstruck\Foundry\ORM;
namespace Zenstruck\Foundry\ORM\ResetDatabase;

use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Platforms\SQLitePlatform;
@@ -43,6 +43,7 @@ private function dropAndResetDatabase(Application $application): void
'--force' => true,
'--if-exists' => true,
]);

self::runCommand($application, 'doctrine:database:create', ['--connection' => $connection]);
}
}
Loading