Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Added an ArgumentResolver with clean extension point #18308

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 3, 2016
Merged
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
5 changes: 5 additions & 0 deletions UPGRADE-3.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ HttpKernel
deprecated and will be removed in Symfony 4.0. The inline fragment
renderer should be used with object attributes.

* The `ControllerResolver::getArguments()` method has been deprecated and will
be removed in 4.0. If you have your own `ControllerResolverInterface`
implementation, you should inject either an `ArgumentResolverInterface`
instance or the new `ArgumentResolver` in the `HttpKernel`.
Copy link
Member

Choose a reason for hiding this comment

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

something similar (but then removed instead of deprecated) should be added to UPGRADE-4.0.md

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wouldn't it be easier to document this in the PR where it's actually removed?


Serializer
----------

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

/**
* Gathers and configures the argument value resolvers.
*
* @author Iltar van der Berg <[email protected]>
*/
class ControllerArgumentValueResolverPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('argument_resolver')) {
return;
}

$definition = $container->getDefinition('argument_resolver');
Copy link
Member

Choose a reason for hiding this comment

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

first, check if the definition exists and return early otherwise.

$argumentResolvers = $this->findAndSortTaggedServices('controller_argument.value_resolver', $container);
$definition->replaceArgument(1, $argumentResolvers);
}

/**
* Finds all services with the given tag name and order them by their priority.
*
* @param string $tagName
* @param ContainerBuilder $container
*
* @return array
*/
private function findAndSortTaggedServices($tagName, ContainerBuilder $container)
{
$services = $container->findTaggedServiceIds($tagName);

$sortedServices = array();
foreach ($services as $serviceId => $tags) {
foreach ($tags as $attributes) {
$priority = isset($attributes['priority']) ? $attributes['priority'] : 0;
$sortedServices[$priority][] = new Reference($serviceId);
}
}

if (empty($sortedServices)) {
return array();
}

krsort($sortedServices);

// Flatten the array
return call_user_func_array('array_merge', $sortedServices);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

missing empty line at the end of this file

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Doesn't github show this if the case? Anyhow, line 66 is an empty line here for me, adding one more would result in 2 empty lines in the end

Copy link
Member

Choose a reason for hiding this comment

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

@HeahDude we should not have an empty line at the end. We should have a final LF char. PhpStorm displays an empty line after this LF, but there is no actual line 66. Github handles this properly (as git does) and displays an extra info at the end of line when it does not end with an EOL character

Copy link
Contributor

Choose a reason for hiding this comment

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

@stof Noted. Thanks for that precision :)

Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,6 @@ public function load(array $configs, ContainerBuilder $container)

$loader->load('debug.xml');

$definition = $container->findDefinition('http_kernel');
$definition->replaceArgument(1, new Reference('debug.controller_resolver'));

// replace the regular event_dispatcher service with the debug one
$definition = $container->findDefinition('event_dispatcher');
$definition->setPublic(false);
Expand All @@ -173,6 +170,9 @@ public function load(array $configs, ContainerBuilder $container)
'Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener',
'Symfony\\Component\\HttpKernel\\EventListener\\RouterListener',
'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver',
'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver',
'Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadata',
'Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadataFactory',
'Symfony\\Component\\HttpKernel\\Event\\KernelEvent',
'Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent',
'Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent',
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConstraintValidatorsPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddValidatorInitializersPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ControllerArgumentValueResolverPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FormPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\PropertyInfoPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingPass;
Expand Down Expand Up @@ -87,6 +88,7 @@ public function build(ContainerBuilder $container)
$container->addCompilerPass(new FragmentRendererPass(), PassConfig::TYPE_AFTER_REMOVING);
$container->addCompilerPass(new SerializerPass());
$container->addCompilerPass(new PropertyInfoPass());
$container->addCompilerPass(new ControllerArgumentValueResolverPass());

