Skip to content

Commit

Permalink
Add named parameters support
Browse files Browse the repository at this point in the history
  • Loading branch information
julienfalque authored May 16, 2020
1 parent fd8f9a4 commit 1cc70b4
Show file tree
Hide file tree
Showing 18 changed files with 763 additions and 138 deletions.
62 changes: 62 additions & 0 deletions fixtures/Entity/DummyWithMethods.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

/*
* This file is part of the Alice package.
*
* (c) Nelmio <hello@nelm.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Nelmio\Alice\Entity;

class DummyWithMethods
{
private $foo1;
private $foo2;
private $bar1;
private $bar2;
private $baz1;
private $baz2;
private $baz3;

public function __construct(string $foo1, string $foo2)
{
$this->foo1 = $foo1;
$this->foo2 = $foo2;
}

public static function create(string $foo1, string $foo2)
{
return new self($foo1, $foo2);
}

public function bar(string $bar1, string $bar2)
{
$this->bar1 = $bar1;
$this->bar2 = $bar2;
}

public function methodWithVariadic(string $baz1, string $baz2, array ...$baz3)
{
$this->baz1 = $baz1;
$this->baz2 = $baz2;
$this->baz3 = $baz3;
}

public function methodWithDefaultValues(string $baz1 = 'value 1', string $baz2 = 'value 2', string $baz3 = 'value 3')
{
$this->baz1 = $baz1;
$this->baz2 = $baz2;
$this->baz3 = $baz3;
}

public function methodWithNullables(?string $bar1, ?string $bar2)
{
$this->bar1 = $bar1;
$this->bar2 = $bar2;
}
}
18 changes: 18 additions & 0 deletions fixtures/Parser/files/json/named_parameters.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"Nelmio\\Alice\\DummyWithMethods": {
"dummy_with_methods": {
"__construct": {
"$foo1": "foo1",
"$foo2": "foo2"
},
"__calls": [
{
"bar": {
"$bar1": "bar1",
"$bar2": "bar2"
}
}
]
}
}
}
31 changes: 31 additions & 0 deletions fixtures/Parser/files/php/named_parameters.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

/*
* This file is part of the Alice package.
*
* (c) Nelmio <hello@nelm.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

return [
'Nelmio\Alice\DummyWithMethods' =>[
'dummy_with_methods' =>[
'__construct' =>[
'$foo1' =>'foo1',
'$foo2' =>'foo2'
],
'__calls' =>[
[
'bar' =>[
'$bar1' =>'bar1',
'$bar2' =>'bar2'
]
]
]
]
]
];
9 changes: 9 additions & 0 deletions fixtures/Parser/files/yaml/named_parameters.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Nelmio\Alice\DummyWithMethods:
dummy_with_methods:
__construct:
$foo1: foo1
$foo2: foo2
__calls:
- bar:
$bar1: bar1
$bar2: bar2
1 change: 1 addition & 0 deletions src/Bridge/Symfony/Resources/config/generator/caller.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
class="Nelmio\Alice\Generator\Caller\SimpleCaller">
<argument type="service" id="nelmio_alice.generator.caller.registry" />
<argument type="service" id="nelmio_alice.generator.resolver.value" />
<argument type="service" id="nelmio_alice.generator.named_arguments_resolver" />
</service>

<service id="nelmio_alice.generator.caller.registry"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<!-- Chainables -->
<service id="nelmio_alice.generator.instantiator.chainable.no_caller_method_instantiator"
class="Nelmio\Alice\Generator\Instantiator\Chainable\NoCallerMethodCallInstantiator">
<argument type="service" id="nelmio_alice.generator.named_arguments_resolver" />
<tag name="nelmio_alice.generator.instantiator.chainable_instantiator" />
</service>

Expand All @@ -50,6 +51,7 @@

<service id="nelmio_alice.generator.instantiator.chainable.static_factory_instantiator"
class="Nelmio\Alice\Generator\Instantiator\Chainable\StaticFactoryInstantiator">
<argument type="service" id="nelmio_alice.generator.named_arguments_resolver" />
<tag name="nelmio_alice.generator.instantiator.chainable_instantiator" />
</service>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!--
~ This file is part of the Alice package.
~
~ (c) Nelmio <hello@nelm.io>
~
~ For the full copyright and license information, please view the LICENSE
~ file that was distributed with this source code.
-->

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>

<service id="nelmio_alice.generator.named_arguments_resolver" class="Nelmio\Alice\Generator\NamedArgumentsResolver" />

</services>

</container>
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function process(
GenerationContext $context,
MethodCallInterface $methodCall
): ResolvedFixtureSet {
$result = $object->getInstance()->{$methodCall->getMethod()}(...$methodCall->getArguments());
$result = $object->getInstance()->{$methodCall->getMethod()}(...array_values($methodCall->getArguments()));

if ($context->needsCallResult()) {
$object = $object->withInstance($result);
Expand Down
21 changes: 18 additions & 3 deletions src/Generator/Caller/SimpleCaller.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Nelmio\Alice\FixtureInterface;
use Nelmio\Alice\Generator\CallerInterface;
use Nelmio\Alice\Generator\GenerationContext;
use Nelmio\Alice\Generator\NamedArgumentsResolver;
use Nelmio\Alice\Generator\ResolvedFixtureSet;
use Nelmio\Alice\Generator\ValueResolverAwareInterface;
use Nelmio\Alice\Generator\ValueResolverInterface;
Expand All @@ -41,18 +42,28 @@ final class SimpleCaller implements CallerInterface, ValueResolverAwareInterface
*/
private $resolver;

