Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit b9d4149

Browse filesBrowse files
committed
feature #36655 Automatically provide Messenger Doctrine schema to "diff" (weaverryan)
This PR was squashed before being merged into the 5.1-dev branch. Discussion ---------- Automatically provide Messenger Doctrine schema to "diff" | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | Alternative to #36629 | License | MIT | Doc PR | TODO - WILL be needed This follows this conversation: #36629 (comment) - it automatically adds SQL to Doctrine's migration/diff system when features are added the require a database table: The new feature works for: ### A) Messenger Doctrine transport **FULL support** Works perfectly: configure a doctrine transport and run `make:migration` **Note**: There is no current way to disable this. So if you have `auto_setup` ON and you run `make:migration` before trying Messenger, it will generate the table SQL. Adding a flag to disable it might be very complicated, because we need to know (in DoctrineBundle, at compile time) whether or not this feature is enabled/disabled so that we can decide *not* to add `messenger_messages` to the `schema_filter`. ### B) `PdoAdapter` from Cache **FULL support** Works perfectly: configure a doctrine transport and run `make:migration` ### C) `PdoStore` from Lock **PARTIAL support** I added `PdoStore::configureSchema()` but did NOT add a listener. While `PdoStore` *does* accept a DBAL `Connection`, I don't think it's possible via the `framework.lock` config to create a `PdoStore` that is passed a `Connection`. In other words: if we added a listener that called `PdoStore::configureSchema` if the user configured a `pdo` lock, that service will *never* have a `Connection` object... so it's kind of worthless. **NEED**: A proper way to inject a DBAL `Connection` into `PdoStore` via `framework.lock` config. ### D) `PdoSessionHandler` **NO support** This class doesn't accept a DBAL `Connection` object. And so, we can't reliably create a listener to add the schema because (if there are multiple connections) we wouldn't know which Connection to use. We could compare (`===`) the `PDO` instance inside `PdoSessionHandler` to the wrapped `PDO` connection in Doctrine. That would only work if the user has configured their `PdoSessionHandler` to re-use the Doctrine PDO connection. The `PdoSessionHandler` *already* has a `createTable()` method on it to help with manual migration. But... it's not easy to call from a migration because you would need to fetch the `PdoSessionHandler` service from the container. Adding something **NEED**: Either: A) A way for `PdoSessionHandler` to use a DBAL Connection or B) We try to hack this feature by comparing the `PDO` instances in the event subscriber or C) We add an easier way to access the `createTable()` method from inside a migration. TODOs * [X] Determine service injection XML needed for getting all PdoAdapter pools * [ ] Finish DoctrineBundle PR: doctrine/DoctrineBundle#1163 Commits ------- 2dd9c3c Automatically provide Messenger Doctrine schema to "diff"
2 parents 3d30ff7 + 2dd9c3c commit b9d4149
Copy full SHA for b9d4149

16 files changed

