Skip to content

Commit

Permalink
Create postGenerateComparisonSchema event
Browse files Browse the repository at this point in the history
  • Loading branch information
wmouwen committed Mar 21, 2024
1 parent 9c56071 commit 5ba0c02
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 46 deletions.
30 changes: 28 additions & 2 deletions docs/en/reference/events.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1003,8 +1003,8 @@ postGenerateSchema
~~~~~~~~~~~~~~~~~~

This event is fired after the schema instance was successfully built and before SQL queries are generated from the
schema information of ``Doctrine\DBAL\Schema\Schema``. It allows to access the full object representation of the database schema
and the EntityManager.
schema information of ``Doctrine\DBAL\Schema\Schema``. It allows to access the full object representation of the schema
and the EntityManager as mapped by the metadata attributes.

.. code-block:: php
Expand All @@ -1025,6 +1025,32 @@ and the EntityManager.
}
}
postGenerateComparisonSchema
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This event is fired after the schema instance was successfully built and before SQL queries are generated from the
schema information of ``Doctrine\DBAL\Schema\Schema``. It allows to access the full object representation of the schema
as it is introspected from the database platform.

.. code-block:: php
<?php
use Doctrine\ORM\Tools\ToolEvents;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
$test = new TestEventListener();
$evm = $em->getEventManager();
$evm->addEventListener(ToolEvents::postGenerateComparisonSchema, $test);
class TestEventListener
{
public function postGenerateComparisonSchema(GenerateSchemaEventArgs $eventArgs)
{
$schema = $eventArgs->getSchema();
$em = $eventArgs->getEntityManager();
}
}
.. _PrePersistEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PrePersistEventArgs.php
.. _PreRemoveEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PreRemoveEventArgs.php
.. _PreUpdateEventArgs: https://github.com/doctrine/orm/blob/HEAD/src/Event/PreUpdateEventArgs.php
Expand Down
4 changes: 0 additions & 4 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,6 @@ parameters:
message: '~^Parameter #1 \$command of method Symfony\\Component\\Console\\Application::add\(\) expects Symfony\\Component\\Console\\Command\\Command, Doctrine\\DBAL\\Tools\\Console\\Command\\ReservedWordsCommand given\.$~'
path: src/Tools/Console/ConsoleRunner.php

-
message: '~Strict comparison using \=\=\= between callable\(\)\: mixed and null will always evaluate to false\.~'
path: src/Tools/SchemaTool.php

# To be removed in 4.0
-
message: '#Negated boolean expression is always false\.#'
Expand Down
44 changes: 30 additions & 14 deletions src/Tools/SchemaTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -905,28 +905,44 @@ public function getUpdateSchemaSql(array $classes): array
*/
private function createSchemaForComparison(Schema $toSchema): Schema
{
$connection = $this->em->getConnection();
$connection = $this->em->getConnection();
$eventManager = $this->em->getEventManager();

// backup schema assets filter
$config = $connection->getConfiguration();
$previousFilter = $config->getSchemaAssetsFilter();
$config = $connection->getConfiguration();

if ($previousFilter === null) {
return $this->schemaManager->introspectSchema();
}
/**
* Explicitly set $previousFilter to callable|null to match DBAL v3 returned types.
*
* @psalm-var callable|null $previousFilter
*/
$previousFilter = $config->getSchemaAssetsFilter();

// whitelist assets we already know about in $toSchema, use the existing filter otherwise
$config->setSchemaAssetsFilter(static function ($asset) use ($previousFilter, $toSchema): bool {
$assetName = $asset instanceof AbstractAsset ? $asset->getName() : $asset;
if ($previousFilter !== null) {
// whitelist assets we already know about in $toSchema, use the existing filter otherwise
$config->setSchemaAssetsFilter(static function ($asset) use ($previousFilter, $toSchema): bool {
$assetName = $asset instanceof AbstractAsset ? $asset->getName() : $asset;

return $toSchema->hasTable($assetName) || $toSchema->hasSequence($assetName) || $previousFilter($asset);
});
return $toSchema->hasTable($assetName) || $toSchema->hasSequence($assetName) || $previousFilter($asset);
});
}

try {
return $this->schemaManager->introspectSchema();
$schema = $this->schemaManager->introspectSchema();
} finally {
// restore schema assets filter
$config->setSchemaAssetsFilter($previousFilter);
if ($previousFilter !== null) {
// restore schema assets filter
$config->setSchemaAssetsFilter($previousFilter);
}
}

if ($eventManager->hasListeners(ToolEvents::postGenerateComparisonSchema)) {
$eventManager->dispatchEvent(
ToolEvents::postGenerateComparisonSchema,
new GenerateSchemaEventArgs($this->em, $schema),
);
}

return $schema;
}
}
7 changes: 7 additions & 0 deletions src/Tools/ToolEvents.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,11 @@ class ToolEvents
* The EventArgs contain the EntityManager and the created Schema instance.
*/
public const postGenerateSchema = 'postGenerateSchema';

/**
* The postGenerateComparisonSchema event is triggered in SchemaTool#createSchemaForComparison()
* after a schema is generated from the current database state.
* The EventArgs contain the EntityManager and the created Schema instance.
*/
public const postGenerateComparisonSchema = 'postGenerateComparisonSchema';
}
95 changes: 69 additions & 26 deletions tests/Tests/ORM/Tools/SchemaToolTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Doctrine\Tests\ORM\Tools;

use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
Expand Down Expand Up @@ -41,9 +42,12 @@
use Doctrine\Tests\Models\NullDefault\NullDefaultColumn;
use Doctrine\Tests\OrmTestCase;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\MockObject\MockObject;

use function assert;
use function count;
use function current;
use function method_exists;

class SchemaToolTest extends OrmTestCase
{
Expand Down Expand Up @@ -147,14 +151,23 @@ public function testPassColumnOptionsToJoinColumn(): void
#[Group('DDC-283')]
public function testPostGenerateEvents(): void
{
$listener = new GenerateSchemaEventListener();
$listener = new class {
public int $tableCalls = 0;
public bool $schemaCalled = false;

public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs): void
{
$this->tableCalls++;
}

public function postGenerateSchema(GenerateSchemaEventArgs $eventArgs): void
{
$this->schemaCalled = true;
}
};

$em = $this->getTestEntityManager();
$em->getEventManager()->addEventListener(
[ToolEvents::postGenerateSchemaTable, ToolEvents::postGenerateSchema],
$listener,
);
$schemaTool = new SchemaTool($em);
$em->getEventManager()->addEventListener([ToolEvents::postGenerateSchemaTable, ToolEvents::postGenerateSchema], $listener);

$classes = [
$em->getClassMetadata(CmsAddress::class),
Expand All @@ -166,12 +179,61 @@ public function testPostGenerateEvents(): void
$em->getClassMetadata(CmsUser::class),
];

$schema = $schemaTool->getSchemaFromMetadata($classes);
$schemaTool = new SchemaTool($em);
$schemaTool->getSchemaFromMetadata($classes);

self::assertEquals(count($classes), $listener->tableCalls);
self::assertTrue($listener->schemaCalled);
}

public function testGetUpdateSchemaSqlEvents(): void
{
$em = $this->getTestEntityManager();

// Listen to events.
$em->getEventManager()->addEventListener([ToolEvents::postGenerateSchema, ToolEvents::postGenerateComparisonSchema], new class {
public function postGenerateSchema(GenerateSchemaEventArgs $eventArgs): void
{
$eventArgs->getSchema()->createTable('test_new');
}

public function postGenerateComparisonSchema(GenerateSchemaEventArgs $eventArgs): void
{
$eventArgs->getSchema()->createTable('test_old');
}
});

$introspectedSchema = new class () extends Schema {
public static bool $called = false;

/** {@inheritDoc} */
public function createTable($name)
{
self::$called = true;

return parent::createTable($name);
}
};

$schemaManager = $em->getConnection()->createSchemaManager();
self::assertInstanceOf(MockObject::class, $schemaManager);

$schemaManager
->expects(self::once())
->method('introspectSchema')
->willReturn($introspectedSchema);

$schemaTool = new SchemaTool($em);
$schemaTool->getUpdateSchemaSql([]);

self::assertTrue($introspectedSchema::$called);

$comparator = $em->getConnection()->createSchemaManager()->createComparator();

assert(method_exists($comparator, 'getOldSchema'));
self::assertEquals($introspectedSchema, $comparator->getOldSchema());
}

public function testNullDefaultNotAddedToPlatformOptions(): void
{
$em = $this->getTestEntityManager();
Expand Down Expand Up @@ -405,25 +467,6 @@ class TestEntityWithAttributeOptionsArgument
private string $test;
}

class GenerateSchemaEventListener
{
/** @var int */
public $tableCalls = 0;

/** @var bool */
public $schemaCalled = false;

public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs): void
{
$this->tableCalls++;
}

public function postGenerateSchema(GenerateSchemaEventArgs $eventArgs): void
{
$this->schemaCalled = true;
}
}

#[Table(name: 'unique_constraint_attribute_table')]
#[UniqueConstraint(name: 'uniq_hash', columns: ['hash'])]
#[Entity]
Expand Down
33 changes: 33 additions & 0 deletions tests/Tests/OrmTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\Comparator;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaConfig;
use Doctrine\DBAL\Schema\SchemaDiff;
use Doctrine\ORM\Cache\CacheConfiguration;
use Doctrine\ORM\Cache\CacheFactory;
use Doctrine\ORM\Cache\DefaultCacheFactory;
Expand Down Expand Up @@ -153,9 +156,15 @@ private function createConnectionMock(AbstractPlatform $platform): Connection

private function createPlatformMock(): AbstractPlatform
{
$schemaDiff = method_exists(SchemaDiff::class, 'toSaveSql')
? new SchemaDiff() // DBAL v3
: new SchemaDiff([], [], [], [], [], [], [], []); // DBAL v4

$schemaManager = $this->createMock(AbstractSchemaManager::class);
$schemaManager->method('createSchemaConfig')
->willReturn(new SchemaConfig());
$schemaManager->method('createComparator')
->willReturn($this->mockComparator($schemaDiff));

$platform = $this->getMockBuilder(AbstractPlatform::class)
->onlyMethods(['supportsIdentityColumns', 'createSchemaManager'])
Expand All @@ -168,6 +177,30 @@ private function createPlatformMock(): AbstractPlatform
return $platform;
}

private function mockComparator(SchemaDiff $schemaDiff): Comparator
{
$comparator = new class (self::createStub(AbstractPlatform::class)) extends Comparator {
public static SchemaDiff $schemaDiff;
public Schema|null $oldSchema = null;

public function compareSchemas(Schema $oldSchema, Schema $newSchema): SchemaDiff
{
$this->oldSchema = $oldSchema;

return self::$schemaDiff;
}

public function getOldSchema(): Schema|null
{
return $this->oldSchema;
}
};

$comparator::$schemaDiff = $schemaDiff;

return $comparator;
}

private function createDriverMock(AbstractPlatform $platform): Driver
{
$result = $this->createMock(Result::class);
Expand Down

0 comments on commit 5ba0c02

Please sign in to comment.