Skip to content

Automatically provide Messenger Doctrine schema to "diff" #36655

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

Merged
merged 1 commit into from
May 5, 2020
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\Doctrine\SchemaListener;

use Doctrine\Common\EventSubscriber;
use Doctrine\DBAL\Event\SchemaCreateTableEventArgs;
use Doctrine\DBAL\Events;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
use Doctrine\ORM\Tools\ToolEvents;
use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport;
use Symfony\Component\Messenger\Transport\TransportInterface;

/**
* Automatically adds any required database tables to the Doctrine Schema.
*
* @author Ryan Weaver <ryan@symfonycasts.com>
*/
final class MessengerTransportDoctrineSchemaSubscriber implements EventSubscriber
{
private const PROCESSING_TABLE_FLAG = self::class.':processing';

private $transports;

/**
* @param iterable|TransportInterface[] $transports
*/
public function __construct(iterable $transports)
{
$this->transports = $transports;
}

public function postGenerateSchema(GenerateSchemaEventArgs $event): void
{
$dbalConnection = $event->getEntityManager()->getConnection();
foreach ($this->transports as $transport) {
if (!$transport instanceof DoctrineTransport) {
continue;
}

$transport->configureSchema($event->getSchema(), $dbalConnection);
}
}

public function onSchemaCreateTable(SchemaCreateTableEventArgs $event): void
{
$table = $event->getTable();

// if this method triggers a nested create table below, allow Doctrine to work like normal
if ($table->hasOption(self::PROCESSING_TABLE_FLAG)) {
return;
}

foreach ($this->transports as $transport) {
if (!$transport instanceof DoctrineTransport) {
continue;
}

$extraSql = $transport->getExtraSetupSqlForTable($table);
if (null === $extraSql) {
continue;
}

// avoid this same listener from creating a loop on this table
$table->addOption(self::PROCESSING_TABLE_FLAG, true);
$createTableSql = $event->getPlatform()->getCreateTableSQL($table);

/*
* Add all the SQL needed to create the table and tell Doctrine
* to "preventDefault" so that only our SQL is used. This is
* the only way to inject some extra SQL.
*/
$event->addSql($createTableSql);
$event->addSql($extraSql);
$event->preventDefault();

return;
}
}

public function getSubscribedEvents(): array
{
return [
ToolEvents::postGenerateSchema,
Events::onSchemaCreateTable,
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\Doctrine\SchemaListener;

use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
use Doctrine\ORM\Tools\ToolEvents;
use Symfony\Component\Cache\Adapter\PdoAdapter;

/**
* Automatically adds the cache table needed for the PdoAdapter.
*
* @author Ryan Weaver <ryan@symfonycasts.com>
*/
final class PdoCacheAdapterDoctrineSchemaSubscriber implements EventSubscriber
{
private $pdoAdapters;

/**
* @param iterable|PdoAdapter[] $pdoAdapters
*/
public function __construct(iterable $pdoAdapters)
{
$this->pdoAdapters = $pdoAdapters;
}

public function postGenerateSchema(GenerateSchemaEventArgs $event): void
{
$dbalConnection = $event->getEntityManager()->getConnection();
foreach ($this->pdoAdapters as $pdoAdapter) {
$pdoAdapter->configureSchema($event->getSchema(), $dbalConnection);
}
}

public function getSubscribedEvents(): array
{
return [
ToolEvents::postGenerateSchema,
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\Doctrine\Tests\SchemaListener;

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Event\SchemaCreateTableEventArgs;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\Table;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Doctrine\SchemaListener\MessengerTransportDoctrineSchemaSubscriber;
use Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport;
use Symfony\Component\Messenger\Transport\TransportInterface;

class MessengerTransportDoctrineSchemaSubscriberTest extends TestCase
{
public function testPostGenerateSchema()
{
$schema = new Schema();
$dbalConnection = $this->createMock(Connection::class);
$entityManager = $this->createMock(EntityManagerInterface::class);
$entityManager->expects($this->once())
->method('getConnection')
->willReturn($dbalConnection);
$event = new GenerateSchemaEventArgs($entityManager, $schema);

$doctrineTransport = $this->createMock(DoctrineTransport::class);
$doctrineTransport->expects($this->once())
->method('configureSchema')
->with($schema, $dbalConnection);
$otherTransport = $this->createMock(TransportInterface::class);
$otherTransport->expects($this->never())
->method($this->anything());

$subscriber = new MessengerTransportDoctrineSchemaSubscriber([$doctrineTransport, $otherTransport]);
$subscriber->postGenerateSchema($event);
}

public function testOnSchemaCreateTable()
{
$platform = $this->createMock(AbstractPlatform::class);
$table = new Table('queue_table');
$event = new SchemaCreateTableEventArgs($table, [], [], $platform);

$otherTransport = $this->createMock(TransportInterface::class);
$otherTransport->expects($this->never())
->method($this->anything());

$doctrineTransport = $this->createMock(DoctrineTransport::class);
$doctrineTransport->expects($this->once())
->method('getExtraSetupSqlForTable')
->with($table)
->willReturn('ALTER TABLE pizza ADD COLUMN extra_cheese boolean');

// we use the platform to generate the full create table sql
$platform->expects($this->once())
->method('getCreateTableSQL')
->with($table)
->willReturn('CREATE TABLE pizza (id integer NOT NULL)');

$subscriber = new MessengerTransportDoctrineSchemaSubscriber([$otherTransport, $doctrineTransport]);
$subscriber->onSchemaCreateTable($event);
$this->assertTrue($event->isDefaultPrevented());
$this->assertSame([
'CREATE TABLE pizza (id integer NOT NULL)',
'ALTER TABLE pizza ADD COLUMN extra_cheese boolean',
], $event->getSql());
}

public function testOnSchemaCreateTableNoExtraSql()
{
$platform = $this->createMock(AbstractPlatform::class);
$table = new Table('queue_table');
$event = new SchemaCreateTableEventArgs($table, [], [], $platform);

$doctrineTransport = $this->createMock(DoctrineTransport::class);
$doctrineTransport->expects($this->once())
->method('getExtraSetupSqlForTable')
->willReturn(null);

$platform->expects($this->never())
->method('getCreateTableSQL');

$subscriber = new MessengerTransportDoctrineSchemaSubscriber([$doctrineTransport]);
$subscriber->onSchemaCreateTable($event);
$this->assertFalse($event->isDefaultPrevented());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\Doctrine\Tests\SchemaListener;

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Doctrine\SchemaListener\PdoCacheAdapterDoctrineSchemaSubscriber;
use Symfony\Component\Cache\Adapter\PdoAdapter;

class PdoCacheAdapterDoctrineSchemaSubscriberTest extends TestCase
{
public function testPostGenerateSchema()
{
$schema = new Schema();
$dbalConnection = $this->createMock(Connection::class);
$entityManager = $this->createMock(EntityManagerInterface::class);
$entityManager->expects($this->once())
->method('getConnection')
->willReturn($dbalConnection);
$event = new GenerateSchemaEventArgs($entityManager, $schema);

$pdoAdapter = $this->createMock(PdoAdapter::class);
$pdoAdapter->expects($this->once())
->method('configureSchema')
->with($schema, $dbalConnection);

$subscriber = new PdoCacheAdapterDoctrineSchemaSubscriber([$pdoAdapter]);
$subscriber->postGenerateSchema($event);
}
}
2 changes: 2 additions & 0 deletions src/Symfony/Bridge/Doctrine/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@
},
"require-dev": {
"symfony/stopwatch": "^4.4|^5.0",
"symfony/cache": "^5.1",
"symfony/config": "^4.4|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/form": "^5.1",
"symfony/http-kernel": "^5.0",
"symfony/messenger": "^4.4|^5.0",
"symfony/doctrine-messenger": "^5.1",
"symfony/property-access": "^4.4|^5.0",
"symfony/property-info": "^5.0",
"symfony/proxy-manager-bridge": "^4.4|^5.0",
Expand Down
56 changes: 39 additions & 17 deletions src/Symfony/Component/Cache/Adapter/PdoAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,24 +115,8 @@ public function createTable()
$conn = $this->getConnection();

if ($conn instanceof Connection) {
$types = [
'mysql' => 'binary',
'sqlite' => 'text',
'pgsql' => 'string',
'oci' => 'string',
'sqlsrv' => 'string',
];
if (!isset($types[$this->driver])) {
throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver));
}

$schema = new Schema();
$table = $schema->createTable($this->table);
$table->addColumn($this->idCol, $types[$this->driver], ['length' => 255]);
$table->addColumn($this->dataCol, 'blob', ['length' => 16777215]);
$table->addColumn($this->lifetimeCol, 'integer', ['unsigned' => true, 'notnull' => false]);
$table->addColumn($this->timeCol, 'integer', ['unsigned' => true]);
$table->setPrimaryKey([$this->idCol]);
$this->addTableToSchema($schema);

foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
$conn->exec($sql);
Expand Down Expand Up @@ -169,6 +153,23 @@ public function createTable()
$conn->exec($sql);
}

/**
* Adds the Table to the Schema if the adapter uses this Connection.
*/
public function configureSchema(Schema $schema, Connection $forConnection): void
{
// only update the schema for this connection
if ($forConnection !== $this->getConnection()) {
return;
}

if ($schema->hasTable($this->table)) {
return;
}

$this->addTableToSchema($schema);
}

/**
* {@inheritdoc}
*/
Expand Down Expand Up @@ -467,4 +468,25 @@ private function getServerVersion(): string

return $this->serverVersion;
}

private function addTableToSchema(Schema $schema): void
{
$types = [
'mysql' => 'binary',
'sqlite' => 'text',
'pgsql' => 'string',
'oci' => 'string',
'sqlsrv' => 'string',
];
if (!isset($types[$this->driver])) {
throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver));
}

$table = $schema->createTable($this->table);
$table->addColumn($this->idCol, $types[$this->driver], ['length' => 255]);
$table->addColumn($this->dataCol, 'blob', ['length' => 16777215]);
$table->addColumn($this->lifetimeCol, 'integer', ['unsigned' => true, 'notnull' => false]);
$table->addColumn($this->timeCol, 'integer', ['unsigned' => true]);
$table->setPrimaryKey([$this->idCol]);
}
}
Loading