Skip to content
This repository has been archived by the owner on Feb 6, 2020. It is now read-only.

Generate deps ignoring unresolved dependencies #176

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 37 additions & 4 deletions src/Tool/ConfigDumper.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace Zend\ServiceManager\Tool;

use Interop\Container\ContainerInterface;
use ReflectionClass;
use ReflectionParameter;
use Traversable;
Expand All @@ -25,13 +26,35 @@ class ConfigDumper
return %s;
EOC;

/**
* @var ContainerInterface
*/
protected $container;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private visibility, please.


/**
* @param ContainerInterface $container
*/
public function __construct(ContainerInterface $container = null)
{
$this->container = $container;
}

/**
* @param ContainerInterface $container
*/
public function setContainer(ContainerInterface $container)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this setter really necessary? If it is, then use it from __construct().

{
$this->container = $container;
}

/**
* @param array $config
* @param string $className
* @param bool $ignoreUnresolved
* @return array
* @throws InvalidArgumentException for invalid $className
*/
public function createDependencyConfig(array $config, $className)
public function createDependencyConfig(array $config, $className, $ignoreUnresolved = false)
{
$this->validateClassName($className);

Expand Down Expand Up @@ -60,22 +83,32 @@ function (ReflectionParameter $argument) {
return $this->createInvokable($config, $className);
}

$config[ConfigAbstractFactory::class][$className] = [];
$classConfig = [];

foreach ($constructorArguments as $constructorArgument) {
$argumentType = $constructorArgument->getClass();
if (is_null($argumentType)) {
if ($ignoreUnresolved) {
// don't throw an exception, just return the previous config
return $config;
}
// don't throw an exception if the class is an already defined service
if ($this->container && $this->container->has($className)) {
return $config;
}
throw new InvalidArgumentException(sprintf(
'Cannot create config for constructor argument "%s", '
. 'it has no type hint, or non-class/interface type hint',
$constructorArgument->getName()
));
}
$argumentName = $argumentType->getName();
$config = $this->createDependencyConfig($config, $argumentName);
$config[ConfigAbstractFactory::class][$className][] = $argumentName;
$config = $this->createDependencyConfig($config, $argumentName, $ignoreUnresolved);
$classConfig[] = $argumentName;
}

$config[ConfigAbstractFactory::class][$className] = $classConfig;

return $config;
}

Expand Down
28 changes: 21 additions & 7 deletions src/Tool/ConfigDumperCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class ConfigDumperCommand
<info>Arguments:</info>

<info>-h|--help|help</info> This usage message
<info>-i|--ignore-unresolved</info> Ignore classes with unresolved
direct dependencies.
<info><configFile></info> Path to a config file for which to generate configuration.
If the file does not exist, it will be created. If it does
exist, it must return an array, and the file will be
Expand Down Expand Up @@ -81,7 +83,11 @@ public function __invoke(array $args)

$dumper = new ConfigDumper();
try {
$config = $dumper->createDependencyConfig($arguments->config, $arguments->class);
$config = $dumper->createDependencyConfig(
$arguments->config,
$arguments->class,
$arguments->ignoreUnresolved
);
} catch (Exception\InvalidArgumentException $e) {
$this->helper->writeErrorMessage(sprintf(
'Unable to create config for "%s": %s',
Expand Down Expand Up @@ -117,6 +123,12 @@ private function parseArgs(array $args)
return $this->createHelpArgument();
}

$ignoreUnresolved = false;
if (in_array($arg1, ['-i', '--ignore-unresolved'], true)) {
$ignoreUnresolved = true;
$arg1 = array_shift($args);
}

if (! count($args)) {
return $this->createErrorArgument('Missing class name');
}
Expand Down Expand Up @@ -157,7 +169,7 @@ private function parseArgs(array $args)
));
}

return $this->createArguments(self::COMMAND_DUMP, $configFile, $config, $class);
return $this->createArguments(self::COMMAND_DUMP, $configFile, $config, $class, $ignoreUnresolved);
}

/**
Expand All @@ -178,15 +190,17 @@ private function help($resource = STDOUT)
* which it will be written.
* @param array $config Parsed configuration.
* @param string $class Name of class to reflect.
* @param bool $ignoreUnresolved If to ignore classes with unresolved direct dependencies.
* @return \stdClass
*/
private function createArguments($command, $configFile, $config, $class)
private function createArguments($command, $configFile, $config, $class, $ignoreUnresolved)
{
return (object) [
'command' => $command,
'configFile' => $configFile,
'config' => $config,
'class' => $class,
'command' => $command,
'configFile' => $configFile,
'config' => $config,
'class' => $class,
'ignoreUnresolved' => $ignoreUnresolved,
];
}

Expand Down
15 changes: 15 additions & 0 deletions test/TestAsset/ObjectWithObjectScalarDependency.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
/**
* @link http://github.com/zendframework/zend-servicemanager for the canonical source repository
* @copyright Copyright (c) 2016 Zend Technologies USA Inc. (http://www.zend.com)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2017, not 2016. 😄

* @license http://framework.zend.com/license/new-bsd New BSD License
*/

namespace ZendTest\ServiceManager\TestAsset;

class ObjectWithObjectScalarDependency
{
public function __construct(SimpleDependencyObject $simpleDependencyObject, ObjectWithScalarDependency $dependency)
{
}
}
44 changes: 44 additions & 0 deletions test/Tool/ConfigDumperCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Zend\ServiceManager\Tool\ConfigDumperCommand;
use Zend\Stdlib\ConsoleHelper;
use ZendTest\ServiceManager\TestAsset\InvokableObject;
use ZendTest\ServiceManager\TestAsset\ObjectWithObjectScalarDependency;
use ZendTest\ServiceManager\TestAsset\ObjectWithScalarDependency;
use ZendTest\ServiceManager\TestAsset\SimpleDependencyObject;

