Skip to content

Commit a9b2a20

Browse files
committed
[Console] Added Command resolver
1 parent e4ab7b7 commit a9b2a20

File tree

9 files changed

+306
-47
lines changed

9 files changed

+306
-47
lines changed

src/Symfony/Component/Console/Application.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Component\Console;
1313

14+
use Symfony\Component\Console\Command\CommandConfiguration;
15+
use Symfony\Component\Console\Command\Resolver\CommandResolverInterface;
1416
use Symfony\Component\Console\Descriptor\TextDescriptor;
1517
use Symfony\Component\Console\Descriptor\XmlDescriptor;
1618
use Symfony\Component\Console\Helper\DebugFormatterHelper;
@@ -60,6 +62,7 @@
6062
class Application
6163
{
6264
private $commands = array();
65+
private $commandResolver;
6366
private $wantHelps = false;
6467
private $runningCommand;
6568
private $name;
@@ -93,11 +96,22 @@ public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
9396
}
9497
}
9598

99+
/**
100+
* @param EventDispatcherInterface $dispatcher
101+
*/
96102
public function setDispatcher(EventDispatcherInterface $dispatcher)
97103
{
98104
$this->dispatcher = $dispatcher;
99105
}
100106

107+
/**
108+
* @param CommandResolverInterface $commandResolver
109+
*/
110+
public function setCommandResolver(CommandResolverInterface $commandResolver)
111+
{
112+
$this->commandResolver = $commandResolver;
113+
}
114+
101115
/**
102116
* Runs the current application.
103117
*
@@ -404,6 +418,21 @@ public function add(Command $command)
404418
return $command;
405419
}
406420

421+
/**
422+
* Adds command configuration.
423+
*
424+
* @param CommandConfiguration $configuration
425+
* @return Command
426+
*/
427+
public function addCommandConfiguration(CommandConfiguration $configuration)
428+
{
429+
if (null === $this->commandResolver) {
430+
throw new \RuntimeException('You have to specify command resolver in order to register separate command configuration');
431+
}
432+
433+
return $this->add(Command::registerLazyLoaded($configuration, $this->commandResolver));
434+
}
435+
407436
/**
408437
* Returns a registered command by name or alias.
409438
*

src/Symfony/Component/Console/Command/Command.php

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\Console\Command;
1313

14+
use Symfony\Component\Console\Command\Resolver\CommandResolverInterface;
1415
use Symfony\Component\Console\Descriptor\TextDescriptor;
1516
use Symfony\Component\Console\Descriptor\XmlDescriptor;
1617
use Symfony\Component\Console\Input\InputDefinition;
@@ -40,30 +41,34 @@ class Command
4041
private $helperSet;
4142

4243
/**
43-
* Registers command in a lazy load manner.
44-
* Factory requires $name as an only argument.
44+
* Registers command in a lazy load manner and returns proxy command.
4545
*
4646
* @param CommandConfiguration $configuration
47-
* @param callable $factory
47+
* @param CommandResolverInterface $resolver
4848
* @return Command
4949
*/
50-
final public static function registerLazyLoaded(CommandConfiguration $configuration, $factory)
50+
final public static function registerLazyLoaded(CommandConfiguration $configuration, CommandResolverInterface $resolver)
5151
{
52-
if (!is_callable($factory)) {
53-
throw new \InvalidArgumentException(sprintf('Factory of the command "%s" must be callable', $configuration->getName()));
54-
}
52+
$proxyCommand = new self(null, $configuration);
53+
$proxyCommand->setCode(function ($input, $output) use ($configuration, $resolver, $proxyCommand) {
54+
$command = $resolver->resolve($configuration->getName());
55+
56+
if ($command instanceof Command) {
57+
$command->application = $proxyCommand->application;
58+
$command->configuration = $proxyCommand->configuration;
59+
$command->helperSet = $proxyCommand->helperSet;
5560

56-
$lazyLoaded = new self(null, $configuration);
57-
$lazyLoaded->setCode(function ($input, $output) use ($configuration, $factory, $lazyLoaded) {
58-
/** @var Command $command */
59-
$command = $factory($configuration->getName());
60-
$command->application = $lazyLoaded->application;
61-
$command->configuration = $lazyLoaded->configuration;
61+
return $command->doRun($input, $output);
62+
}
6263

63-
return $command->doRun($input, $output);
64+
if (is_callable($command)) {
65+
return (int) $command($input, $output);
66+
}
67+
68+
throw new \RuntimeException(sprintf('Command or callable was expected, %s given', gettype($command)));
6469
});
6570

66-
return $lazyLoaded;
71+
return $proxyCommand;
6772
}
6873

6974
/**
@@ -85,10 +90,6 @@ public function __construct($name = null, CommandConfiguration $configuration =
8590
$this->configuration = $configuration ?: new CommandConfiguration($name);
8691

8792
$this->configure();
88-
89-
if (!$this->configuration->getName()) {
90-
throw new \LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this)));
91-
}
9293
}
9394

9495
/**
@@ -463,6 +464,10 @@ public function setProcessTitle($title)
463464
*/
464465
public function getName()
465466
{
467+
if (!$this->configuration->getName()) {
468+
throw new \LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this)));
469+
}
470+
466471
return $this->configuration->getName();
467472
}
468473

src/Symfony/Component/Console/Command/CommandConfiguration.php

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,23 @@ final class CommandConfiguration
3232
private $synopsis;
3333

