Skip to content

Commit 6f6ead1

Browse files
committed
Introduce addDeferredSql method in migration class
This will allow to add queries that needs to be executed after changes made to schema object.
1 parent a3fc5b3 commit 6f6ead1

8 files changed

+137
-3
lines changed

UPGRADE.md

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
to enable wrapping all migrations in a single transaction. To disable it, you can use the `--no-all-or-nothing`
66
option instead. Both options override the configuration value.
77

8+
## Migration classes
9+
- It is now possible to add SQL statements, that needs to be executed after changes made to schema object. Use
10+
`addDeferredSql` method in your migrations for this purpose.
811

912

1013
# Upgrade to 3.6

docs/en/reference/migration-classes.rst

+25-1
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ addSql
188188
You can use the ``addSql`` method within the ``up`` and ``down`` methods. Internally the ``addSql`` calls are passed
189189
to the executeQuery method in the DBAL. This means that you can use the power of prepared statements easily and that
190190
you don't need to copy paste the same query with different parameters. You can just pass those different parameters
191-
to the addSql method as parameters.
191+
to the addSql method as parameters. These queries are executed before changes applied to ``$schema``.
192192

193193
.. code-block:: php
194194
@@ -205,6 +205,30 @@ to the addSql method as parameters.
205205
}
206206
}
207207
208+
addDeferredSql
209+
~~~~~~~~~~~~~~
210+
211+
Works just like the ``addSql`` method, but queries are deferred to be executed after changes to ``$schema`` were
212+
planned.
213+
214+
.. code-block:: php
215+
216+
public function up(Schema $schema): void
217+
{
218+
$schema->getTable('user')->addColumn('happy', 'boolean')->setDefault(false);
219+
220+
$users = [
221+
['name' => 'mike', 'id' => 1],
222+
['name' => 'jwage', 'id' => 2],
223+
['name' => 'ocramius', 'id' => 3],
224+
];
225+
226+
foreach ($users as $user) {
227+
// Use addDeferredSql since "happy" is new column, that will not yet be present in schema if called by using addSql
228+
$this->addDeferredSql('UPDATE user SET happy = true WHERE name = :name AND id = :id', $user);
229+
}
230+
}
231+
208232
write
209233
~~~~~
210234

src/AbstractMigration.php

+27
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ abstract class AbstractMigration
3737
/** @var Query[] */
3838
private array $plannedSql = [];
3939

40+
/** @var Query[] */
41+
private array $deferredSql = [];
42+
4043
private bool $frozen = false;
4144

4245
public function __construct(Connection $connection, private readonly LoggerInterface $logger)
@@ -135,12 +138,36 @@ protected function addSql(
135138
$this->plannedSql[] = new Query($sql, $params, $types);
136139
}
137140

141+
/**
142+
* Adds SQL queries which should be executed after schema changes
143+
*
144+
* @param mixed[] $params
145+
* @param mixed[] $types
146+
*/
147+
protected function addDeferredSql(
148+
string $sql,
149+
array $params = [],
150+
array $types = [],
151+
): void {
152+
if ($this->frozen) {
153+
throw FrozenMigration::new();
154+
}
155+
156+
$this->deferredSql[] = new Query($sql, $params, $types);
157+
}
158+
138159
/** @return Query[] */
139160
public function getSql(): array
140161
{
141162
return $this->plannedSql;
142163
}
143164

165+
/** @return Query[] */
166+
public function getDeferredSql(): array
167+
{
168+
return $this->deferredSql;
169+
}
170+
144171
public function freeze(): void
145172
{
146173
$this->frozen = true;

src/Version/DbalExecutor.php

+4
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ private function executeMigration(
145145
$this->addSql(new Query($sql));
146146
}
147147

148+
foreach ($migration->getDeferredSql() as $deferredSqlQuery) {
149+
$this->addSql($deferredSqlQuery);
150+
}
151+
148152
$migration->freeze();
149153

150154
if (count($this->sql) !== 0) {

tests/AbstractMigrationTest.php

+16
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ public function testAddSql(): void
5151
self::assertEquals([new Query('SELECT 1', [1], [2])], $this->migration->getSql());
5252
}
5353

54+
public function testAddDeferredSql(): void
55+
{
56+
$this->migration->exposedAddDeferredSql('SELECT 2', [1], [2]);
57+
58+
self::assertEquals([new Query('SELECT 2', [1], [2])], $this->migration->getDeferredSql());
59+
}
60+
5461
public function testThrowFrozenMigrationException(): void
5562
{
5663
$this->expectException(FrozenMigration::class);
@@ -60,6 +67,15 @@ public function testThrowFrozenMigrationException(): void
6067
$this->migration->exposedAddSql('SELECT 1', [1], [2]);
6168
}
6269

70+
public function testThrowFrozenMigrationExceptionOnDeferredAdd(): void
71+
{
72+
$this->expectException(FrozenMigration::class);
73+
$this->expectExceptionMessage('The migration is frozen and cannot be edited anymore.');
74+
75+
$this->migration->freeze();
76+
$this->migration->exposedAddDeferredSql('SELECT 2', [1], [2]);
77+
}
78+
6379
public function testWarnIfOutputMessage(): void
6480
{
6581
$this->migration->warnIf(true, 'Warning was thrown');

tests/MigratorTest.php

+30-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
use Doctrine\Migrations\Metadata\Storage\MetadataStorage;
1616
use Doctrine\Migrations\MigratorConfiguration;
1717
use Doctrine\Migrations\ParameterFormatter;
18+
use Doctrine\Migrations\Provider\DBALSchemaDiffProvider;
1819
use Doctrine\Migrations\Provider\SchemaDiffProvider;
1920
use Doctrine\Migrations\Tests\Stub\Functional\MigrateNotTouchingTheSchema;
21+
use Doctrine\Migrations\Tests\Stub\Functional\MigrateWithDeferredSql;
2022
use Doctrine\Migrations\Tests\Stub\Functional\MigrationThrowsError;
2123
use Doctrine\Migrations\Tests\Stub\NonTransactional\MigrationNonTransactional;
2224
use Doctrine\Migrations\Version\DbalExecutor;
@@ -73,6 +75,32 @@ public function testGetSql(): void
7375
);
7476
}
7577

