Skip to content

Commit

Permalink
Add migrate:fresh command
Browse files Browse the repository at this point in the history
  • Loading branch information
brendt committed Aug 12, 2024
1 parent deed784 commit 0bbe681
Show file tree
Hide file tree
Showing 20 changed files with 238 additions and 17 deletions.
2 changes: 2 additions & 0 deletions src/Tempest/Console/Console.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

interface Console
{
public function call(string $command): ExitCode;

public function readln(): string;

public function read(int $bytes): string;
Expand Down
7 changes: 7 additions & 0 deletions src/Tempest/Console/GenericConsole.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Tempest\Console;

use Closure;
use Tempest\Console\Actions\ExecuteConsoleCommand;
use Tempest\Console\Components\Interactive\ConfirmComponent;
use Tempest\Console\Components\Interactive\MultipleChoiceComponent;
use Tempest\Console\Components\Interactive\PasswordComponent;
Expand All @@ -27,9 +28,15 @@ public function __construct(
private readonly OutputBuffer $output,
private readonly InputBuffer $input,
private readonly Highlighter $highlighter,
private readonly ExecuteConsoleCommand $executeConsoleCommand,
) {
}

public function call(string $command): ExitCode
{
return ($this->executeConsoleCommand)($command);
}

public function setComponentRenderer(InteractiveComponentRenderer $componentRenderer): self
{
$this->componentRenderer = $componentRenderer;
Expand Down
3 changes: 3 additions & 0 deletions src/Tempest/Console/Initializers/ConsoleInitializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Tempest\Console\Initializers;

use Tempest\Console\Actions\ExecuteConsoleCommand;
use Tempest\Console\Commands\ScheduleTaskCommand;
use Tempest\Console\Components\InteractiveComponentRenderer;
use Tempest\Console\Console;
Expand Down Expand Up @@ -49,6 +50,7 @@ public function backgroundTaskConsole(Container $container): GenericConsole
output: $container->get(LogOutputBuffer::class),
input: new UnsupportedInputBuffer(),
highlighter: $textHighlighter,
executeConsoleCommand: $container->get(ExecuteConsoleCommand::class),
);
}

Expand All @@ -60,6 +62,7 @@ public function cliConsole(Container $container): GenericConsole
output: $container->get(StdoutOutputBuffer::class),
input: $container->get(StdinInputBuffer::class),
highlighter: $terminalHighlighter,
executeConsoleCommand: $container->get(ExecuteConsoleCommand::class),
))->setComponentRenderer($container->get(InteractiveComponentRenderer::class));

