Skip to content

Commit 77827fe

Browse files
committed
Merge branch 'main' of https://github.com/WebFiori/database
2 parents b14854a + 7df8fda commit 77827fe

7 files changed

Lines changed: 473 additions & 34 deletions

File tree

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,29 @@ $runner->createSchemaTable();
340340
$runner->apply();
341341
```
342342

343+
344+
#### Connection-Targeted Migrations
345+
346+
In multi-database architectures, you can restrict a migration to specific named connections. This is useful when different databases serve different purposes (e.g., one for user data, another for reporting):
347+
348+
```php
349+
class CreateReportsTable extends AbstractMigration {
350+
public function getTargetConnections(): array {
351+
return ['reporting-db']; // Only runs against the 'reporting-db' connection
352+
}
353+
354+
public function up(Database $db): void {
355+
// ...
356+
}
357+
358+
public function down(Database $db): void {
359+
// ...
360+
}
361+
}
362+
```
363+
364+
Migrations with an empty `getTargetConnections()` (the default) run on all connections. The connection name comes from `ConnectionInfo::getName()`.
365+
343366
### Database Seeders
344367

345368
Populate your database with sample data:

WebFiori/Database/Schema/DatabaseChange.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,18 @@ public function getName(): string {
114114
return static::class;
115115
}
116116

117+
/**
118+
* Get the target connections where this change should be executed.
119+
*
120+
* Override this method to restrict execution to specific named connections.
121+
* Uses the logical connection name from ConnectionInfo::getName().
122+
*
123+
* @return array Array of connection names. Empty array means all connections.
124+
*/
125+
public function getTargetConnections(): array {
126+
return [];
127+
}
128+
117129
/**
118130
* Get the type of database change.
119131
*

WebFiori/Database/Schema/SchemaRunner.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,12 @@ public function apply(): DatabaseChangeResult {
156156
continue;
157157
}
158158

159+
if (!$this->shouldRunForConnection($change)) {
160+
$processed[$name] = true;
161+
$result->addSkipped($change, 'Connection mismatch');
162+
continue;
163+
}
164+
159165
if (!$this->areDependenciesSatisfied($change)) {
160166
continue; // Don't mark as processed - may be satisfied later
161167
}
@@ -211,6 +217,10 @@ public function applyOne(): ?DatabaseChange {
211217
continue;
212218
}
213219

220+
if (!$this->shouldRunForConnection($change)) {
221+
continue;
222+
}
223+
214224
if (!$this->areDependenciesSatisfied($change)) {
215225
continue;
216226
}
@@ -363,6 +373,10 @@ public function getPendingChanges(bool $withQueries = false): array {
363373
continue;
364374
}
365375

376+
if (!$this->shouldRunForConnection($change)) {
377+
continue;
378+
}
379+
366380
$info = ['change' => $change, 'queries' => []];
367381

368382
if ($withQueries) {
@@ -580,6 +594,10 @@ public function skipAll(): array {
580594
continue;
581595
}
582596

597+
if (!$this->shouldRunForConnection($change)) {
598+
continue;
599+
}
600+
583601
$this->getRepository()->recordSkipped($change, $batch);
584602
$skipped[] = $change;
585603
}
@@ -612,6 +630,10 @@ public function skipNext(int $count = 1): array {
612630
continue;
613631
}
614632

633+
if (!$this->shouldRunForConnection($change)) {
634+
continue;
635+
}
636+
615637
$this->getRepository()->recordSkipped($change, $batch);
616638
$skipped[] = $change;
617639
}
@@ -646,6 +668,14 @@ public function skipUpTo(string $changeName): array {
646668
continue;
647669
}
648670

671+
if (!$this->shouldRunForConnection($change)) {
672+
if ($change->getName() === $changeName) {
673+
break;
674+
}
675+
676+
continue;
677+
}
678+
649679
$this->getRepository()->recordSkipped($change, $batch);
650680
$skipped[] = $change;
651681

@@ -740,6 +770,18 @@ private function resolveClassName(\SplFileInfo $file, string $basePath, string $
740770
return null;
741771
}
742772

773+
private function shouldRunForConnection(DatabaseChange $change): bool {
774+
$targets = $change->getTargetConnections();
775+
776+
if (empty($targets)) {
777+
return true;
778+
}
779+
780+
$connInfo = $this->getConnectionInfo();
781+
782+
return $connInfo !== null && in_array($connInfo->getName(), $targets);
783+
}
784+
743785
private function shouldRunInEnvironment(DatabaseChange $change): bool {
744786
$environments = $change->getEnvironments();
745787

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
use WebFiori\Database\ColOption;
4+
use WebFiori\Database\Database;
5+
use WebFiori\Database\DataType;
6+
use WebFiori\Database\Schema\AbstractMigration;
7+
8+
/**
9+
* Migration that only runs against the 'reporting-db' connection.
10+
*
11+
* In multi-database architectures, some tables belong to specific databases.
12+
* For example, a reporting database might have aggregation tables that don't
13+
* belong in the main application database.
14+
*
15+
* By overriding getTargetConnections(), this migration will be skipped
16+
* when running against any connection other than 'reporting-db'.
17+
*/
18+
class CreateReportsTableMigration extends AbstractMigration {
19+
public function getTargetConnections(): array {
20+
return ['reporting-db'];
21+
}
22+
23+
public function up(Database $db): void {
24+
$db->createBlueprint('daily_reports')->addColumns([
25+
'id' => [
26+
ColOption::TYPE => DataType::INT,
27+
ColOption::PRIMARY => true,
28+
ColOption::AUTO_INCREMENT => true
29+
],
30+
'report-date' => [
31+
ColOption::TYPE => DataType::DATE,
32+
ColOption::NULL => false
33+
],
34+
'total-orders' => [
35+
ColOption::TYPE => DataType::INT,
36+
ColOption::DEFAULT => 0
37+
],
38+
'revenue' => [
39+
ColOption::TYPE => DataType::DECIMAL,
40+
ColOption::SIZE => 10
41+
]
42+
]);
43+
44+
$db->table('daily_reports')->createTable();
45+
$db->execute();
46+
}
47+
48+
public function down(Database $db): void {
49+
$db->raw("DROP TABLE IF EXISTS daily_reports")->execute();
50+
}
51+
}

examples/06-migrations/README.md

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,35 @@
1-
# Database Migrations
2-
3-
This example demonstrates how to create and run database migrations using WebFiori's migration system.
4-
5-
## What This Example Shows
6-
7-
- Creating migration classes that extend AbstractMigration
8-
- Implementing up() and down() methods for schema changes
9-
- Running migrations using SchemaRunner
10-
- Rolling back migrations
11-
12-
## Files
13-
14-
- [`example.php`](example.php) - Main example code
15-
- [`CreateUsersTableMigration.php`](CreateUsersTableMigration.php) - Migration to create users table
16-
- [`AddEmailIndexMigration.php`](AddEmailIndexMigration.php) - Migration to add email index
17-
18-
## Running the Example
19-
20-
```bash
21-
php example.php
22-
```
23-
24-
## Expected Output
25-
26-
The example will create migration classes, run them to modify the database schema, and demonstrate rollback functionality.
27-
28-
29-
## Related Examples
30-
31-
- [03-table-blueprints](../03-table-blueprints/) - Define table structures
32-
- [07-seeders](../07-seeders/) - Populate data after migrations
33-
- [05-transactions](../05-transactions/) - Understand rollback behavior
1+
# Database Migrations
2+
3+
This example demonstrates how to create and run database migrations using WebFiori's migration system.
4+
5+
## What This Example Shows
6+
7+
- Creating migration classes that extend AbstractMigration
8+
- Implementing up() and down() methods for schema changes
9+
- Running migrations using SchemaRunner
10+
- Rolling back migrations
11+
- Connection-targeted migrations (restricting migrations to specific databases)
12+
13+
## Files
14+
15+
- [`example.php`](example.php) - Main example code
16+
- [`CreateUsersTableMigration.php`](CreateUsersTableMigration.php) - Migration to create users table
17+
- [`AddEmailIndexMigration.php`](AddEmailIndexMigration.php) - Migration to add email index
18+
- [`CreateReportsTableMigration.php`](CreateReportsTableMigration.php) - Migration targeting a specific connection
19+
20+
## Running the Example
21+
22+
```bash
23+
php example.php
24+
```
25+
26+
## Expected Output
27+
28+
The example will create migration classes, run them to modify the database schema, and demonstrate rollback functionality.
29+
30+
31+
## Related Examples
32+
33+
- [03-table-blueprints](../03-table-blueprints/) - Define table structures
34+
- [07-seeders](../07-seeders/) - Populate data after migrations
35+
- [05-transactions](../05-transactions/) - Understand rollback behavior

examples/06-migrations/example.php

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,44 @@
9191
echo "\n";
9292

9393
echo SEP;
94-
echo "7. Cleanup:\n";
94+
echo "7. Connection-Targeted Migrations:\n";
95+
echo " In multi-database setups, migrations can target specific connections.\n\n";
96+
97+
// Simulate running against 'main-app' connection (not 'reporting-db')
98+
$mainAppConn = new ConnectionInfo('mysql', 'root', '123456', 'testing_db');
99+
$mainAppConn->setName('main-app');
100+
101+
$runner2 = new SchemaRunner($mainAppConn);
102+
require_once __DIR__.'/CreateReportsTableMigration.php';
103+
$runner2->register('CreateReportsTableMigration');
104+
$runner2->createSchemaTable();
105+
106+
$result2 = $runner2->apply();
107+
108+
foreach ($result2->getSkipped() as $skipped) {
109+
echo " ⊘ Skipped: ".$skipped['change']->getName()." (".$skipped['reason'].")\n";
110+
}
111+
112+
// Now simulate running against 'reporting-db' connection
113+
$reportingConn = new ConnectionInfo('mysql', 'root', '123456', 'testing_db');
114+
$reportingConn->setName('reporting-db');
115+
116+
$runner3 = new SchemaRunner($reportingConn);
117+
$runner3->register('CreateReportsTableMigration');
118+
$runner3->createSchemaTable();
119+
120+
$result3 = $runner3->apply();
121+
122+
foreach ($result3->getApplied() as $applied) {
123+
echo " ✓ Applied: ".$applied->getName()." (connection matches)\n";
124+
}
125+
126+
$runner3->rollbackUpTo(null);
127+
$runner3->dropSchemaTable();
128+
echo "\n";
129+
130+
echo SEP;
131+
echo "8. Cleanup:\n";
95132
$runner->dropSchemaTable();
96133
echo " ✓ Schema tracking table dropped\n";
97134
} catch (Exception $e) {

0 commit comments

Comments
 (0)