public function __construct(CallProcessorInterface $callProcessor, ValueResolverInterface $resolver = null)
{
/**
* @var NamedArgumentsResolver|null
*/
private $namedArgumentsResolver;

// TODO: make $namedArgumentsResolver non-nullable in 4.0. It is currently nullable only for BC purposes
public function __construct(
CallProcessorInterface $callProcessor,
ValueResolverInterface $resolver = null,
NamedArgumentsResolver $namedArgumentsResolver = null
) {
$this->callProcessor = $callProcessor;
$this->resolver = $resolver;
$this->namedArgumentsResolver = $namedArgumentsResolver;
}

/**
* @inheritdoc
*/
public function withValueResolver(ValueResolverInterface $resolver): self
{
return new self($this->callProcessor, $resolver);
return new self($this->callProcessor, $resolver, $this->namedArgumentsResolver);
}

/**
Expand Down Expand Up @@ -108,6 +119,10 @@ private function processArguments(
}
}

if (null !== $this->namedArgumentsResolver) {
$arguments = $this->namedArgumentsResolver->resolveArguments($arguments, $fixture->getClassName(), $methodCall->getMethod());
}

return [$methodCall->withArguments($arguments), $fixtureSet];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,21 @@

use Nelmio\Alice\Definition\MethodCall\NoMethodCall;
use Nelmio\Alice\FixtureInterface;
use Nelmio\Alice\Generator\NamedArgumentsResolver;

final class NoCallerMethodCallInstantiator extends AbstractChainableInstantiator
{
/**
* @var NamedArgumentsResolver|null
*/
private $namedArgumentsResolver;

// TODO: make $namedArgumentsResolver non-nullable in 4.0. It is currently nullable only for BC purposes
public function __construct(NamedArgumentsResolver $namedArgumentsResolver = null)
{
$this->namedArgumentsResolver = $namedArgumentsResolver;
}

/**
* @inheritDoc
*/
Expand All @@ -35,9 +47,13 @@ protected function createInstance(FixtureInterface $fixture)
{
list($class, $arguments) = [
$fixture->getClassName(),
array_values($fixture->getSpecs()->getConstructor()->getArguments())
$fixture->getSpecs()->getConstructor()->getArguments()
];

return new $class(...$arguments);
if (null !== $this->namedArgumentsResolver) {
$arguments = $this->namedArgumentsResolver->resolveArguments($arguments, $class, '__construct');
}

return new $class(...array_values($arguments));
}
}
16 changes: 16 additions & 0 deletions src/Generator/Instantiator/Chainable/StaticFactoryInstantiator.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,22 @@
use Nelmio\Alice\Definition\MethodCall\NoMethodCall;
use Nelmio\Alice\Definition\ServiceReference\StaticReference;
use Nelmio\Alice\FixtureInterface;
use Nelmio\Alice\Generator\NamedArgumentsResolver;
use Nelmio\Alice\Throwable\Exception\Generator\Instantiator\InstantiationExceptionFactory;

final class StaticFactoryInstantiator extends AbstractChainableInstantiator
{
/**
* @var NamedArgumentsResolver|null
*/
private $namedArgumentsResolver;

// TODO: make $namedArgumentsResolver non-nullable in 4.0. It is currently nullable only for BC purposes
public function __construct(NamedArgumentsResolver $namedArgumentsResolver = null)
{
$this->namedArgumentsResolver = $namedArgumentsResolver;
}

/**
* @inheritDoc
*/
Expand Down Expand Up @@ -47,6 +59,10 @@ protected function createInstance(FixtureInterface $fixture)
$arguments = [];
}

if (null !== $this->namedArgumentsResolver) {
$arguments = $this->namedArgumentsResolver->resolveArguments($arguments, $factory, $method);
}

$instance = $factory::$method(...array_values($arguments));
if (false === $instance instanceof $class) {
throw InstantiationExceptionFactory::createForInvalidInstanceType($fixture, $instance);
Expand Down
85 changes: 85 additions & 0 deletions src/Generator/NamedArgumentsResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

/*
* This file is part of the Alice package.
*
* (c) Nelmio <hello@nelm.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Nelmio\Alice\Generator;

class NamedArgumentsResolver
{
public function resolveArguments(array $arguments, string $className, string $methodName): array
{
try {
$method = new \ReflectionMethod($className, $methodName);
} catch (\ReflectionException $exception) {
return $arguments;
}

$sortedArguments = [];
$buffer = [];

foreach ($method->getParameters() as $parameter) {
$name = $parameter->getName();

if ($parameter->isVariadic() && [] !== $arguments) {
$sortedArguments = array_merge($sortedArguments, $buffer, array_values($arguments));
$arguments = [];
$buffer = [];

break;
}

if (array_key_exists($name, $arguments)) {
$sortedArguments = array_merge($sortedArguments, $buffer, [$name => $arguments[$name]]);
unset($arguments[$name]);
$buffer = [];

continue;
}

foreach ($arguments as $key => $value) {
if (is_int($key)) {
$sortedArguments = array_merge($sortedArguments, $buffer, [$arguments[$key]]);
unset($arguments[$key]);
$buffer = [];

continue 2;
}
}

if (!$parameter->isDefaultValueAvailable()) {
throw new \RuntimeException(sprintf(
'Argument $%s of %s::%s() is not passed a value and does not define a default one.',
$name,
$className,
$methodName
));
}

$buffer[] = $parameter->getDefaultValue();
}

$unknownNamedParameters = array_filter(array_keys($arguments), static function ($key) {
return is_string($key);
});

if ([] !== $unknownNamedParameters) {
throw new \RuntimeException(sprintf(
'Unknown arguments for %s::%s(): $%s.',
$className,
$methodName,
implode(', $', $unknownNamedParameters)
));
}

return $sortedArguments;
}
}
Loading

0 comments on commit 1cc70b4

Please sign in to comment.