Skip to content
This repository has been archived by the owner on Dec 7, 2019. It is now read-only.

Commit

Permalink
Merge branch 'feature/28' into develop
Browse files Browse the repository at this point in the history
Close #28
Fixes #15
  • Loading branch information
weierophinney committed Jul 11, 2016
2 parents d999b4d + 3baa73a commit 812b430
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 11 deletions.
37 changes: 37 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,43 @@

All notable changes to this project will be documented in this file, in reverse chronological order by release.

## 1.3.0 - TBD

### Added

- [#28](https://github.com/zfcampus/zf-console/pull/28) adds the ability to
provide an `Interop\Container\ContainerInterface` instance to
`ZF\Console\Dispatcher` during instantiation; when present, `dispatch()` will
attempt to look up command callables via the container. This allows simpler
configuration:

```php
$dispatch = new Dispatcher($container);

$routes = [
[
'name' => 'hello',
'handler' => HelloCommand::class,
],
];

$app = new Application('App', 1.0, $routes, null, $dispatcher);
```

(vs wrapping the handler in a closure.)

### Deprecated

- Nothing.

### Removed

- Nothing.

### Fixed

- Nothing.

## 1.2.1 - TBD

### Added
Expand Down
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,50 @@ The `Route` instance contains several methods of interest:
- `getName()` will return the name of the route (which may be useful if you use the same callable
for multiple routes).

Pulling commands from a container
---------------------------------

> - Since 1.3.0
Instead of specifying a callable or a class name for a command handler, you may
store your handlers within a dependency injection container compatible with
[container-interop](https://github.com/container-interop/container-interop);
when you do so, you can specify the *service name* of the handler instead.

To do this, you will need to create a `Dispatcher` instance, passing it the
container you are using at instantiation:


```php
$serviceManager = new ServiceManager(/* ... */);

// use `zend-servicemanager` as container
$dispatcher = new Dispatcher($serviceManager);
```

From there, you can configure routes using the service name (which is often a
class name):

```
$routes = [
[
'name' => 'hello',
'handler' => HelloCommand::class,
]
];
```

Finally, do not forget to pass your dispatcher to your application when you
initialize it:

```php
$application = new Application('App', 1.0, $routes, null, $dispatcher);
```

In the above examples, when the `hello` route is matched, the `Dispatcher` will
attempt to pull the `HelloCommand` service from the container prior to
dispatching it.

Exception Handling
------------------

Expand Down
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@
"phpunit/phpunit": "~4.7",
"squizlabs/php_codesniffer": "^2.3.1",
"zendframework/zend-filter": "~2.3",
"zendframework/zend-validator": "~2.3"
"zendframework/zend-validator": "~2.3",
"container-interop/container-interop": "^1.1"
},
"suggest": {
"zendframework/zend-filter": "~2.3; Useful for filtering/normalizing argument values",
"zendframework/zend-validator": "~2.3; Useful for providing more thorough argument validation logic"
"zendframework/zend-validator": "~2.3; Useful for providing more thorough argument validation logic",
"container-interop/container-interop": "^1.1; For ability to pull dispatched commands from container"
},
"autoload": {
"psr-4": {
Expand Down
60 changes: 51 additions & 9 deletions src/Dispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace ZF\Console;

use Interop\Container\ContainerInterface;
use InvalidArgumentException;
use RuntimeException;
use Zend\Console\Adapter\AdapterInterface as ConsoleAdapter;
Expand All @@ -15,16 +16,55 @@ class Dispatcher
{
protected $commandMap = [];

/**
* Container from which to pull command services when dispatching.
*
* @var null|ContainerInterface
*/
protected $container;

/**
* @param null|ContainerInterface $container Container from which to pull
* command services when dispatching.
*/
public function __construct(ContainerInterface $container = null)
{
$this->container = $container;
}

/**
* Map a command name to its handler.
*
* @param string $command
* @param callable|string $command A callable command, or a string service
* or class name to use as a handler.
*/
public function map($command, $callable)
{
if (! is_string($command) || empty($command)) {
throw new InvalidArgumentException('Invalid command specified; must be a non-empty string');
}

if (! is_callable($callable)) {
if (! is_string($callable) || ! class_exists($callable)) {
throw new InvalidArgumentException('Invalid command callback specified; must be callable');
}
if (is_callable($callable)) {
$this->commandMap[$command] = $callable;
return $this;
}

if (! is_string($callable)) {
throw new InvalidArgumentException(
'Invalid command callback specified; must be callable or a string class or service name'
);
}

if (class_exists($callable)) {
$this->commandMap[$command] = $callable;
return $this;
}

if (! $this->container || ! $this->container->has($callable)) {
throw new InvalidArgumentException(
'Invalid command callback specified; must be callable or a string class or service name'
);
}

$this->commandMap[$command] = $callable;
Expand Down Expand Up @@ -56,12 +96,14 @@ public function dispatch(Route $route, ConsoleAdapter $console)
$callable = $this->commandMap[$name];

if (! is_callable($callable) && is_string($callable)) {
$callable = new $callable();
$callable = ($this->container && $this->container->has($callable))
? $this->container->get($callable)
: new $callable();

if (! is_callable($callable)) {
throw new RuntimeException(sprintf(
'Invalid command class specified for "%s"; class must be invokable',
$name
));
throw new RuntimeException(
sprintf('Invalid command class specified for "%s"; class must be invokable', $name)
);
}
$this->commandMap[$name] = $callable;
}
Expand Down
23 changes: 23 additions & 0 deletions test/DispatcherTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@

namespace ZFTest\Console;

use Interop\Container\ContainerInterface;
use PHPUnit_Framework_TestCase as TestCase;
use Zend\Console\Adapter\AdapterInterface;
use ZF\Console\Dispatcher;
use ZF\Console\Route;
use ZFTest\Console\TestAsset\FooCommand;

class DispatcherTest extends TestCase
{
/**
* @var Dispatcher
*/
private $dispatcher;

public function setUp()
{
$this->route = $this->getMockBuilder('ZF\Console\Route')
Expand Down Expand Up @@ -91,4 +98,20 @@ public function testDispatchReturnsCallableReturnIntegerOnSuccess()
->will($this->returnValue('test'));
$this->assertEquals(2, $this->dispatcher->dispatch($this->route, $this->console));
}

public function testDispatchCanPullCommandFromContainer()
{
$container = $this->prophesize(ContainerInterface::class);
$container->has(FooCommand::class)->willReturn(true);
$container->get(FooCommand::class)->willReturn(new FooCommand());

$route = $this->prophesize(Route::class);
$route->getName()->willReturn('foobar');

$console = $this->prophesize(AdapterInterface::class);

$dispatcher = new Dispatcher($container->reveal());
$dispatcher->map('foobar', FooCommand::class);
$dispatcher->dispatch($route->reveal(), $console->reveal());
}
}
14 changes: 14 additions & 0 deletions test/TestAsset/FooCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php
/**
* @license http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause
* @copyright Copyright (c) 2016 Zend Technologies USA Inc. (http://www.zend.com)
*/

namespace ZFTest\Console\TestAsset;

class FooCommand
{
public function __invoke()
{
}
}

0 comments on commit 812b430

Please sign in to comment.