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
18 changes: 8 additions & 10 deletions src/cmd/Console.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@

use Exception;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
use Symfony\Component\Console\Output\ConsoleOutput;
use dbschemix\migrator\cmd\presentation\CreateCommand;
use dbschemix\migrator\cmd\presentation\DownCommand;
use dbschemix\migrator\cmd\presentation\FixtureCommand;
Expand All @@ -20,6 +18,9 @@

final readonly class Console
{
/**
* @throws Exception if Symfony Console fails to run the application
*/
public static function run(MigratorInterface $migrator): never
{
$console = new Application();
Expand All @@ -37,13 +38,10 @@ public static function run(MigratorInterface $migrator): never
)
);

$output = new ConsoleOutput();

try {
exit($console->run());
} catch (Exception $e) {
$output->writeln($e->getMessage());
exit(Command::FAILURE);
}
// Application::run() catches uncaught throwables, renders them
// (exception class + message, full stack trace under -v) to stderr,
// and returns a non-zero exit code. We must not wrap it in our own
// catch: doing so swallows the type and trace Symfony would render.
exit($console->run());
}
}
7 changes: 4 additions & 3 deletions src/cmd/presentation/CreateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace dbschemix\migrator\cmd\presentation;

use Override;
use Throwable;
use InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
Expand All @@ -16,6 +15,7 @@
use Symfony\Component\Console\Output\OutputInterface;
use dbschemix\core\exception\MigratorException;
use dbschemix\core\MigratorInterface;
use dbschemix\migrator\cmd\presentation\support\CommandOptions;

#[AsCommand(
name: 'migrate:create',
Expand Down Expand Up @@ -51,10 +51,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
} catch (InvalidArgumentException | MigratorException $e) {
$output->writeln($e->getMessage());
return Command::INVALID;
} catch (Throwable) {
return Command::FAILURE;
}

// Unexpected throwables are not swallowed: they propagate to
// Application::run(), which renders the exception type and message
// (with a full stack trace under -v) and returns a non-zero exit code.
return Command::SUCCESS;
}
}
16 changes: 7 additions & 9 deletions src/cmd/presentation/DownCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
namespace dbschemix\migrator\cmd\presentation;

use Override;
use Throwable;
use InvalidArgumentException;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use dbschemix\core\exception\InitializationException;
use dbschemix\core\exception\MigratorException;
use dbschemix\core\MigratorInterface;
use dbschemix\migrator\cmd\presentation\support\CommandOptions;
use dbschemix\migrator\cmd\presentation\support\MigratorExceptionReporter;

