Add command requirements support

master 3.6.0
Julien Rosset 9 months ago
parent 7d21bb492c
commit 26815fcac3

@ -20,6 +20,7 @@
"jrosset/extendedmonolog": "^2.0", "jrosset/extendedmonolog": "^2.0",
"psr/log": "^2.0", "psr/log": "^2.0",
"symfony/console": "^6.1", "symfony/console": "^6.1",
"symfony/event-dispatcher": "^6.1",
"voku/arrayy": "^7.9" "voku/arrayy": "^7.9"
}, },
"autoload": { "autoload": {

@ -0,0 +1,22 @@
<?php
namespace jrosset\CliProgram;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Helper class for command/application output
*/
abstract class OutputHelper {
/**
* Get the “error” output if exists, else the output itself
*
* @param OutputInterface $output The output
*
* @return OutputInterface The “error” output if exists, else the output itself
*/
public static final function getErrorOutput (OutputInterface $output): OutputInterface {
return $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
}
}

@ -0,0 +1,24 @@
<?php
namespace jrosset\CliProgram\Requirements;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;
/**
* Interface for a command requirements
*/
interface IRequirements {
/**
* Check the requirements
*
* @param InputInterface $input The command input
* @param OutputInterface $output The command output
*
* @return void
*
* @throws Throwable If a requirement failed
*/
public function checkRequirements (InputInterface $input, OutputInterface $output): void;
}

@ -0,0 +1,72 @@
<?php
namespace jrosset\CliProgram\Requirements;
use jrosset\CliProgram\OutputHelper;
use ReflectionAttribute;
use ReflectionClass;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Throwable;
/**
* An application with command requirements checking
*
* @see IRequirements
*/
trait TRequirementsApplication {
/**
* Register the listener for command requirements
*
* @param EventDispatcherInterface|null $dispatcher The event dispatcher is already existing
*
* @return void
*/
protected final function registerCommandRequirementsListener (?EventDispatcherInterface $dispatcher = null): void {
$dispatcher ??= new EventDispatcher();
$dispatcher->addListener(ConsoleEvents::COMMAND, $this->checkCommandRequirements(...));
$this->setDispatcher($dispatcher);
}
/**
* Check the requirements of a command
*
* @param ConsoleCommandEvent $event The command event
*
* @return void
*/
private function checkCommandRequirements (ConsoleCommandEvent $event): void {
$commandInput = $event->getInput();
$commandOutput = $event->getOutput();
try {
//region Check the command is valid
if (($command = $event->getCommand()) === null) {
return;
}
$commandReflection = new ReflectionClass($command);
//endregion
//region Check attribute requirements
foreach ($commandReflection->getAttributes(IRequirements::class, ReflectionAttribute::IS_INSTANCEOF) as $commandRequirementsAttribute) {
/** @var IRequirements $commandRequirementsAttributeInstance */
$commandRequirementsAttributeInstance = $commandRequirementsAttribute->newInstance();
$commandRequirementsAttributeInstance->checkRequirements($commandInput, $commandOutput);
}
//endregion
//region Contrôle pré-requis (implémentation directe)
if ($command instanceof IRequirements) {
$command->checkRequirements($commandInput, $commandOutput);
}
//endregion
}
catch (Throwable $exception) {
$this->renderThrowable($exception, OutputHelper::getErrorOutput($commandOutput));
$event->disableCommand();
$event->stopPropagation();
return;
}
}
}

@ -6,12 +6,15 @@ use jrosset\CliProgram\ApplicationWithCommandMonolog;
use jrosset\CliProgram\AutoDiscovery\AutoDiscoveryDirectory; use jrosset\CliProgram\AutoDiscovery\AutoDiscoveryDirectory;
use jrosset\CliProgram\AutoDiscovery\TAutoDiscoveryApplication; use jrosset\CliProgram\AutoDiscovery\TAutoDiscoveryApplication;
use jrosset\CliProgram\AutoPrefix\AutoPrefixNamespaceManager; use jrosset\CliProgram\AutoPrefix\AutoPrefixNamespaceManager;
use jrosset\CliProgram\Requirements\TRequirementsApplication;
class Application extends ApplicationWithCommandMonolog { class Application extends ApplicationWithCommandMonolog {
use TAutoDiscoveryApplication; use TAutoDiscoveryApplication;
use TRequirementsApplication;
public function __construct (string $name = 'UNKNOWN', string $version = 'UNKNOWN') { public function __construct (string $name = 'UNKNOWN', string $version = 'UNKNOWN') {
parent::__construct(__DIR__ . '/logs/', $name, $version); parent::__construct(__DIR__ . '/logs/', $name, $version);
$this->registerCommandRequirementsListener();
$spot = new AutoDiscoveryDirectory(__DIR__ . '/Commands'); $spot = new AutoDiscoveryDirectory(__DIR__ . '/Commands');
$spot->getAutoPrefixManagers()->prepend(new AutoPrefixNamespaceManager('\\jrosset\\Tests\\Commands', 'test')); $spot->getAutoPrefixManagers()->prepend(new AutoPrefixNamespaceManager('\\jrosset\\Tests\\Commands', 'test'));

@ -0,0 +1,15 @@
<?php
namespace jrosset\Tests\Commands;
use jrosset\Tests\FailedRequirement;
#[FailedRequirement]
class FailedHello extends Hello {
/**
* @inheritDoc
*/
public function __construct () {
parent::__construct('failedHello');
}
}

@ -10,12 +10,14 @@ use jrosset\CliProgram\Validation\Validators\DateValidator;
use jrosset\CliProgram\Validation\Validators\EnumValidator; use jrosset\CliProgram\Validation\Validators\EnumValidator;
use jrosset\CliProgram\Validation\Validators\IntegerValidator; use jrosset\CliProgram\Validation\Validators\IntegerValidator;
use jrosset\Tests\Lang; use jrosset\Tests\Lang;
use jrosset\Tests\SuccessRequirement;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
#[SuccessRequirement]
class Hello extends CommandWithValidation { class Hello extends CommandWithValidation {
/** /**
* @inheritdoc * @inheritdoc
@ -25,8 +27,8 @@ class Hello extends CommandWithValidation {
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function __construct () { public function __construct (?string $name = null) {
parent::__construct('hello', 'bonjour'); parent::__construct($name ?? 'hello', 'bonjour');
} }
/** /**

@ -0,0 +1,19 @@
<?php
namespace jrosset\Tests;
use Attribute;
use jrosset\CliProgram\Requirements\IRequirements;
use LogicException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[Attribute(Attribute::TARGET_CLASS)]
class FailedRequirement implements IRequirements {
/**
* @inheritDoc
*/
public function checkRequirements (InputInterface $input, OutputInterface $output): void {
throw new LogicException('The "foo" requirement failed');
}
}

@ -0,0 +1,17 @@
<?php
namespace jrosset\Tests;
use Attribute;
use jrosset\CliProgram\Requirements\IRequirements;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[Attribute(Attribute::TARGET_CLASS)]
class SuccessRequirement implements IRequirements {
/**
* @inheritDoc
*/
public function checkRequirements (InputInterface $input, OutputInterface $output): void {
}
}
Loading…
Cancel
Save