if ($container->getParameter('kernel.debug')) {
$container->addCompilerPass(new UnusedTagsPass(), PassConfig::TYPE_AFTER_REMOVING);
Expand Down
10 changes: 8 additions & 2 deletions src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@
<argument type="service" id="logger" on-invalid="null" />
</service>

<service id="debug.controller_resolver" class="Symfony\Component\HttpKernel\Controller\TraceableControllerResolver">
<argument type="service" id="controller_resolver" />
<service id="debug.controller_resolver" decorates="controller_resolver" class="Symfony\Component\HttpKernel\Controller\TraceableControllerResolver">
<argument type="service" id="debug.controller_resolver.inner" />
<argument type="service" id="debug.stopwatch" />
<argument type="service" id="argument_resolver" />
</service>

<service id="debug.argument_resolver" decorates="argument_resolver" class="Symfony\Component\HttpKernel\Controller\TraceableArgumentResolver">
<argument type="service" id="debug.argument_resolver.inner" />
<argument type="service" id="debug.stopwatch" />
</service>
</services>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<argument type="service" id="event_dispatcher" />
<argument type="service" id="controller_resolver" />
<argument type="service" id="request_stack" />
<argument type="service" id="argument_resolver" />
</service>

<service id="request_stack" class="Symfony\Component\HttpFoundation\RequestStack" />
Expand Down
23 changes: 23 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,29 @@
<argument type="service" id="logger" on-invalid="ignore" />
</service>

<service id="argument_metadata_factory" class="Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory" public="false" />

<service id="argument_resolver" class="Symfony\Component\HttpKernel\Controller\ArgumentResolver" public="false">
<argument type="service" id="argument_metadata_factory" />
<argument type="collection" />
</service>

<service id="argument_resolver.request_attribute" class="Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver" public="false">
<tag name="controller_argument.value_resolver" priority="100" />
</service>

<service id="argument_resolver.request" class="Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver" public="false">
<tag name="controller_argument.value_resolver" priority="50" />
</service>

<service id="argument_resolver.default" class="Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver" public="false">
<tag name="controller_argument.value_resolver" priority="-100" />
</service>

<service id="argument_resolver.variadic" class="Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver" public="false">
<tag name="controller_argument.value_resolver" priority="-150" />
</service>
Copy link
Member

Choose a reason for hiding this comment

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

What about merging the last two as being the "fallback" behavior based on reflection? That could even be "hardcoded" (not sure).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You mean directly in the ArgumentResolver or in 1 specific resolver?


<service id="response_listener" class="Symfony\Component\HttpKernel\EventListener\ResponseListener">
<tag name="kernel.event_subscriber" />
<argument>%kernel.charset%</argument>
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Bundle/FrameworkBundle/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"symfony/config": "~2.8|~3.0",
"symfony/event-dispatcher": "~2.8|~3.0",
"symfony/http-foundation": "~3.1",
"symfony/http-kernel": "~2.8|~3.0",
"symfony/http-kernel": "~3.1",
"symfony/polyfill-mbstring": "~1.0",
"symfony/filesystem": "~2.8|~3.0",
"symfony/finder": "~2.8|~3.0",
Expand Down
6 changes: 6 additions & 0 deletions src/Symfony/Component/HttpKernel/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ CHANGELOG
3.1.0
-----
* deprecated passing objects as URI attributes to the ESI and SSI renderers
* added `Symfony\Component\HttpKernel\Controller\LegacyArgumentResolver`
* deprecated `ControllerResolver::getArguments()`
* made `ControllerResolver` extend the `LegacyArgumentResolver` for BC
* added `Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface`
* added `Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface` as argument to `HttpKernel`
* added `Symfony\Component\HttpKernel\Controller\ArgumentResolver`

3.0.0
-----
Expand Down
87 changes: 87 additions & 0 deletions src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpKernel\Controller;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactoryInterface;

/**
* Responsible for resolving the arguments passed to an action.
*
* @author Iltar van der Berg <[email protected]>
*/
final class ArgumentResolver implements ArgumentResolverInterface
{
private $argumentMetadataFactory;

/**
* @var ArgumentValueResolverInterface[]
*/
private $argumentValueResolvers;

public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, array $argumentValueResolvers = array())
{
$this->argumentMetadataFactory = $argumentMetadataFactory ?: new ArgumentMetadataFactory();
$this->argumentValueResolvers = $argumentValueResolvers ?: array(
new RequestAttributeValueResolver(),
new RequestValueResolver(),
new DefaultValueResolver(),
new VariadicValueResolver(),
);
Copy link
Member

Choose a reason for hiding this comment

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

Elsewhere, we would also have an add() method to be able to add more value resolver. But this logic here forbids to have one. What about (and I know it's going to be controversial) removing the array typehint, make the default value to null and only automatically register the default revolsers when the value is null?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The current method of adding them is via a compiler pass, you tag it, gets collected and then injected into the constructor. I'm personally in favour of this method to avoid 4+ method calls each webrequest

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now that I'm not in the middle of nowhere with an actual keyboard...

This compiler pass does all the logic: https://github.com/symfony/symfony/pull/18308/files#diff-6df4a64da7596af5827901ecbc5d2e78R18

  • If not given, same behavior as the LegacyArgumentResolver
  • Enhanced experience without the FrameworkBundle wiring everything via services
  • Easily extendable because you only need to tag your service with controller_argument.value_resolver

In your service.yml you would only have to do this:

services:
    app.argument_resolver.user:
        class: App\ArgumentResolver\UserValueResolver
        arguments:
            - "@security.token_storage"
        tags:
            - { name: controller_argument.value_resolver, priority: 150 }

The compiler pass will simply generate an array and replace the second argument to avoid the aforementioned method calls.

Copy link
Member

Choose a reason for hiding this comment

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

I totally understand how that works for Symfony full-stack, I was more thinking about the integration with Silex or Drupal where the FrameworkBundle is not available. Anyway, I'm going to merge like this and we still have 2 months to see how to deal with that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What could be done, is move the compiler pass to the component, but I'm not sure if silex uses this

Copy link
Contributor

Choose a reason for hiding this comment

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

AFAIK Silex does not use Config and DependencyInjection components by default.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Once this is merged, let's see if we can find a nice solution for those cases because ideally I'd like to support that before 3.1 is released.

Copy link
Member

Choose a reason for hiding this comment

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

If we were to allow to add value resolvers after the argument resolver has been created, we should imo move the priority handling to this class instead of doing that in the compiler pass (i.e. adding a method like addArgumentValueResolver(ArgumentValueResolver $resolver, $priority)).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now we have the advantage of little overhead because everything is compiled, I'm personally for this approach because it feels safer

Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should allow to add value resolver after the argument resolver has been created. 👍 for the current way

}

/**
* {@inheritdoc}
*/
public function getArguments(Request $request, $controller)
Copy link
Contributor

Choose a reason for hiding this comment

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

missing doc block ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Was removed on purpose, I can always add it instead of the @var. As long as the array'd type can be deferred I'm happy.

Copy link
Contributor

Choose a reason for hiding this comment

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

You should use {@inheritdoc} to link the ArgumentResolverInterface with auto generated doc api.

Copy link
Contributor

Choose a reason for hiding this comment

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

same here, needed.

{
$arguments = array();

foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller) as $metadata) {
foreach ($this->argumentValueResolvers as $resolver) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Edited

foreach ($this->argumentValueResolvers as $resolver) {
    if (!$resolver->supports($request, $metadata)) {
        continue;
    }

    $resolved = $resolver->getValue($request, $metadata);
    if ($resolved) {
        // variadic is a special case, always being the last and being an array
        if ($metadata->isVariadic() && is_array($resolved)) {
            return array_merge($arguments, $resolved);
        }

        return $resolved;
    }
}

$repr = $controller;

if (is_array($controller)) {
    $repr = sprintf('%s::%s()', get_class($controller[0]), $controller[1]);
} elseif (is_object($controller)) {
    $repr = get_class($controller);
}

throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument (because there is no default value or because there is a non optional argument after this one).', $repr, $metadata->getArgumentName()));

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That would cause an issue with (Request $request, $foo) where foo wouldn't exist. Because at that point it would return true while the argument couldn't be found.

I've also thought about the guard clause you've posted but this case will actually cause 2 arguments to be resolved if they overlap. That means if (Request $request) is given and 2 resolvers support this:

  • Request as class
  • 'request' in the attribute bag for example

this would mean you'd call the method with 2 parameters instead of 1.

if (!$resolver->supports($request, $metadata)) {
continue;
}

$resolved = $resolver->resolve($request, $metadata);

if (!$resolved instanceof \Generator) {
Copy link
Member

Choose a reason for hiding this comment

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

Technically, we should be more flexible here, by allowing any Traversable (as we only need to loop over it using foreach. There is no reason to force consumers to use a generator when implementing the iterator (even though the core does it this way for simplicity)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This makes it more complex. If it returns an array, I should iterate over it. If it's not an array, I should make it an array and iterate over it. This last case was discussed to rather throw an exception. If you check the comments above, it was decide to use yield as an elegant alternative. This way someone can yield an array and it won't give a buggy result. If I were to return an array while that single argument would have to be an array, it would start adding each array item as one, effectively breaking fooAction(array $foo)

This is one of the cases where I think it's easier to support only 1 method which is compatible with both arrays, non-arrays and variadic arguments.

Copy link
Member

Choose a reason for hiding this comment

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

@iltar Why ? Document that the return value is a Traversable being a list of argument, and always iterate over it. The code is the same than today, except for the instanceof safeguard here. People may want to implement iterators in other ways than through a generator.
When you only need iterations, the fact that a Generator is used should be an implementation detail. You are overly restrictive here IMO.

The only case where a consumer should care that it deals with a Generator is when using Generator-only APIs (i.e. Generator::send).

I'm not asking you to be able to return a single value from the resolver here (which is what you describe in your comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As I've discussed this before; @fabpot do you also agree that returning Traversable|array should be possible?

In my opinion it's a bit dangerous as this should only be possible for variadics.

Copy link
Member

Choose a reason for hiding this comment

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

@iltar returning an array in this case would then work the same than when returning an iterator. Iterating over it. I don't see much difference between the case of the array and the Generator here, for the consumer side (and the resolver can be implement in the way the implementer prefers)

What we should have though is a validation in the ArgumentResolver that any non-variadic argument leads to an iterator containing exactly 1 value.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@stof as the current solution is more strict, we can always decide to change it in a later version as it will remain backwards compatible. I don't think the current situation will cause any issues and if they do, we can patch it

Copy link
Member

Choose a reason for hiding this comment

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

@iltar If we want to make that change, it must be done before the 3.1 release. Otherwise, code relying on Generator instances being returned might break when a Traversable is returned instead.

Copy link
Member

Choose a reason for hiding this comment

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

@xabbuh but we're talking about the return value that is only internally used. No other code should ever rely on what the argument value resolver returns

Copy link
Member

Choose a reason for hiding this comment

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

@wouterj If we don't expect other code to make use of the return values, we should remove the ArgumentResolverInterface. Having such an interface calls for custom implementations imo which will then probably rely on the return type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@xabbuh in theory it shouldn't return anything else and a clear exception
is thrown when not the case. The decision for yield was that it's a simple
syntax and uniform for every case. Ideally I'd return the resolve value,
but that means that the variadic version breaks. If it's allowed to also
return iterators and such, it would make it too easy to break things. If
you would return 2 array items, it will become the wrong sequence of
arguments and break your method.

In my opinion having just yield is a good idea because it limits the usage
and reduces the chance of breaking. If you have an idea on how to approve
it, I'm all ears though

On Sun, Apr 10, 2016 at 1:41 PM Christian Flothmann <
[email protected]> wrote:

In src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php
#18308 (comment):

  •    $this->argumentValueResolvers = $argumentValueResolvers;
    
  • }
  • public function getArguments(Request $request, $controller)
  • {
  •    $arguments = array();
    
  •    foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller) as $metadata) {
    
  •        foreach ($this->argumentValueResolvers as $resolver) {
    
  •            if (!$resolver->supports($request, $metadata)) {
    
  •                continue;
    
  •            }
    
  •            $resolved = $resolver->resolve($request, $metadata);
    
  •            if (!$resolved instanceof \Generator) {
    

@wouterj https://github.com/WouterJ If we don't expect other code to
make use of the return values, we should remove the
ArgumentResolverInterface. Having such an interface calls for custom
implementations imo which will then probably rely on the return type.


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
https://github.com/symfony/symfony/pull/18308/files/b1e12875553883b0c3a01a7581109e0bc946a68a#r59132159

throw new \InvalidArgumentException(sprintf('%s::resolve() must yield at least one value.', get_class($resolver)));
}

foreach ($resolved as $append) {
$arguments[] = $append;
}

// continue to the next controller argument
continue 2;
}

$representative = $controller;

if (is_array($representative)) {
$representative = sprintf('%s::%s()', get_class($representative[0]), $representative[1]);
} elseif (is_object($representative)) {
$representative = get_class($representative);
}

throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument (because there is no default value or because there is a non optional argument after this one).', $representative, $metadata->getName()));
}

return $arguments;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;

/**
* Yields the default value defined in the action signature when no value has been given.
*
* @author Iltar van der Berg <[email protected]>
*/
final class DefaultValueResolver implements ArgumentValueResolverInterface
{
/**
* {@inheritdoc}
*/
public function supports(Request $request, ArgumentMetadata $argument)
{
return $argument->hasDefaultValue() && !$request->attributes->has($argument->getName());
}

/**
* {@inheritdoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument)
{
yield $argument->getDefaultValue();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;

/**
* Yields a non-variadic argument's value from the request attributes.
*
* @author Iltar van der Berg <[email protected]>
*/
final class RequestAttributeValueResolver implements ArgumentValueResolverInterface
{
/**
* {@inheritdoc}
*/
public function supports(Request $request, ArgumentMetadata $argument)
{
return !$argument->isVariadic() && $request->attributes->has($argument->getName());
}

/**
* {@inheritdoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument)
{
yield $request->attributes->get($argument->getName());
}
}
Loading