#[AsCommand(
name: 'migrate:down',
Expand All @@ -24,6 +24,7 @@
final class DownCommand extends Command
{
use CommandOptions;
use MigratorExceptionReporter;

/**
* @throws LogicException
Expand Down Expand Up @@ -66,15 +67,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int
try {
$this->migrator->down($this->getOptions($input));
} catch (InvalidArgumentException | MigratorException $e) {
if ($e instanceof InitializationException) {
$output->writeln('Calling the command "migrate:init" may help fix the error.');
}
$output->writeln($e->getMessage());
return Command::INVALID;
} catch (Throwable) {
return Command::FAILURE;
return $this->reportRecoverableFailure($e, $output);
}

// Unexpected throwables are not swallowed: they propagate to
// Application::run(), which renders the exception type and message
// (with a full stack trace under -v) and returns a non-zero exit code.
return Command::SUCCESS;
}
}
7 changes: 4 additions & 3 deletions src/cmd/presentation/FixtureCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace dbschemix\migrator\cmd\presentation;

use Override;
use Throwable;
use InvalidArgumentException;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
Expand All @@ -15,6 +14,7 @@
use Symfony\Component\Console\Output\OutputInterface;
use dbschemix\core\exception\MigratorException;
use dbschemix\core\MigratorInterface;
use dbschemix\migrator\cmd\presentation\support\CommandOptions;

#[AsCommand(
name: 'migrate:fixture',
Expand Down Expand Up @@ -61,10 +61,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
} catch (InvalidArgumentException | MigratorException $e) {
$output->writeln($e->getMessage());
return Command::INVALID;
} catch (Throwable) {
return Command::FAILURE;
}

// Unexpected throwables are not swallowed: they propagate to
// Application::run(), which renders the exception type and message
// (with a full stack trace under -v) and returns a non-zero exit code.
return Command::SUCCESS;
}
}
6 changes: 3 additions & 3 deletions src/cmd/presentation/InitCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace dbschemix\migrator\cmd\presentation;

use Throwable;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\LogicException;
Expand Down Expand Up @@ -34,10 +33,11 @@ public function __invoke(InputInterface $input, OutputInterface $output): int
} catch (MigratorException $e) {
$output->writeln($e->getMessage());
return Command::INVALID;
} catch (Throwable) {
return Command::FAILURE;
}

// Unexpected throwables are not swallowed: they propagate to
// Application::run(), which renders the exception type and message
// (with a full stack trace under -v) and returns a non-zero exit code.
return Command::SUCCESS;
}
}
16 changes: 7 additions & 9 deletions src/cmd/presentation/RedoCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
namespace dbschemix\migrator\cmd\presentation;

use Override;
use Throwable;
use InvalidArgumentException;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use dbschemix\core\exception\InitializationException;
use dbschemix\core\exception\MigratorException;
use dbschemix\core\MigratorInterface;
use dbschemix\migrator\cmd\presentation\support\CommandOptions;
use dbschemix\migrator\cmd\presentation\support\MigratorExceptionReporter;

#[AsCommand(
name: 'migrate:redo',
Expand All @@ -24,6 +24,7 @@
final class RedoCommand extends Command
{
use CommandOptions;
use MigratorExceptionReporter;

/**
* @throws LogicException
Expand Down Expand Up @@ -66,15 +67,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int
try {
$this->migrator->redo($this->getOptions($input));
} catch (InvalidArgumentException | MigratorException $e) {
if ($e instanceof InitializationException) {
$output->writeln('Calling the command "migrate:init" may help fix the error.');
}
$output->writeln($e->getMessage());
return Command::INVALID;
} catch (Throwable) {
return Command::FAILURE;
return $this->reportRecoverableFailure($e, $output);
}

// Unexpected throwables are not swallowed: they propagate to
// Application::run(), which renders the exception type and message
// (with a full stack trace under -v) and returns a non-zero exit code.
return Command::SUCCESS;
}
}
16 changes: 7 additions & 9 deletions src/cmd/presentation/UpCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
namespace dbschemix\migrator\cmd\presentation;

use Override;
use Throwable;
use InvalidArgumentException;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use dbschemix\core\exception\InitializationException;
use dbschemix\core\exception\MigratorException;
use dbschemix\core\MigratorInterface;
use dbschemix\migrator\cmd\presentation\support\CommandOptions;
use dbschemix\migrator\cmd\presentation\support\MigratorExceptionReporter;

#[AsCommand(
name: 'migrate:up',
Expand All @@ -24,6 +24,7 @@
final class UpCommand extends Command
{
use CommandOptions;
use MigratorExceptionReporter;

/**
* @throws LogicException
Expand Down Expand Up @@ -74,15 +75,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int
try {
$this->migrator->up($this->getOptions($input));
} catch (InvalidArgumentException | MigratorException $e) {
if ($e instanceof InitializationException) {
$output->writeln('Calling the command "migrate:init" may help fix the error.');
}
$output->writeln($e->getMessage());
return Command::INVALID;
} catch (Throwable) {
return Command::FAILURE;
return $this->reportRecoverableFailure($e, $output);
}

// Unexpected throwables are not swallowed: they propagate to
// Application::run(), which renders the exception type and message
// (with a full stack trace under -v) and returns a non-zero exit code.
return Command::SUCCESS;
}
}
16 changes: 7 additions & 9 deletions src/cmd/presentation/VerifyCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
namespace dbschemix\migrator\cmd\presentation;

use Override;
use Throwable;
use InvalidArgumentException;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use dbschemix\core\exception\InitializationException;
use dbschemix\core\exception\MigratorException;
use dbschemix\core\MigratorInterface;
use dbschemix\migrator\cmd\presentation\support\CommandOptions;
use dbschemix\migrator\cmd\presentation\support\MigratorExceptionReporter;

#[AsCommand(
name: 'migrate:verify',
Expand All @@ -24,6 +24,7 @@
final class VerifyCommand extends Command
{
use CommandOptions;
use MigratorExceptionReporter;

/**
* @throws LogicException
Expand Down Expand Up @@ -60,15 +61,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int
try {
$this->migrator->verify($this->getOptions($input));
} catch (InvalidArgumentException | MigratorException $e) {
if ($e instanceof InitializationException) {
$output->writeln('Calling the command "migrate:init" may help fix the error.');
}
$output->writeln($e->getMessage());
return Command::INVALID;
} catch (Throwable) {
return Command::FAILURE;
return $this->reportRecoverableFailure($e, $output);
}

// Unexpected throwables are not swallowed: they propagate to
// Application::run(), which renders the exception type and message
// (with a full stack trace under -v) and returns a non-zero exit code.
return Command::SUCCESS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace dbschemix\migrator\cmd\presentation;
namespace dbschemix\migrator\cmd\presentation\support;

use InvalidArgumentException;
use Symfony\Component\Console\Input\InputInterface;
Expand Down Expand Up @@ -48,8 +48,17 @@ protected function getOptions(InputInterface $input): InputOptions
}

/**
* Normalizes the raw CLI option into the type required by the core
* contract. The "limit" business rule itself is owned by
* {@see InputOptions}, which declares the parameter as a
* non-negative-int; this method only guarantees the transport value
* can satisfy that contract before it reaches the core, so a malformed
* option is rejected at the edge instead of producing an out-of-contract
* value downstream.
*
* @return non-negative-int
* @throws InvalidArgumentException
* @throws InvalidArgumentException if the option cannot be represented
* as the core contract's non-negative-int
*/
private function getOptionLimit(InputInterface $input): int
{
Expand Down
34 changes: 34 additions & 0 deletions src/cmd/presentation/support/MigratorExceptionReporter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace dbschemix\migrator\cmd\presentation\support;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;
use dbschemix\core\exception\InitializationException;

/**
* Renders a recoverable migrator failure to the command output.
*
* The {@see InitializationException} hint text and the condition that
* triggers it live here only, so the four commands that surface it
* (migrate:up / down / redo / verify) cannot drift apart.
*/
trait MigratorExceptionReporter
{
/**
* @return Command::INVALID
*/
private function reportRecoverableFailure(Throwable $e, OutputInterface $output): int
{
if ($e instanceof InitializationException) {
$output->writeln('Calling the command "migrate:init" may help fix the error.');
}

$output->writeln($e->getMessage());

return Command::INVALID;
}
}
2 changes: 0 additions & 2 deletions tests/cmd/BootstrapTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ public function assert_migrator_throws_when_config_returned_null(): void
{
// Given / When / Then
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('return $migrator');

Bootstrap::assertMigrator(null);
}
Expand All @@ -43,7 +42,6 @@ public function assert_migrator_throws_when_config_returned_other_object(): void
{
// Given / When / Then
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('MigratorInterface');

Bootstrap::assertMigrator(new stdClass());
}
Expand Down
Loading
Loading