+566
-27
lines changed
+96Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Doctrine\SchemaListener;
13+
14+
use Doctrine\Common\EventSubscriber;
15+
use Doctrine\DBAL\Event\SchemaCreateTableEventArgs;
16+
use Doctrine\DBAL\Events;
17+
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
18+
use Doctrine\ORM\Tools\ToolEvents;
19+
use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport;
20+
use Symfony\Component\Messenger\Transport\TransportInterface;
21+
22+
/**
23+
* Automatically adds any required database tables to the Doctrine Schema.
24+
*
25+
* @author Ryan Weaver <ryan@symfonycasts.com>
26+
*/
27+
final class MessengerTransportDoctrineSchemaSubscriber implements EventSubscriber
28+
{
29+
private const PROCESSING_TABLE_FLAG = self::class.':processing';
30+
31+
private $transports;
32+
33+
/**
34+
* @param iterable|TransportInterface[] $transports
35+
*/
36+
public function __construct(iterable $transports)
37+
{
38+
$this->transports = $transports;
39+
}
40+
41+
public function postGenerateSchema(GenerateSchemaEventArgs $event): void
42+
{
43+
$dbalConnection = $event->getEntityManager()->getConnection();
44+
foreach ($this->transports as $transport) {
45+
if (!$transport instanceof DoctrineTransport) {
46+
continue;
47+
}
48+
49+
$transport->configureSchema($event->getSchema(), $dbalConnection);
50+
}
51+
}
52+
53+
public function onSchemaCreateTable(SchemaCreateTableEventArgs $event): void
54+
{
55+
$table = $event->getTable();
56+
57+
// if this method triggers a nested create table below, allow Doctrine to work like normal
58+
if ($table->hasOption(self::PROCESSING_TABLE_FLAG)) {
59+
return;
60+
}
61+
62+
foreach ($this->transports as $transport) {
63+
if (!$transport instanceof DoctrineTransport) {
64+
continue;
65+
}
66+
67+
$extraSql = $transport->getExtraSetupSqlForTable($table);
68+
if (null === $extraSql) {
69+
continue;
70+
}
71+
72+
// avoid this same listener from creating a loop on this table
73+
$table->addOption(self::PROCESSING_TABLE_FLAG, true);
74+
$createTableSql = $event->getPlatform()->getCreateTableSQL($table);
75+
76+
/*
77+
* Add all the SQL needed to create the table and tell Doctrine
78+
* to "preventDefault" so that only our SQL is used. This is
79+
* the only way to inject some extra SQL.
80+
*/
81+
$event->addSql($createTableSql);
82+
$event->addSql($extraSql);
83+
$event->preventDefault();
84+
85+
return;
86+
}
87+
}
88+
89+
public function getSubscribedEvents(): array
90+
{
91+
return [
92+
ToolEvents::postGenerateSchema,
93+
Events::onSchemaCreateTable,
94+
];
95+
}
96+
}
+50Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Doctrine\SchemaListener;
13+
14+
use Doctrine\Common\EventSubscriber;
15+
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
16+
use Doctrine\ORM\Tools\ToolEvents;
17+
use Symfony\Component\Cache\Adapter\PdoAdapter;
18+
19+
/**
20+
* Automatically adds the cache table needed for the PdoAdapter.
21+
*
22+
* @author Ryan Weaver <ryan@symfonycasts.com>
23+
*/
24+
final class PdoCacheAdapterDoctrineSchemaSubscriber implements EventSubscriber
25+
{
26+
private $pdoAdapters;
27+
28+
/**
29+
* @param iterable|PdoAdapter[] $pdoAdapters
30+
*/
31+
public function __construct(iterable $pdoAdapters)
32+
{
33+
$this->pdoAdapters = $pdoAdapters;
34+
}
35+
36+
public function postGenerateSchema(GenerateSchemaEventArgs $event): void
37+
{
38+
$dbalConnection = $event->getEntityManager()->getConnection();
39+
foreach ($this->pdoAdapters as $pdoAdapter) {
40+
$pdoAdapter->configureSchema($event->getSchema(), $dbalConnection);
41+
}
42+
}
43+
44+
public function getSubscribedEvents(): array
45+
{
46+
return [
47+
ToolEvents::postGenerateSchema,
48+
];
49+
}
50+
}
+99Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Doctrine\Tests\SchemaListener;
13+
14+
use Doctrine\DBAL\Connection;
15+
use Doctrine\DBAL\Event\SchemaCreateTableEventArgs;
16+
use Doctrine\DBAL\Platforms\AbstractPlatform;
17+
use Doctrine\DBAL\Schema\Schema;
18+
use Doctrine\DBAL\Schema\Table;
19+
use Doctrine\ORM\EntityManagerInterface;
20+
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
21+
use PHPUnit\Framework\TestCase;
22+
use Symfony\Bridge\Doctrine\SchemaListener\MessengerTransportDoctrineSchemaSubscriber;
23+
use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport;
24+
use Symfony\Component\Messenger\Transport\TransportInterface;
25+
26+
class MessengerTransportDoctrineSchemaSubscriberTest extends TestCase
27+
{
28+
public function testPostGenerateSchema()
29+
{
30+
$schema = new Schema();
31+
$dbalConnection = $this->createMock(Connection::class);
32+
$entityManager = $this->createMock(EntityManagerInterface::class);
33+
$entityManager->expects($this->once())
34+
->method('getConnection')
35+
->willReturn($dbalConnection);
36+
$event = new GenerateSchemaEventArgs($entityManager, $schema);
37+
38+
$doctrineTransport = $this->createMock(DoctrineTransport::class);
39+
$doctrineTransport->expects($this->once())
40+
->method('configureSchema')
41+
->with($schema, $dbalConnection);
42+
$otherTransport = $this->createMock(TransportInterface::class);
43+
$otherTransport->expects($this->never())
44+
->method($this->anything());
45+
46+
$subscriber = new MessengerTransportDoctrineSchemaSubscriber([$doctrineTransport, $otherTransport]);
47+
$subscriber->postGenerateSchema($event);
48+
}
49+
50+
public function testOnSchemaCreateTable()
51+
{
52+
$platform = $this->createMock(AbstractPlatform::class);
53+
$table = new Table('queue_table');
54+
$event = new SchemaCreateTableEventArgs($table, [], [], $platform);
55+
56+
$otherTransport = $this->createMock(TransportInterface::class);
57+
$otherTransport->expects($this->never())
58+
->method($this->anything());
59+
60+
$doctrineTransport = $this->createMock(DoctrineTransport::class);
61+
$doctrineTransport->expects($this->once())
62+
->method('getExtraSetupSqlForTable')
63+
->with($table)
64+
->willReturn('ALTER TABLE pizza ADD COLUMN extra_cheese boolean');
65+
66+
// we use the platform to generate the full create table sql
67+
$platform->expects($this->once())
68+
->method('getCreateTableSQL')
69+
->with($table)
70+
->willReturn('CREATE TABLE pizza (id integer NOT NULL)');
71+
72+
$subscriber = new MessengerTransportDoctrineSchemaSubscriber([$otherTransport, $doctrineTransport]);
73+
$subscriber->onSchemaCreateTable($event);
74+
$this->assertTrue($event->isDefaultPrevented());
75+
$this->assertSame([
76+
'CREATE TABLE pizza (id integer NOT NULL)',
77+
'ALTER TABLE pizza ADD COLUMN extra_cheese boolean',
78+
], $event->getSql());
79+
}
80+
81+
public function testOnSchemaCreateTableNoExtraSql()
82+
{
83+
$platform = $this->createMock(AbstractPlatform::class);
84+
$table = new Table('queue_table');
85+
$event = new SchemaCreateTableEventArgs($table, [], [], $platform);
86+
87+
$doctrineTransport = $this->createMock(DoctrineTransport::class);
88+
$doctrineTransport->expects($this->once())
89+
->method('getExtraSetupSqlForTable')
90+
->willReturn(null);
91+
92+
$platform->expects($this->never())
93+
->method('getCreateTableSQL');
94+
95+
$subscriber = new MessengerTransportDoctrineSchemaSubscriber([$doctrineTransport]);
96+
$subscriber->onSchemaCreateTable($event);
97+
$this->assertFalse($event->isDefaultPrevented());
98+
}
99+
}
+42Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Doctrine\Tests\SchemaListener;
13+
14+
use Doctrine\DBAL\Connection;
15+
use Doctrine\DBAL\Schema\Schema;
16+
use Doctrine\ORM\EntityManagerInterface;
17+
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
18+
use PHPUnit\Framework\TestCase;
19+
use Symfony\Bridge\Doctrine\SchemaListener\PdoCacheAdapterDoctrineSchemaSubscriber;
20+
use Symfony\Component\Cache\Adapter\PdoAdapter;
21+
22+
class PdoCacheAdapterDoctrineSchemaSubscriberTest extends TestCase
23+
{
24+
public function testPostGenerateSchema()
25+
{
26+
$schema = new Schema();
27+
$dbalConnection = $this->createMock(Connection::class);
28+
$entityManager = $this->createMock(EntityManagerInterface::class);
29+
$entityManager->expects($this->once())
30+
->method('getConnection')
31+
->willReturn($dbalConnection);
32+
$event = new GenerateSchemaEventArgs($entityManager, $schema);
33+
34+
$pdoAdapter = $this->createMock(PdoAdapter::class);
35+
$pdoAdapter->expects($this->once())
36+
->method('configureSchema')
37+
->with($schema, $dbalConnection);
38+
39+
$subscriber = new PdoCacheAdapterDoctrineSchemaSubscriber([$pdoAdapter]);
40+
$subscriber->postGenerateSchema($event);
41+
}
42+
}