78+
public function testQueriesOrder(): void
79+
{
80+
$this->config->addMigrationsDirectory('DoctrineMigrations\\', __DIR__ . '/Stub/migrations-empty-folder');
81+
82+
$conn = $this->getSqliteConnection();
83+
$migrator = $this->createTestMigrator(
84+
schemaDiff: new DBALSchemaDiffProvider($conn->createSchemaManager(), $conn->getDatabasePlatform()),
85+
);
86+
87+
$migration = new MigrateWithDeferredSql($conn, $this->logger);
88+
$plan = new MigrationPlan(new Version(MigrateWithDeferredSql::class), $migration, Direction::UP);
89+
$planList = new MigrationPlanList([$plan], Direction::UP);
90+
91+
$sql = $migrator->migrate($planList, $this->migratorConfiguration);
92+
93+
self::assertArrayHasKey(MigrateWithDeferredSql::class, $sql);
94+
self::assertSame(
95+
[
96+
'SELECT 1',
97+
'CREATE TABLE test (id INTEGER NOT NULL)',
98+
'INSERT INTO test(id) VALUES(123)',
99+
],
100+
array_map(strval(...), $sql[MigrateWithDeferredSql::class]),
101+
);
102+
}
103+
76104
public function testEmptyPlanShowsMessage(): void
77105
{
78106
$migrator = $this->createTestMigrator();
@@ -84,7 +112,7 @@ public function testEmptyPlanShowsMessage(): void
84112
self::assertStringContainsString('No migrations', $this->logger->records[0]['message']);
85113
}
86114

87-
protected function createTestMigrator(): DbalMigrator
115+
protected function createTestMigrator(SchemaDiffProvider|null $schemaDiff = null): DbalMigrator
88116
{
89117
$eventManager = new EventManager();
90118
$eventDispatcher = new EventDispatcher($this->conn, $eventManager);
@@ -94,7 +122,7 @@ protected function createTestMigrator(): DbalMigrator
94122
$stopwatch = new Stopwatch();
95123
$paramFormatter = $this->createMock(ParameterFormatter::class);
96124
$storage = $this->createMock(MetadataStorage::class);
97-
$schemaDiff = $this->createMock(SchemaDiffProvider::class);
125+
$schemaDiff ??= $this->createMock(SchemaDiffProvider::class);
98126

99127
return new DbalMigrator(
100128
$this->conn,

tests/Stub/AbstractMigrationStub.php

+9
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,13 @@ public function exposedAddSql(string $sql, array $params = [], array $types = []
3535
{
3636
$this->addSql($sql, $params, $types);
3737
}
38+
39+
/**
40+
* @param int[] $params
41+
* @param int[] $types
42+
*/
43+
public function exposedAddDeferredSql(string $sql, array $params = [], array $types = []): void
44+
{
45+
$this->addDeferredSql($sql, $params, $types);
46+
}
3847
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Migrations\Tests\Stub\Functional;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Doctrine\Migrations\AbstractMigration;
9+
10+
class MigrateWithDeferredSql extends AbstractMigration
11+
{
12+
public function up(Schema $schema): void
13+
{
14+
// Last to be executed
15+
$this->addDeferredSql('INSERT INTO test(id) VALUES(123)');
16+
17+
// Executed after queries from addSql()
18+
$schema->createTable('test')->addColumn('id', 'integer');
19+
20+
// First to be executed
21+
$this->addSql('SELECT 1');
22+
}
23+
}

0 commit comments

Comments
 (0)