Skip to content
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
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,29 @@ $runner->createSchemaTable();
$runner->apply();
```


#### Connection-Targeted Migrations

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):

```php
class CreateReportsTable extends AbstractMigration {
public function getTargetConnections(): array {
return ['reporting-db']; // Only runs against the 'reporting-db' connection
}

public function up(Database $db): void {
// ...
}

public function down(Database $db): void {
// ...
}
}
```

Migrations with an empty `getTargetConnections()` (the default) run on all connections. The connection name comes from `ConnectionInfo::getName()`.

### Database Seeders

Populate your database with sample data:
Expand Down
12 changes: 12 additions & 0 deletions WebFiori/Database/Schema/DatabaseChange.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,18 @@ public function getName(): string {
return static::class;
}

/**
* Get the target connections where this change should be executed.
*
* Override this method to restrict execution to specific named connections.
* Uses the logical connection name from ConnectionInfo::getName().
*
* @return array Array of connection names. Empty array means all connections.
*/
public function getTargetConnections(): array {
return [];
}

/**
* Get the type of database change.
*
Expand Down
42 changes: 42 additions & 0 deletions WebFiori/Database/Schema/SchemaRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@ public function apply(): DatabaseChangeResult {
continue;
}

if (!$this->shouldRunForConnection($change)) {
$processed[$name] = true;
$result->addSkipped($change, 'Connection mismatch');
continue;
}

if (!$this->areDependenciesSatisfied($change)) {
continue; // Don't mark as processed - may be satisfied later
}
Expand Down Expand Up @@ -211,6 +217,10 @@ public function applyOne(): ?DatabaseChange {
continue;
}

if (!$this->shouldRunForConnection($change)) {
continue;
}

if (!$this->areDependenciesSatisfied($change)) {
continue;
}
Expand Down Expand Up @@ -364,6 +374,10 @@ public function getPendingChanges(bool $withQueries = false): array {
continue;
}

if (!$this->shouldRunForConnection($change)) {
continue;
}

$info = ['change' => $change, 'queries' => []];

if ($withQueries) {
Expand Down Expand Up @@ -581,6 +595,10 @@ public function skipAll(): array {
continue;
}

if (!$this->shouldRunForConnection($change)) {
continue;
}

$this->getRepository()->recordSkipped($change, $batch);
$skipped[] = $change;
}
Expand Down Expand Up @@ -613,6 +631,10 @@ public function skipNext(int $count = 1): array {
continue;
}

if (!$this->shouldRunForConnection($change)) {
continue;
}

$this->getRepository()->recordSkipped($change, $batch);
$skipped[] = $change;
}
Expand Down Expand Up @@ -647,6 +669,14 @@ public function skipUpTo(string $changeName): array {
continue;
}

if (!$this->shouldRunForConnection($change)) {
if ($change->getName() === $changeName) {
break;
}

continue;
}

$this->getRepository()->recordSkipped($change, $batch);
$skipped[] = $change;

Expand Down Expand Up @@ -741,6 +771,18 @@ private function resolveClassName(\SplFileInfo $file, string $basePath, string $
return null;
}

private function shouldRunForConnection(DatabaseChange $change): bool {
$targets = $change->getTargetConnections();

if (empty($targets)) {
return true;
}

$connInfo = $this->getConnectionInfo();

return $connInfo !== null && in_array($connInfo->getName(), $targets);
}

private function shouldRunInEnvironment(DatabaseChange $change): bool {
$environments = $change->getEnvironments();

Expand Down
51 changes: 51 additions & 0 deletions examples/06-migrations/CreateReportsTableMigration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

use WebFiori\Database\ColOption;
use WebFiori\Database\Database;
use WebFiori\Database\DataType;
use WebFiori\Database\Schema\AbstractMigration;

/**
* Migration that only runs against the 'reporting-db' connection.
*
* In multi-database architectures, some tables belong to specific databases.
* For example, a reporting database might have aggregation tables that don't
* belong in the main application database.
*
* By overriding getTargetConnections(), this migration will be skipped
* when running against any connection other than 'reporting-db'.
*/
class CreateReportsTableMigration extends AbstractMigration {
public function getTargetConnections(): array {
return ['reporting-db'];
}

public function up(Database $db): void {
$db->createBlueprint('daily_reports')->addColumns([
'id' => [
ColOption::TYPE => DataType::INT,
ColOption::PRIMARY => true,
ColOption::AUTO_INCREMENT => true
],
'report-date' => [
ColOption::TYPE => DataType::DATE,
ColOption::NULL => false
],
'total-orders' => [
ColOption::TYPE => DataType::INT,
ColOption::DEFAULT => 0
],
'revenue' => [
ColOption::TYPE => DataType::DECIMAL,
ColOption::SIZE => 10
]
]);

$db->table('daily_reports')->createTable();
$db->execute();
}

public function down(Database $db): void {
$db->raw("DROP TABLE IF EXISTS daily_reports")->execute();
}
}
68 changes: 35 additions & 33 deletions examples/06-migrations/README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
# Database Migrations

This example demonstrates how to create and run database migrations using WebFiori's migration system.

## What This Example Shows

- Creating migration classes that extend AbstractMigration
- Implementing up() and down() methods for schema changes
- Running migrations using SchemaRunner
- Rolling back migrations

## Files

- [`example.php`](example.php) - Main example code
- [`CreateUsersTableMigration.php`](CreateUsersTableMigration.php) - Migration to create users table
- [`AddEmailIndexMigration.php`](AddEmailIndexMigration.php) - Migration to add email index

## Running the Example

```bash
php example.php
```

## Expected Output

The example will create migration classes, run them to modify the database schema, and demonstrate rollback functionality.


## Related Examples

- [03-table-blueprints](../03-table-blueprints/) - Define table structures
- [07-seeders](../07-seeders/) - Populate data after migrations
- [05-transactions](../05-transactions/) - Understand rollback behavior
# Database Migrations

This example demonstrates how to create and run database migrations using WebFiori's migration system.

## What This Example Shows

- Creating migration classes that extend AbstractMigration
- Implementing up() and down() methods for schema changes
- Running migrations using SchemaRunner
- Rolling back migrations
- Connection-targeted migrations (restricting migrations to specific databases)

## Files

- [`example.php`](example.php) - Main example code
- [`CreateUsersTableMigration.php`](CreateUsersTableMigration.php) - Migration to create users table
- [`AddEmailIndexMigration.php`](AddEmailIndexMigration.php) - Migration to add email index
- [`CreateReportsTableMigration.php`](CreateReportsTableMigration.php) - Migration targeting a specific connection

## Running the Example

```bash
php example.php
```

## Expected Output

The example will create migration classes, run them to modify the database schema, and demonstrate rollback functionality.


## Related Examples

- [03-table-blueprints](../03-table-blueprints/) - Define table structures
- [07-seeders](../07-seeders/) - Populate data after migrations
- [05-transactions](../05-transactions/) - Understand rollback behavior
39 changes: 38 additions & 1 deletion examples/06-migrations/example.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,44 @@
echo "\n";

echo SEP;
echo "7. Cleanup:\n";
echo "7. Connection-Targeted Migrations:\n";
echo " In multi-database setups, migrations can target specific connections.\n\n";

// Simulate running against 'main-app' connection (not 'reporting-db')
$mainAppConn = new ConnectionInfo('mysql', 'root', '123456', 'testing_db');
$mainAppConn->setName('main-app');

$runner2 = new SchemaRunner($mainAppConn);
require_once __DIR__.'/CreateReportsTableMigration.php';
$runner2->register('CreateReportsTableMigration');
$runner2->createSchemaTable();

$result2 = $runner2->apply();

foreach ($result2->getSkipped() as $skipped) {
echo " ⊘ Skipped: ".$skipped['change']->getName()." (".$skipped['reason'].")\n";
}

// Now simulate running against 'reporting-db' connection
$reportingConn = new ConnectionInfo('mysql', 'root', '123456', 'testing_db');
$reportingConn->setName('reporting-db');

$runner3 = new SchemaRunner($reportingConn);
$runner3->register('CreateReportsTableMigration');
$runner3->createSchemaTable();

$result3 = $runner3->apply();

foreach ($result3->getApplied() as $applied) {
echo " ✓ Applied: ".$applied->getName()." (connection matches)\n";
}

$runner3->rollbackUpTo(null);
$runner3->dropSchemaTable();
echo "\n";

echo SEP;
echo "8. Cleanup:\n";
$runner->dropSchemaTable();
echo " ✓ Schema tracking table dropped\n";
} catch (Exception $e) {
Expand Down
Loading
Loading