3434
/**
35-
* @param $name
35+
* @param string $name
36+
* @return CommandConfiguration
37+
*/
38+
public static function create($name)
39+
{
40+
return new self($name);
41+
}
42+
43+
/**
44+
* @param string $name
3645
* @param InputDefinition $definition
3746
* @param string[] $aliases
38-
* @param $enabled
39-
* @param $processTitle
40-
* @param $help
41-
* @param $description
42-
* @param $synopsis
47+
* @param bool $enabled
48+
* @param string $processTitle
49+
* @param string $help
50+
* @param string $description
51+
* @param string $synopsis
4352
*/
4453
public function __construct(
4554
$name = null,
@@ -50,8 +59,7 @@ public function __construct(
5059
$help = null,
5160
$description = null,
5261
$synopsis = null
53-
)
54-
{
62+
) {
5563
if ($name !== null) {
5664
$this->validateName($name);
5765
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Console\Command\Resolver;
13+
14+
/**
15+
* @author Nikita Konstantinov
16+
*
17+
* @api
18+
*/
19+
final class ClosureCommandResolver implements CommandResolverInterface
20+
{
21+
/**
22+
* @var \Closure
23+
*/
24+
private $closure;
25+
26+
/**
27+
* @param \Closure $closure
28+
*/
29+
public function __construct(\Closure $closure)
30+
{
31+
$this->closure = $closure;
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function resolve($commandName)
38+
{
39+
$command = call_user_func($this->closure, $commandName);
40+
41+
if (null === $command) {
42+
throw new CommandResolutionException(sprintf('Command "%s" could not be resolved', $commandName));
43+
}
44+
45+
return $command;
46+
}
47+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Console\Command\Resolver;
13+
14+
/**
15+
* @author Nikita Konstantinov
16+
*/
17+
class CommandResolutionException extends \Exception
18+
{
19+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Console\Command\Resolver;
13+
14+
use Symfony\Component\Console\Command\Command;
15+
16+
/**
17+
* Command resolver allows to get instance of a Command (or any callable) by name.
18+
*
19+
* @author Nikita Konstantinov
20+
*
21+
* @api
22+
*/
23+
interface CommandResolverInterface
24+
{
25+
/**
26+
* @param string $commandName
27+
* @return Command|callable
28+
* @throws CommandResolutionException
29+
*/
30+
public function resolve($commandName);
31+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Console\Tests\Command;
13+
14+
use Symfony\Component\Console\Command\CommandConfiguration;
15+
use Symfony\Component\Console\Input\InputDefinition;
16+
17+
class CommandConfigurationTest extends \PHPUnit_Framework_TestCase
18+
{
19+
public function testImmutablity()
20+
{
21+
$configuration = new CommandConfiguration('foo:bar');
22+
23+
$newConfiguration = $configuration->withName('foo:baz');
24+
$this->assertNotSame($configuration, $newConfiguration, '->withName() does not mutate original configuration');
25+
26+
$configuration = $newConfiguration;
27+
$newConfiguration = $configuration->withAliases(['bar:foo']);
28+
$this->assertNotSame($configuration, $newConfiguration, '->withAliases() does not mutate original configuration');
29+
30+
$configuration = $newConfiguration;
31+
$newConfiguration = $configuration->withDefinition(new InputDefinition());
32+
$this->assertNotSame($configuration, $newConfiguration, '->withDefinition() does not mutate original configuration');
33+
34+
$configuration = $newConfiguration;
35+
$newConfiguration = $configuration->withProcessTitle('title');
36+
$this->assertNotSame($configuration, $newConfiguration, '->withProcessTitle() does not mutate original configuration');
37+
38+
$configuration = $newConfiguration;
39+
$newConfiguration = $configuration->withHelp('help');
40+
$this->assertNotSame($configuration, $newConfiguration, '->withHelp() does not mutate original configuration');
41+
42+
$configuration = $newConfiguration;
43+
$newConfiguration = $configuration->withDescription('description');
44+
$this->assertNotSame($configuration, $newConfiguration, '->withDescription() does not mutate original configuration');
45+
}
46+
47+
public function testFactories()
48+
{
49+
$configuration = new CommandConfiguration('foo:bar');
50+
51+
$this->assertSame('foo:bar', $configuration->getName(), '->getName() returns correct command name');
52+
$this->assertSame('bar:foo', $configuration->withName('bar:foo')->getName(), '->withName() returns configuration with new name');
53+
54+
$this->assertSame([], $configuration->getAliases());
55+
$this->assertSame(['bar:baz'], $configuration->withAliases(['bar:baz'])->getAliases(), '->withAliases() returns configuration with new aliases');
56+
57+
$this->assertNull($configuration->getProcessTitle());
58+
$this->assertSame('title', $configuration->withProcessTitle('title')->getProcessTitle(), '->getProcessTitle() returns configuration with new title');
59+
60+
$this->assertNull($configuration->getHelp());
61+
$this->assertSame('help', $configuration->withHelp('help')->getHelp(), '->withHelp() returns configuration with new help');
62+
63+
$this->assertNull($configuration->getDescription());
64+
$this->assertSame('description', $configuration->withDescription('description')->getDescription(), '->withDescription() returns configuration with new description');
65+
}
66+
67+
/**
68+
* @dataProvider provideInvalidCommandNames
69+
*/
70+
public function testInvalidCommandNames($name)
71+
{
72+
$this->setExpectedException('InvalidArgumentException', sprintf('Command name "%s" is invalid.', $name));
73+
74+
new CommandConfiguration($name);
75+
}
76+
77+
public function provideInvalidCommandNames()
78+
{
79+
return array(
80+
array(''),
81+
array('foo:'),
82+
);
83+
}
84+
}

0 commit comments

Comments
 (0)