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

Draft for an event with all information after schema change was perfomed #11409

Draft
wants to merge 5 commits into
base: 3.2.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
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
31 changes: 31 additions & 0 deletions docs/en/reference/events.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1025,6 +1025,37 @@ and the EntityManager.
}
}

postSchemaChange
~~~~~~~~~~~~~~~~

This event is fired after the schema was successfully updated. It allows
to access the full object representation of the database schema before
and after the change, as well as the EntityManager and the SQL queries
that were executed.

.. code-block:: php

<?php

use Doctrine\ORM\Tools\ToolEvents;
use Doctrine\ORM\Tools\Event\SchemaChangedEventArgs;

$test = new TestEventListener();
$evm = $em->getEventManager();
$evm->addEventListener(ToolEvents::postSchemaChanged, $test);

class TestEventListener
{
public function postGenerateSchema(SchemaChangedEventArgs $eventArgs)
{
$schema = $eventArgs->getSchema();
$oldSchema = $eventArgs->getOldSchema();
$em = $eventArgs->getEntityManager();
$sqls = $eventArgs->getSqls();
}
}


.. _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
42 changes: 42 additions & 0 deletions src/Tools/Event/SchemaChangedEventArgs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Tools\Event;

use Doctrine\Common\EventArgs;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\ORM\EntityManagerInterface;

class SchemaChangedEventArgs extends EventArgs
{
/** @param array<string> $sqls */
public function __construct(
private readonly EntityManagerInterface $em,
private readonly Schema $schema,
private readonly Schema $oldSchema,
private readonly array $sqls,
) {
}

public function getEntityManager(): EntityManagerInterface
{
return $this->em;
}

public function getSchema(): Schema
{
return $this->schema;
}

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

/** @return array<string> */
public function getSqls(): array
{
return $this->sqls;
}
}
25 changes: 23 additions & 2 deletions src/Tools/SchemaTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Doctrine\ORM\Mapping\QuoteStrategy;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
use Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs;
use Doctrine\ORM\Tools\Event\SchemaChangedEventArgs;
use Doctrine\ORM\Tools\Exception\MissingColumnException;
use Doctrine\ORM\Tools\Exception\NotSupported;
use Throwable;
Expand Down Expand Up @@ -73,7 +74,9 @@ public function __construct(private readonly EntityManagerInterface $em)
*/
public function createSchema(array $classes): void
{
$createSchemaSql = $this->getCreateSchemaSql($classes);
$schema = $this->getSchemaFromMetadata($classes);

$createSchemaSql = $schema->toSql($this->platform);
$conn = $this->em->getConnection();

foreach ($createSchemaSql as $sql) {
Expand All @@ -83,6 +86,12 @@ public function createSchema(array $classes): void
throw ToolsException::schemaToolFailure($sql, $e);
}
}

$eventManager = $this->em->getEventManager();
$eventManager->dispatchEvent(
ToolEvents::postSchemaChanged,
new SchemaChangedEventArgs($this->em, $schema, new Schema(), $createSchemaSql),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you for looking into this. so the idea would be that e.g. symfony messsenger could listen to this, inspect the changes and fire its own sql statements against the connection if it sees that something was done which it wants to alter?

is this event also useful when doctrine migrations are used rather than database updated on the fly?

with dbal 3, symfony was adding extra sql in https://github.com/symfony/symfony/blob/73f8713e143c64dc5f045dcdee7d5e893cccfcd8/src/Symfony/Bridge/Doctrine/SchemaListener/MessengerTransportDoctrineSchemaListener.php#L76C1-L76C38 (and that sql afaik can not be represented in the doctrine schema models.
that sql got saved to the migration file, which was quite nice to review it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, something like:

public function onPostSchemaChanged($event)
{
    if (!$this->hasMessengerTable($event)) {
        $event->getEntityManage()->getConnection()->executeStatement($this->getMessengerTableSql());
    }
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and how can this be integrated with the doctrine migrations? could the listener also add sql into a migration?

);
}

/**
Expand Down Expand Up @@ -875,11 +884,23 @@ public function getDropSchemaSQL(array $classes): array
*/
public function updateSchema(array $classes): void
{
$toSchema = $this->getSchemaFromMetadata($classes);
$fromSchema = $this->createSchemaForComparison($toSchema);
$comparator = $this->schemaManager->createComparator();
$schemaDiff = $comparator->compareSchemas($fromSchema, $toSchema);

$sqls = $this->platform->getAlterSchemaSQL($schemaDiff);
$conn = $this->em->getConnection();

foreach ($this->getUpdateSchemaSql($classes) as $sql) {
foreach ($sqls as $sql) {
$conn->executeStatement($sql);
}

$eventManager = $this->em->getEventManager();
$eventManager->dispatchEvent(
ToolEvents::postSchemaChanged,
new SchemaChangedEventArgs($this->em, $toSchema, $fromSchema, $sqls),
);
}

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

public const postSchemaChanged = 'postSchemaChanged';
}
27 changes: 27 additions & 0 deletions tests/Tests/ORM/Tools/SchemaToolTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Doctrine\Tests\ORM\Tools;

use Composer\InstalledVersions;
use Composer\Semver\VersionParser;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\Column;
Expand All @@ -20,6 +22,7 @@
use Doctrine\ORM\Mapping\UniqueConstraint;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
use Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs;
use Doctrine\ORM\Tools\Event\SchemaChangedEventArgs;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\ORM\Tools\ToolEvents;
use Doctrine\Persistence\Mapping\Driver\StaticPHPDriver;
Expand Down Expand Up @@ -172,6 +175,30 @@ public function testPostGenerateEvents(): void
self::assertTrue($listener->schemaCalled);
}

public function testSchemaChangedEvent(): void
{
if (InstalledVersions::satisfies(new VersionParser(), 'doctrine/dbal', '^3.0')) {
self::markTestSkipped('This test is not compatible with DBAL 3');
}

$em = $this->getTestEntityManager();

$schemaTool = new SchemaTool($em);

$listener = new class ()
{
public bool $called = false;

public function postSchemaChanged(SchemaChangedEventArgs $eventArgs): void
{
$this->called = true;
}
};
$em->getEventManager()->addEventListener(ToolEvents::postSchemaChanged, $listener);
$schemaTool->updateSchema([]);
self::assertTrue($listener->called);
}

public function testNullDefaultNotAddedToPlatformOptions(): void
{
$em = $this->getTestEntityManager();
Expand Down
Loading