‎src/Symfony/Bridge/Doctrine/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/Doctrine/composer.json
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@
2626
},
2727
"require-dev": {
2828
"symfony/stopwatch": "^4.4|^5.0",
29+
"symfony/cache": "^5.1",
2930
"symfony/config": "^4.4|^5.0",
3031
"symfony/dependency-injection": "^4.4|^5.0",
3132
"symfony/form": "^5.1",
3233
"symfony/http-kernel": "^5.0",
3334
"symfony/messenger": "^4.4|^5.0",
35+
"symfony/doctrine-messenger": "^5.1",
3436
"symfony/property-access": "^4.4|^5.0",
3537
"symfony/property-info": "^5.0",
3638
"symfony/proxy-manager-bridge": "^4.4|^5.0",

‎src/Symfony/Component/Cache/Adapter/PdoAdapter.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Cache/Adapter/PdoAdapter.php
+39-17Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -115,24 +115,8 @@ public function createTable()
115115
$conn = $this->getConnection();
116116

117117
if ($conn instanceof Connection) {
118-
$types = [
119-
'mysql' => 'binary',
120-
'sqlite' => 'text',
121-
'pgsql' => 'string',
122-
'oci' => 'string',
123-
'sqlsrv' => 'string',
124-
];
125-
if (!isset($types[$this->driver])) {
126-
throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver));
127-
}
128-
129118
$schema = new Schema();
130-
$table = $schema->createTable($this->table);
131-
$table->addColumn($this->idCol, $types[$this->driver], ['length' => 255]);
132-
$table->addColumn($this->dataCol, 'blob', ['length' => 16777215]);
133-
$table->addColumn($this->lifetimeCol, 'integer', ['unsigned' => true, 'notnull' => false]);
134-
$table->addColumn($this->timeCol, 'integer', ['unsigned' => true]);
135-
$table->setPrimaryKey([$this->idCol]);
119+
$this->addTableToSchema($schema);
136120

