Skip to content

Instantly share code, notes, and snippets.

@geerteltink
Last active June 1, 2017 12:57
Show Gist options
  • Save geerteltink/0f0cb7656988401719431364d757ad34 to your computer and use it in GitHub Desktop.
Save geerteltink/0f0cb7656988401719431364d757ad34 to your computer and use it in GitHub Desktop.
Symfony Console ProxyCommand
<?php
require __DIR__ . '/../vendor/autoload.php';
use App\Infrastructure\Console\ProxyCommand;
use Symfony\Component\Console\Application;
/** @var \Interop\Container\ContainerInterface $container */
$container = require __DIR__ . '/../config/container.php';
$application = new Application('Application console');
$commands = $container->get('config')['console']['commands'] ?? [];
foreach ($commands as $command) {
$application->add(new ProxyCommand($command, $container));
}
$application->run();
<?php
namespace App\Console;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class GreetCommand extends Command
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
parent::__construct();
}
protected function configure(): void
{
$this
->setName('demo:greet')
->setDescription('Greet someone')
->addArgument(
'name',
InputArgument::OPTIONAL,
'Who do you want to greet?'
)
->addOption(
'yell',
null,
InputOption::VALUE_NONE,
'If set, the task will yell in uppercase letters'
);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$name = $input->getArgument('name');
$text = 'Hello';
if ($name) {
$text = 'Hello ' . $name;
}
if ($input->getOption('yell')) {
$text = strtoupper($text);
}
$output->writeln($text);
$this->logger->info('GreetCommand triggered', ['name' => $name]);
}
}
<?php
namespace App\Console;
use Interop\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
class GreetCommandFactory
{
public function __invoke(ContainerInterface $container)
{
return new GreetCommand(
$container->get(LoggerInterface::class)
);
}
}
<?php
namespace App\Infrastructure\Console;
use Psr\Container\ContainerInterface;
use ReflectionClass;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Symfony Console Proxy Command
*
* Symfony Console loads all classes and their dependencies. Even if you don't
* want to execute a certain command.
*
* This ProxyCommand starts the Command but without loading the dependencies. It
* than copies all original settings to the ProxyCommand. When the actual
* command is called it will construct the original command with its dependencies.
*/
class ProxyCommand extends Command
{
/**
* @var string
*/
private $command;
/**
* @var ContainerInterface
*/
private $container;
public function __construct(string $command, ContainerInterface $container)
{
$this->command = $command;
$this->container = $container;
parent::__construct();
}
/**
* Use Reflection voodoo to to capture the original configuration
*/
protected function configure(): void
{
// Construct the ReflectionClass
$refCommand = new ReflectionClass($this->command);
// Make sure the configure method is accessible
$configure = $refCommand->getMethod('configure');
$configure->setAccessible(true);
// Create the command without loading dependencies
$command = $refCommand->newInstanceWithoutConstructor();
// Call the parent constructor
$parent = $refCommand->getParentClass();
$construct = $parent->getMethod('__construct');
$construct->invoke($command);
// Copy original properties to this proxy command
$baseCommand = new ReflectionClass(Command::class);
$properties = $baseCommand->getProperties();
foreach ($properties as $property) {
$property->setAccessible(true);
$property->setValue($this, $property->getValue($command));
}
}
/**
* Get the original command and inject all its dependencies this time
*
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null
*/
protected function execute(InputInterface $input, OutputInterface $output): ?int
{
/** @var Command $command */
$command = $this->container->get($this->command);
// Execute the original command
return $command->execute($input, $output);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment