Skip to content

Commit

Permalink
Give command handlers the ability to save and restore their state (#208)
Browse files Browse the repository at this point in the history
* Give command handlers the ability to save and restore their state before injecting input / output objects. Do not inject into objects that do not do this.

* Looks like I may have removed some injection points that are actually necessary.
  • Loading branch information
greg-1-anderson committed Aug 30, 2020
1 parent 4f7a58b commit 30734aa
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 76 deletions.
23 changes: 16 additions & 7 deletions src/AnnotatedCommand.php
Expand Up @@ -6,6 +6,8 @@
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Output\OutputAwareInterface;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Consolidation\AnnotatedCommand\State\State;
use Consolidation\AnnotatedCommand\State\StateHelper;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputAwareInterface;
Expand Down Expand Up @@ -276,36 +278,40 @@ public function optionsHookForHookAnnotations($commandInfoList)
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
$this->injectIntoCommandfileInstance($input, $output);
$state = $this->injectIntoCommandfileInstance($input, $output);
$this->commandProcessor()->interact(
$input,
$output,
$this->getNames(),
$this->annotationData
);
$state->restore();
}

protected function initialize(InputInterface $input, OutputInterface $output)
{
$this->injectIntoCommandfileInstance($input, $output);
$state = $this->injectIntoCommandfileInstance($input, $output);
// Allow the hook manager a chance to provide configuration values,
// if there are any registered hooks to do that.
$this->commandProcessor()->initializeHook($input, $this->getNames(), $this->annotationData);
$state->restore();
}

/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->injectIntoCommandfileInstance($input, $output);
$state = $this->injectIntoCommandfileInstance($input, $output);
// Validate, run, process, alter, handle results.
return $this->commandProcessor()->process(
$result = $this->commandProcessor()->process(
$output,
$this->getNames(),
$this->commandCallback,
$this->createCommandData($input, $output)
);
$state->restore();
return $result;
}

/**
Expand All @@ -316,7 +322,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
*/
public function processResults(InputInterface $input, OutputInterface $output, $results)
{
$this->injectIntoCommandfileInstance($input, $output);
$state = $this->injectIntoCommandfileInstance($input, $output);
$commandData = $this->createCommandData($input, $output);
$commandProcessor = $this->commandProcessor();
$names = $this->getNames();
Expand All @@ -325,12 +331,14 @@ public function processResults(InputInterface $input, OutputInterface $output, $
$results,
$commandData
);
return $commandProcessor->handleResults(
$status = $commandProcessor->handleResults(
$output,
$names,
$results,
$commandData
);
$state->restore();
return $status;
}

protected function createCommandData(InputInterface $input, OutputInterface $output)
Expand Down Expand Up @@ -359,9 +367,10 @@ protected function createCommandData(InputInterface $input, OutputInterface $out
*
* @param callable $commandCallback
* @param CommandData $commandData
* @return State
*/
public function injectIntoCommandfileInstance(InputInterface $input, OutputInterface $output)
{
InjectionHelper::injectIntoCallbackObject($this->commandCallback, $input, $output);
return StateHelper::injectIntoCallbackObject($this->commandCallback, $input, $output);
}
}
11 changes: 4 additions & 7 deletions src/Hooks/Dispatchers/CommandEventHookDispatcher.php
Expand Up @@ -4,7 +4,8 @@

use Consolidation\AnnotatedCommand\AnnotatedCommand;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\InjectionHelper;
use Consolidation\AnnotatedCommand\State\State;
use Consolidation\AnnotatedCommand\State\StateHelper;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
Expand All @@ -21,14 +22,9 @@ class CommandEventHookDispatcher extends HookDispatcher
*/
public function callCommandEventHooks(ConsoleCommandEvent $event)
{
$command = $event->getCommand();
$input = $event->getInput();
$output = $event->getOutput();

if ($command instanceof AnnotatedCommand) {
$command->injectIntoCommandfileInstance($input, $output);
}

$hooks = [
HookManager::PRE_COMMAND_EVENT,
HookManager::COMMAND_EVENT,
Expand All @@ -40,8 +36,9 @@ public function callCommandEventHooks(ConsoleCommandEvent $event)
$commandEvent->dispatch($event, ConsoleEvents::COMMAND);
}
if (is_callable($commandEvent)) {
InjectionHelper::injectIntoCallbackObject($commandEvent, $input, $output);
$state = StateHelper::injectIntoCallbackObject($commandEvent, $input, $output);
$commandEvent($event);
$state->restore();
}
}
}
Expand Down
12 changes: 10 additions & 2 deletions src/Hooks/Dispatchers/InitializeHookDispatcher.php
Expand Up @@ -5,7 +5,8 @@
use Consolidation\AnnotatedCommand\AnnotationData;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Hooks\InitializeHookInterface;
use Consolidation\AnnotatedCommand\InjectionHelper;
use Consolidation\AnnotatedCommand\State\State;
use Consolidation\AnnotatedCommand\State\StateHelper;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;