$container->singleton(ConsoleExceptionHandler::class, fn () => new ConsoleExceptionHandler(
Expand Down
1 change: 1 addition & 0 deletions src/Tempest/Console/Testing/ConsoleTester.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public function call(string|Closure|array $command): self
output: $memoryOutputBuffer,
input: $memoryInputBuffer,
highlighter: $clone->container->get(Highlighter::class),
executeConsoleCommand: $clone->container->get(ExecuteConsoleCommand::class),
);

if ($this->componentRenderer) {
Expand Down
14 changes: 14 additions & 0 deletions src/Tempest/Database/Migrations/FreshMigrationFailed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Tempest\Database\Migrations;

use Throwable;

final readonly class FreshMigrationFailed
{
public function __construct(public Throwable $throwable)
{
}
}
51 changes: 48 additions & 3 deletions src/Tempest/Database/Migrations/MigrationManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@

namespace Tempest\Database\Migrations;

use Exception;
use PDOException;
use Tempest\Container\Container;
use Tempest\Database\Database;
use Tempest\Database\DatabaseConfig;
use Tempest\Database\DatabaseDialect;
use Tempest\Database\Exceptions\QueryException;
use Tempest\Database\Migration as MigrationInterface;
use Tempest\Database\Query;
use Tempest\Database\UnsupportedDialect;
use function Tempest\event;
use function Tempest\map;
use Throwable;
use UnhandledMatchError;

final readonly class MigrationManager
Expand Down Expand Up @@ -58,9 +63,9 @@ public function down(): void
$existingMigrations = Migration::all();
} catch (PDOException $exception) {
/** @throw UnhandledMatchError */
match ((string) $exception->getCode()) {
match ((string)$exception->getCode()) {
$this->databaseConfig->driver()->dialect()->tableNotFoundCode() => event(
event: new MigrationFailed(name: 'Migration', exception: MigrationException::noTable())
event: new MigrationFailed(name: 'Migration', exception: MigrationException::noTable()),
),
default => throw new UnhandledMatchError($exception->getMessage()),
};
Expand Down Expand Up @@ -88,6 +93,46 @@ public function down(): void
}
}

public function dropAll(): void
{
$dialect = $this->databaseConfig->driver()->dialect();

try {
// Get all tables
$tables = map((new Query(match ($dialect) {
DatabaseDialect::MYSQL => "SHOW FULL TABLES WHERE table_type = 'BASE TABLE'",
DatabaseDialect::SQLITE => "select type, name from sqlite_master where type = 'table' and name not like 'sqlite_%'",
default => throw new UnsupportedDialect(),
}))->fetch())->collection()->to(TableDefinition::class);

// Disable foreign key checks
(new Query(match ($dialect) {
DatabaseDialect::MYSQL => 'SET FOREIGN_KEY_CHECKS=0',
DatabaseDialect::SQLITE => 'PRAGMA foreign_keys = 0',
default => throw new UnsupportedDialect(),
}))->execute();

// Drop each table
foreach ($tables as $table) {
(new Query(match ($dialect) {
DatabaseDialect::MYSQL, DatabaseDialect::SQLITE => sprintf('DROP TABLE IF EXISTS %s', $table->name),
default => throw new UnsupportedDialect(),
}))->execute();

event(new TableDropped($table->name));
}
} catch (Throwable $throwable) {
event(new FreshMigrationFailed($throwable));
} finally {
// Enable foreign key checks
(new Query(match ($dialect) {
DatabaseDialect::MYSQL => 'SET FOREIGN_KEY_CHECKS=1',
DatabaseDialect::SQLITE => 'PRAGMA foreign_keys = 1',
default => throw new UnsupportedDialect(),
}))->execute();
}
}

public function executeUp(MigrationInterface $migration): void
{
$query = $migration->up();
Expand Down Expand Up @@ -132,7 +177,7 @@ public function executeDown(MigrationInterface $migration): void
new Query(
"DELETE FROM Migration WHERE name = :name",
['name' => $migration->getName()],
)
),
);
} catch (QueryException $e) {
/**
Expand Down
14 changes: 14 additions & 0 deletions src/Tempest/Database/Migrations/TableDefinition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Tempest\Database\Migrations;

final class TableDefinition
{
public function __construct(
public string $name,
public string $type = 'table',
) {
}
}
12 changes: 12 additions & 0 deletions src/Tempest/Database/Migrations/TableDropped.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Tempest\Database\Migrations;

final readonly class TableDropped
{
public function __construct(public string $name)
{
}
}
15 changes: 15 additions & 0 deletions src/Tempest/Database/UnsupportedDialect.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Tempest\Database;

use Exception;

final class UnsupportedDialect extends Exception
{
public function __construct()
{
parent::__construct('Unsupported dialect');
}
}
9 changes: 5 additions & 4 deletions src/Tempest/Framework/Commands/MigrateDownCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@
use Tempest\Console\ConsoleCommand;
use Tempest\Console\Middleware\CautionMiddleware;
use Tempest\Console\Middleware\ForceMiddleware;
use Tempest\Container\Singleton;
use Tempest\Database\Migrations\MigrationFailed;
use Tempest\Database\Migrations\MigrationManager;
use Tempest\Database\Migrations\MigrationRolledBack;
use Tempest\EventBus\EventHandler;

#[Singleton]
final class MigrateDownCommand
{
private static int $count = 0;
private int $count = 0;

public function __construct(
private readonly Console $console,
Expand All @@ -32,15 +34,14 @@ public function __invoke(): void
{
$this->migrationManager->down();

$this->console->success("Done");
$this->console->writeln(sprintf("Rolled back %s migrations", self::$count));
$this->console->success(sprintf("Rolled back %s migrations", $this->count));
}

#[EventHandler]
public function onMigrationRolledBack(MigrationRolledBack $event): void
{
$this->console->writeln("- Rollback {$event->name}");
self::$count += 1;
$this->count += 1;
}

#[EventHandler]
Expand Down
61 changes: 61 additions & 0 deletions src/Tempest/Framework/Commands/MigrateFreshCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Tempest\Framework\Commands;

use Tempest\Console\Console;
use Tempest\Console\ConsoleCommand;
use Tempest\Console\ExitCode;
use Tempest\Console\Middleware\CautionMiddleware;
use Tempest\Console\Middleware\ForceMiddleware;
use Tempest\Container\Singleton;
use Tempest\Database\Migrations\FreshMigrationFailed;
use Tempest\Database\Migrations\MigrationManager;
use Tempest\Database\Migrations\TableDropped;
use Tempest\EventBus\EventHandler;

#[Singleton]
final class MigrateFreshCommand
{
private int $count = 0;

public function __construct(
private readonly Console $console,
private readonly MigrationManager $migrationManager,
) {
}

#[ConsoleCommand(
name: 'migrate:fresh',
description: 'Drop all tables and rerun migrations from scratch',
middleware: [ForceMiddleware::class, CautionMiddleware::class],
)]
public function __invoke(): ExitCode
{
$this->console->info('Dropping tables…');

$this->migrationManager->dropAll();

$this->console
->success(sprintf("Dropped %s tables", $this->count))
->writeln();

$this->console->info('Migrate up…');

return $this->console->call('migrate:up');
}

#[EventHandler]
public function onTableDropped(TableDropped $event): void
{
$this->console->writeln("- Dropped {$event->name}");
$this->count += 1;
}

#[EventHandler]
public function onFreshMigrationFailed(FreshMigrationFailed $event): void
{
$this->console->error($event->throwable->getMessage());
}
}
10 changes: 6 additions & 4 deletions src/Tempest/Framework/Commands/MigrateUpCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@
use Tempest\Console\ConsoleCommand;
use Tempest\Console\Middleware\CautionMiddleware;
use Tempest\Console\Middleware\ForceMiddleware;
use Tempest\Container\Singleton;
use Tempest\Database\Migrations\MigrationFailed;
use Tempest\Database\Migrations\MigrationManager;
use Tempest\Database\Migrations\MigrationMigrated;
use Tempest\EventBus\EventHandler;

#[Singleton]
final class MigrateUpCommand
{
private static int $count = 0;
private int $count = 0;

public function __construct(
private readonly Console $console,
Expand All @@ -32,15 +34,15 @@ public function __invoke(): void
{
$this->migrationManager->up();

$this->console->success("Done");
$this->console->writeln(sprintf("Migrated %s migrations", self::$count));
$this->console
->success(sprintf("Migrated %s migrations", $this->count));
}

#[EventHandler]
public function onMigrationMigrated(MigrationMigrated $event): void
{
$this->console->writeln("- {$event->name}");
self::$count += 1;
$this->count += 1;
}

#[EventHandler]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Tests\Tempest\Integration\Console\Exceptions;

use Exception;
use Tempest\Console\Actions\ExecuteConsoleCommand;
use Tempest\Console\Exceptions\ConsoleExceptionHandler;
use Tempest\Console\GenericConsole;
use Tempest\Console\Highlight\TextTerminalTheme;
Expand All @@ -31,6 +32,7 @@ public function test_render_console_exception(): void
output: $output,
input: new UnsupportedInputBuffer(),
highlighter: $highlighter,
executeConsoleCommand: $this->container->get(ExecuteConsoleCommand::class),
),
highlighter: $highlighter,
argumentBag: $this->container->get(ConsoleArgumentBag::class),
Expand Down
2 changes: 2 additions & 0 deletions tests/Integration/Console/RenderConsoleCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Tests\Tempest\Integration\Console;

use ReflectionMethod;
use Tempest\Console\Actions\ExecuteConsoleCommand;
use Tempest\Console\Actions\RenderConsoleCommand;
use Tempest\Console\ConsoleCommand;
use Tempest\Console\GenericConsole;
Expand Down Expand Up @@ -37,6 +38,7 @@ public function test_render()
output: $output,
input: new UnsupportedInputBuffer(),
highlighter: $highlighter,
executeConsoleCommand: $this->container->get(ExecuteConsoleCommand::class)
);

(new RenderConsoleCommand($console))($consoleCommand);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

declare(strict_types=1);

namespace Tests\Tempest\Integration\Console\Commands;
namespace Tests\Tempest\Integration\Framework\Commands;

use Tempest\Framework\Application\AppConfig;
use Tests\Tempest\Integration\Console\Commands\MyDiscovery;
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Tests\Tempest\Integration\Console\Commands;
namespace Tests\Tempest\Integration\Framework\Commands;

use Tempest\Framework\Application\AppConfig;
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
Expand Down
Loading

0 comments on commit 0bbe681

Please sign in to comment.