Expand Down Expand Up @@ -59,6 +60,14 @@ public function helpArguments()
];
}

public function ignoreUnresolvedArguments()
{
return [
'short' => ['-i'],
'long' => ['--ignore-unresolved'],
];
}

/**
* @dataProvider helpArguments
*/
Expand Down Expand Up @@ -110,6 +119,41 @@ public function testGeneratesConfigFileWhenProvidedConfigurationFileNotFound()
$this->assertEquals([], $factoryConfig[InvokableObject::class]);
}

/**
* @dataProvider ignoreUnresolvedArguments
*/
public function testGeneratesConfigFileIgnoringUnresolved($argument)
{
$command = $this->command;
vfsStream::newDirectory('config', 0775)
->at($this->configDir);
$config = vfsStream::url('project/config/test.config.php');

$this->helper->writeLine('<info>[DONE]</info> Changes written to ' . $config)->shouldBeCalled();

$this->assertEquals(0, $command([$argument, $config, ObjectWithObjectScalarDependency::class]));

$generated = include $config;
$this->assertInternalType('array', $generated);
$this->assertArrayHasKey(ConfigAbstractFactory::class, $generated);
$factoryConfig = $generated[ConfigAbstractFactory::class];
$this->assertInternalType('array', $factoryConfig);
$this->assertArrayHasKey(SimpleDependencyObject::class, $factoryConfig);
$this->assertArrayHasKey(InvokableObject::class, $factoryConfig);
$this->assertContains(InvokableObject::class, $factoryConfig[SimpleDependencyObject::class]);
$this->assertEquals([], $factoryConfig[InvokableObject::class]);

$this->assertArrayHasKey(ObjectWithObjectScalarDependency::class, $factoryConfig);
$this->assertContains(
SimpleDependencyObject::class,
$factoryConfig[ObjectWithObjectScalarDependency::class]
);
$this->assertContains(
ObjectWithScalarDependency::class,
$factoryConfig[ObjectWithObjectScalarDependency::class]
);
}

public function testEmitsErrorWhenConfigurationFileDoesNotReturnArray()
{
$command = $this->command;
Expand Down
72 changes: 72 additions & 0 deletions test/Tool/ConfigDumperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace ZendTest\ServiceManager\Tool;

use Interop\Container\ContainerInterface;
use PHPUnit_Framework_TestCase as TestCase;
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
use Zend\ServiceManager\Exception\InvalidArgumentException;
Expand All @@ -16,6 +17,7 @@
use ZendTest\ServiceManager\TestAsset\DoubleDependencyObject;
use ZendTest\ServiceManager\TestAsset\FailingFactory;
use ZendTest\ServiceManager\TestAsset\InvokableObject;
use ZendTest\ServiceManager\TestAsset\ObjectWithObjectScalarDependency;
use ZendTest\ServiceManager\TestAsset\ObjectWithScalarDependency;
use ZendTest\ServiceManager\TestAsset\SecondComplexDependencyObject;
use ZendTest\ServiceManager\TestAsset\SimpleDependencyObject;
Expand Down Expand Up @@ -101,6 +103,76 @@ public function testCreateDependencyConfigWithoutTypeHintedParameterExcepts()
);
}

public function testCreateDependencyConfigWithContainerAndNoServiceWithoutTypeHintedParameterExcepts()
{
self::expectException(InvalidArgumentException::class);
self::expectExceptionMessage(
'Cannot create config for constructor argument "aName", '
. 'it has no type hint, or non-class/interface type hint'
);
$container = $this->prophesize(ContainerInterface::class);
$container->has(ObjectWithScalarDependency::class)
->shouldBeCalled()
->willReturn(false);
$this->dumper->setContainer($container->reveal());
$config = $this->dumper->createDependencyConfig(
[ConfigAbstractFactory::class => []],
ObjectWithScalarDependency::class
);
}

public function testCreateDependencyConfigWithContainerWithoutTypeHintedParameter()
{
$container = $this->prophesize(ContainerInterface::class);
$container->has(ObjectWithScalarDependency::class)
->shouldBeCalled()
->willReturn(true);
$this->dumper->setContainer($container->reveal());
$config = $this->dumper->createDependencyConfig(
[ConfigAbstractFactory::class => []],
ObjectWithObjectScalarDependency::class
);
self::assertEquals(
[
ConfigAbstractFactory::class => [
SimpleDependencyObject::class => [
InvokableObject::class,
],
InvokableObject::class => [],
ObjectWithObjectScalarDependency::class => [
SimpleDependencyObject::class,
ObjectWithScalarDependency::class,
],
]
],
$config
);
}

public function testCreateDependencyConfigWithoutTypeHintedParameterIgnoringUnresolved()
{
$config = $this->dumper->createDependencyConfig(
[ConfigAbstractFactory::class => []],
ObjectWithObjectScalarDependency::class,
true
);
self::assertEquals(
[
ConfigAbstractFactory::class => [
SimpleDependencyObject::class => [
InvokableObject::class,
],
InvokableObject::class => [],
ObjectWithObjectScalarDependency::class => [
SimpleDependencyObject::class,
ObjectWithScalarDependency::class,
],
]
],
$config
);
}

public function testCreateDependencyConfigWorksWithExistingConfig()
{
$config = [
Expand Down