Expand All @@ -31,7 +32,14 @@ public function initialize(

protected function callInitializeHook($provider, $input, AnnotationData $annotationData)
{
InjectionHelper::injectIntoCallbackObject($provider, $input);
$state = StateHelper::injectIntoCallbackObject($provider, $input);
$result = $this->doInitializeHook($provider, $input, $annotationData);
$state->restore();
return $result;
}

private function doInitializeHook($provider, $input, AnnotationData $annotationData)
{
if ($provider instanceof InitializeHookInterface) {
return $provider->initialize($input, $annotationData);
}
Expand Down
12 changes: 10 additions & 2 deletions src/Hooks/Dispatchers/InteractHookDispatcher.php
Expand Up @@ -5,7 +5,8 @@
use Consolidation\AnnotatedCommand\AnnotationData;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Hooks\InteractorInterface;
use Consolidation\AnnotatedCommand\InjectionHelper;
use Consolidation\AnnotatedCommand\State\State;
use Consolidation\AnnotatedCommand\State\StateHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

Expand All @@ -32,7 +33,14 @@ public function interact(

protected function callInteractor($interactor, $input, $output, AnnotationData $annotationData)
{
InjectionHelper::injectIntoCallbackObject($interactor, $input, $output);
$state = StateHelper::injectIntoCallbackObject($interactor, $input, $output);
$result = $this->doInteractor($interactor, $input, $output, $annotationData);
$state->restore();
return $result;
}

private function doInteractor($interactor, $input, $output, AnnotationData $annotationData)
{
if ($interactor instanceof InteractorInterface) {
return $interactor->interact($input, $output, $annotationData);
}
Expand Down
12 changes: 10 additions & 2 deletions src/Hooks/Dispatchers/ProcessResultHookDispatcher.php
Expand Up @@ -6,7 +6,8 @@
use Consolidation\AnnotatedCommand\CommandData;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Hooks\ProcessResultInterface;
use Consolidation\AnnotatedCommand\InjectionHelper;
use Consolidation\AnnotatedCommand\State\State;
use Consolidation\AnnotatedCommand\State\StateHelper;

/**
* Call hooks
Expand Down Expand Up @@ -39,7 +40,14 @@ public function process($result, CommandData $commandData)

protected function callProcessor($processor, $result, CommandData $commandData)
{
InjectionHelper::injectIntoCallbackObject($processor, $commandData->input(), $commandData->output());
$state = StateHelper::injectIntoCallbackObject($processor, $commandData->input(), $commandData->output());
$result = $this->doProcessor($processor, $result, $commandData);
$state->restore();
return $result;
}

private function doProcessor($processor, $result, CommandData $commandData)
{
$processed = null;
if ($processor instanceof ProcessResultInterface) {
$processed = $processor->process($result, $commandData);
Expand Down
12 changes: 10 additions & 2 deletions src/Hooks/Dispatchers/ValidateHookDispatcher.php
Expand Up @@ -7,7 +7,8 @@
use Consolidation\AnnotatedCommand\CommandError;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Hooks\ValidatorInterface;
use Consolidation\AnnotatedCommand\InjectionHelper;
use Consolidation\AnnotatedCommand\State\State;
use Consolidation\AnnotatedCommand\State\StateHelper;

/**
* Call hooks
Expand Down Expand Up @@ -37,7 +38,14 @@ public function validate(CommandData $commandData)

protected function callValidator($validator, CommandData $commandData)
{
InjectionHelper::injectIntoCallbackObject($validator, $commandData->input(), $commandData->output());
$state = StateHelper::injectIntoCallbackObject($validator, $commandData->input(), $commandData->output());
$result = $this->doValidator($validator, $commandData);
$state->restore();
return $result;
}

private function doValidator($validator, CommandData $commandData)
{
if ($validator instanceof ValidatorInterface) {
return $validator->validate($commandData);
}
Expand Down
54 changes: 0 additions & 54 deletions src/InjectionHelper.php

This file was deleted.

10 changes: 10 additions & 0 deletions src/State.php
@@ -0,0 +1,10 @@
<?php
namespace Consolidation\AnnotatedCommand;

interface State
{
/**
* Restore state to a previously cached value.
*/
public function restore();
}
12 changes: 12 additions & 0 deletions src/State/SavableState.php
@@ -0,0 +1,12 @@
<?php
namespace Consolidation\AnnotatedCommand\State;

interface SavableState
{
/**
* Return the current state of this object.
*
* @return State
*/
public function currentState();
}
10 changes: 10 additions & 0 deletions src/State/State.php
@@ -0,0 +1,10 @@
<?php
namespace Consolidation\AnnotatedCommand\State;

interface State
{
/**
* Restore state to a previously cached value.
*/
public function restore();
}
77 changes: 77 additions & 0 deletions src/State/StateHelper.php
@@ -0,0 +1,77 @@
<?php
namespace Consolidation\AnnotatedCommand\State;

use Consolidation\AnnotatedCommand\Output\OutputAwareInterface;
use Consolidation\AnnotatedCommand\State\SavableState;
use Consolidation\AnnotatedCommand\State\State;
use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class StateHelper
{
/**
* Inject $input and $output into the command instance if it is set up to receive them.
*
* @param Callable|object $callback
* @param OutputInterface $output
* @return State
*/
public static function injectIntoCallbackObject($callback, InputInterface $input, OutputInterface $output = null)
{
return static::inject(static::recoverCallbackObject($callback), $input, $output);
}

/**
* Inject $input and $output into the command instance if it is set up to receive them.
*
* @param Callable|object $callback
* @param OutputInterface $output
* @return State
*/
public static function inject($target, InputInterface $input, OutputInterface $output = null)
{
// Do not allow injection unless the target can save its state
if (!$target || !($target instanceof SavableState)) {
return new class implements State {
public function restore()
{
}
};
}

$state = $target->currentState();

if ($target instanceof InputAwareInterface) {
$target->setInput($input);
}
if (isset($output) && $target instanceof OutputAwareInterface) {
$target->setOutput($output);
}

return $state;
}

/**
* If the command callback is a method of an object, return the object.
*
* @param Callable|object $callback
* @return object|bool
*/
protected static function recoverCallbackObject($callback)
{
if (is_object($callback)) {
return $callback;
}

if (!is_array($callback)) {
return false;
}

if (!is_object($callback[0])) {
return false;
}

return $callback[0];
}
}

0 comments on commit 30734aa

Please sign in to comment.