feature #23510 [Console] Add a factory command loader for standalone application with lazy-loading needs (ogizanagi)

This PR was merged into the 3.4 branch.

Discussion
----------

[Console] Add a factory command loader for standalone application with lazy-loading needs

| Q             | A
| ------------- | ---
| Branch?       | 3.4 <!-- see comment below -->
| Bug fix?      | no
| New feature?  | yes <!-- don't forget updating src/**/CHANGELOG.md files -->
| BC breaks?    | no
| Deprecations? | no <!-- don't forget updating UPGRADE-*.md files -->
| Tests pass?   | yes (failure unrelated)
| Fixed tickets | https://github.com/symfony/symfony/pull/22734#issuecomment-314706630 <!-- #-prefixed issue number(s), if any -->
| License       | MIT
| Doc PR        | todo (with https://github.com/symfony/symfony-docs/issues/8147)

So standalone applications can also benefit from the lazy loading feature without requiring a PSR-11 implementation specifically for this need.

The loader does not memoize any resolved command from factories, as it's the `Application` responsibility and the `ContainerCommandLoader` does not either (the PSR-11 does not enforce two successive calls to return the same value).

Commits
-------

9b40b4a [Console] Add a factory command loader for standalone application with lazy-loading needs
This commit is contained in:
Robin Chalas 2017-07-19 12:10:53 +02:00
commit 5556a3a1f9
4 changed files with 134 additions and 13 deletions

View File

@ -4,7 +4,8 @@ CHANGELOG
3.4.0
-----
* added `CommandLoaderInterface` and PSR-11 `ContainerCommandLoader`
* added `CommandLoaderInterface`, `FactoryCommandLoader` and PSR-11
`ContainerCommandLoader` for commands lazy-loading
3.3.0
-----

View File

@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\CommandLoader;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
* A simple command loader using factories to instantiate commands lazily.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
class FactoryCommandLoader implements CommandLoaderInterface
{
private $factories;
/**
* @param callable[] $factories Indexed by command names
*/
public function __construct(array $factories)
{
$this->factories = $factories;
}
/**
* {@inheritdoc}
*/
public function has($name)
{
return isset($this->factories[$name]);
}
/**
* {@inheritdoc}
*/
public function get($name)
{
if (!isset($this->factories[$name])) {
throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
}
$factory = $this->factories[$name];
return $factory();
}
/**
* {@inheritdoc}
*/
public function getNames()
{
return array_keys($this->factories);
}
}

View File

@ -14,7 +14,7 @@ namespace Symfony\Component\Console\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\FormatterHelper;
@ -35,7 +35,6 @@ use Symfony\Component\Console\Event\ConsoleExceptionEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\EventDispatcher\EventDispatcher;
class ApplicationTest extends TestCase
@ -129,10 +128,9 @@ class ApplicationTest extends TestCase
$commands = $application->all('foo');
$this->assertCount(1, $commands, '->all() takes a namespace as its first argument');
$application->setCommandLoader(new ContainerCommandLoader(
new ServiceLocator(array('foo-bar' => function () { return new \Foo1Command(); })),
array('foo:bar1' => 'foo-bar')
));
$application->setCommandLoader(new FactoryCommandLoader(array(
'foo:bar1' => function () { return new \Foo1Command(); },
)));
$commands = $application->all('foo');
$this->assertCount(2, $commands, '->all() takes a namespace as its first argument');
$this->assertInstanceOf(\FooCommand::class, $commands['foo:bar'], '->all() returns the registered commands');
@ -202,9 +200,9 @@ class ApplicationTest extends TestCase
$this->assertEquals($foo, $application->get('foo:bar'), '->get() returns a command by name');
$this->assertEquals($foo, $application->get('afoobar'), '->get() returns a command by alias');
$application->setCommandLoader(new ContainerCommandLoader(new ServiceLocator(array(
'foo-bar' => function () { return new \Foo1Command(); },
)), array('foo:bar1' => 'foo-bar', 'afoobar1' => 'foo-bar')));
$application->setCommandLoader(new FactoryCommandLoader(array(
'foo:bar1' => function () { return new \Foo1Command(); },
)));
$this->assertTrue($application->has('afoobar'), '->has() returns true if an instance is registered for an alias even with command loader');
$this->assertEquals($foo, $application->get('foo:bar'), '->get() returns an instance by name even with command loader');
@ -321,9 +319,9 @@ class ApplicationTest extends TestCase
public function testFindWithCommandLoader()
{
$application = new Application();
$application->setCommandLoader(new ContainerCommandLoader(new ServiceLocator(array(
'foo-bar' => $f = function () { return new \FooCommand(); },
)), array('foo:bar' => 'foo-bar')));
$application->setCommandLoader(new FactoryCommandLoader(array(
'foo:bar' => $f = function () { return new \FooCommand(); },
)));
$this->assertInstanceOf('FooCommand', $application->find('foo:bar'), '->find() returns a command if its name exists');
$this->assertInstanceOf('Symfony\Component\Console\Command\HelpCommand', $application->find('h'), '->find() returns a command if its name exists');

View File

@ -0,0 +1,60 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Tests\CommandLoader;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
class FactoryCommandLoaderTest extends TestCase
{
public function testHas()
{
$loader = new FactoryCommandLoader(array(
'foo' => function () { return new Command('foo'); },
'bar' => function () { return new Command('bar'); },
));
$this->assertTrue($loader->has('foo'));
$this->assertTrue($loader->has('bar'));
$this->assertFalse($loader->has('baz'));
}
public function testGet()
{
$loader = new FactoryCommandLoader(array(
'foo' => function () { return new Command('foo'); },
'bar' => function () { return new Command('bar'); },
));
$this->assertInstanceOf(Command::class, $loader->get('foo'));
$this->assertInstanceOf(Command::class, $loader->get('bar'));
}
/**
* @expectedException \Symfony\Component\Console\Exception\CommandNotFoundException
*/
public function testGetUnknownCommandThrows()
{
(new FactoryCommandLoader(array()))->get('unknown');
}
public function testGetCommandNames()
{
$loader = new FactoryCommandLoader(array(
'foo' => function () { return new Command('foo'); },
'bar' => function () { return new Command('bar'); },
));
$this->assertSame(array('foo', 'bar'), $loader->getNames());
}
}