137121
foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
138122
$conn->exec($sql);
@@ -169,6 +153,23 @@ public function createTable()
169153
$conn->exec($sql);
170154
}
171155

156+
/**
157+
* Adds the Table to the Schema if the adapter uses this Connection.
158+
*/
159+
public function configureSchema(Schema $schema, Connection $forConnection): void
160+
{
161+
// only update the schema for this connection
162+
if ($forConnection !== $this->getConnection()) {
163+
return;
164+
}
165+
166+
if ($schema->hasTable($this->table)) {
167+
return;
168+
}
169+
170+
$this->addTableToSchema($schema);
171+
}
172+
172173
/**
173174
* {@inheritdoc}
174175
*/
@@ -467,4 +468,25 @@ private function getServerVersion(): string
467468

468469
return $this->serverVersion;
469470
}
471+
472+
private function addTableToSchema(Schema $schema): void
473+
{
474+
$types = [
475+
'mysql' => 'binary',
476+
'sqlite' => 'text',
477+
'pgsql' => 'string',
478+
'oci' => 'string',
479+
'sqlsrv' => 'string',
480+
];
481+
if (!isset($types[$this->driver])) {
482+
throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver));
483+
}
484+
485+
$table = $schema->createTable($this->table);
486+
$table->addColumn($this->idCol, $types[$this->driver], ['length' => 255]);
487+
$table->addColumn($this->dataCol, 'blob', ['length' => 16777215]);
488+
$table->addColumn($this->lifetimeCol, 'integer', ['unsigned' => true, 'notnull' => false]);
489+
$table->addColumn($this->timeCol, 'integer', ['unsigned' => true]);
490+
$table->setPrimaryKey([$this->idCol]);
491+
}
470492
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.