Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Added docs about ArgumentValueResolvers#6438

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
17 commits
Select commitHold shift + click to select a range
7fc9fe8
Added docs about ArgumentValueResolvers
Apr 6, 2016
7ddf5d6
we>you, php>PHP, fixed directive start & list style
Apr 6, 2016
1a19d2e
Fixed the list indents
Apr 6, 2016
f491199
Added missing use statements to example
Apr 6, 2016
a07fcc3
Processed feedback from @wouterj
Apr 7, 2016
2e41c07
Added some extra info about the DefaultValueResolver
Apr 11, 2016
7cdc96b
Fixed a typo in code example
Apr 11, 2016
7d00d8c
Fixed a wrong tag name
Apr 12, 2016
f9cbe71
Added instanceof check in example
Apr 12, 2016
4c6ed2a
Respecting the line length
Apr 12, 2016
9784406
Fixed the tag name
Apr 14, 2016
e3d1b48
Tried to make the default value resolving more clear
Apr 18, 2016
55a87b5
Minor fixes
Apr 29, 2016
8d30575
Added instance
May 6, 2016
dd225e8
Feedback from PR
May 17, 2016
ca014a6
Fixed sub-title case
May 17, 2016
14d77e1
typehinted > type-hinted
May 17, 2016
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
219 changes: 219 additions & 0 deletionscookbook/controller/argument_value_resolver.rst
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
.. index::
single: Controller; Argument Value Resolvers

Extending Action Argument Resolving
===================================

.. versionadded:: 3.1
The ``ArgumentResolver`` and value resolvers were introduced in Symfony 3.1.

In the book, you've learned that you can get the :class:`Symfony\\Component\\HttpFoundation\\Request`
object via an argument in your controller. This argument has to be type-hinted
by the ``Request`` class in order to be recognized. This is done via the
:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver`. By
creating and registering custom argument value resolvers, you can extend
this functionality.

Functionality Shipped with the HttpKernel
-----------------------------------------

Symfony ships with four value resolvers in the HttpKernel component:

:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolver\\ArgumentFromAttributeResolver`
Attempts to find a request attribute that matches the name of the argument.

:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolver\\RequestValueResolver`
Injects the current ``Request`` if type-hinted with ``Request``, or a
sub-class thereof.

:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolver\\DefaultValueResolver`
Will set the default value of the argument if present and the argument
is optional.

:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolver\\VariadicValueResolver`
Verifies in the request if your data is an array and will add all of
them to the argument list. When the action is called, the last (variadic)
argument will contain all the values of this array.

.. note::

Prior to Symfony 3.1, this logic was resolved within the ``ControllerResolver``.
The old functionality is rewritten to the aforementioned value resolvers.

Adding a Custom Value Resolver
------------------------------

Adding a new value resolver requires one class and one service defintion.
In the next example, you'll create a value resolver to inject the ``User``
object from the security system. Given you write the following action::

namespace AppBundle\Controller;

use AppBundle\Entity\User;
use Symfony\Component\HttpFoundation\Response;

class UserController
{
public function indexAction(User $user)
Copy link
Contributor

Choose a reason for hiding this comment

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

I understand correctly comments below, you should add "= null" in the signature, right ?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Only when it's optional

{
return new Response('<html><body>Hello '.$user->getUsername().'!</body></html>');
}
}

Somehow you will have to get the ``User`` object and inject it into the controller.
This can be done by implementing the :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolverInterface`.
This interface specifies that you have to implement two methods:

``supports()``
This method is used to check whether the value resolver supports the
given argument. ``resolve()`` will only be executed when this returns ``true``.
``resolve()``
This method will resolve the actual value for the argument. Once the value
is resolved, you must `yield`_ the value to the ``ArgumentResolver``.

Both methods get the ``Request`` object, which is the current request, and an
:class:`Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadata`
instance. This object contains all information retrieved from the method signature
for the current argument.

Now that you know what to do, you can implement this interface. To get the
current ``User``, you need the current security token. This token can be
retrieved from the token storage::

namespace AppBundle\ArgumentValueResolver;

use AppBundle\Entity\User;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class UserValueResolver implements ArgumentValueResolverInterface
{
private $tokenStorage;

public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}

public function supports(Request $request, ArgumentMetadata $argument)
{
if (User::class !== $argument->getType()) {
return false;
}

$token = $this->tokenStorage->getToken();

if (!$token instanceof TokenInterface) {
return false;
}

return $token->getUser() instanceof User;
}

public function resolve(Request $request, ArgumentMetadata $argument)
{
yield $this->tokenStorage->getToken()->getUser();
Copy link
Member

Choose a reason for hiding this comment

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

yield can be pretty new to our readers. Maybe we should add a short description about what it does?

MacDada reacted with thumbs down emoji
Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

I could add a reference to the php.net docs with one of those nice footnote features rst has

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Theyield_ has been added to the definition list explaining supports and resolve

Copy link
Member

Choose a reason for hiding this comment

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

What if there is no token or no user? This should probably return null, which means that you must have a= null default when using this in a controller signature.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

In that case the default value resolver will handle it

Copy link
Member

Choose a reason for hiding this comment

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

Right, so I think it deserves a comment to explain this behavior. And adding= null is needed.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Would you want it additionally in the docblock or just in here?

}
}

In order to get the actual ``User`` object in your argument, the given value
must fulfill the following requirements:

* An argument must be type-hinted as ``User`` in your action method signature;
* A security token must be present;
* The value must be an instance of the ``User``.

When all those requirements are met and true is returned, the ``ArgumentResolver``
calls ``resolve()`` with the same values as it called ``supports()``.

That's it! Now all you have to do is add the configuration for the service
container. This can be done by tagging the service with ``controller.argument_resolver``
and adding a priority.

.. note::

While adding a priority is optional, it's recommended to add one to
make sure the expected value is injected. The ``ArgumentFromAttributeResolver``
has a priority of 100. As this one is responsible for fetching attributes
from the ``Request``, it's also recommended to trigger your custom value
resolver with a lower priority. This makes sure the argument resolvers
are not triggered in (e.g.) subrequests if you pass your user along:
``{{ render(controller('AppBundle:User:index', {'user', app.user})) }}``.

.. configuration-block::

.. code-block:: yaml

# app/config/services.yml
services:
app.value_resolver.user:
class: AppBundle\ArgumentValueResolver\UserValueResolver
arguments:
- '@security.token_storage'
Copy link
Contributor

Choose a reason for hiding this comment

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

" instead of' ?

Copy link
Member

Choose a reason for hiding this comment

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

Using single quotes is better in Yaml

Copy link
Contributor

Choose a reason for hiding this comment

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

Would be curious for the reason: not that I don't agree with it but I always thought there was absolutely not difference. Besides it's quite inconsistent (or at least used to be I didn't check for a while now) in Symfony/Symfony docs.

Choose a reason for hiding this comment

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

Here you can find more details about single/double quotes:http://symfony.com/doc/current/components/yaml/yaml_format.html

Copy link
Contributor

Choose a reason for hiding this comment

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

@javiereguiluz my comment was maybe a bit misleading: I'm aware of the differences, I was only talking for when both were giving the same result as why to pick simple quotes over double ones.

Copy link
Contributor

Choose a reason for hiding this comment

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

@theofidry the link actually says:

Strings containing any of the following characters must be quoted. Although you can use double quotes, for these characters it is more convenient to use single quotes, which avoids having to escape any backslash \

Copy link
Member

Choose a reason for hiding this comment

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

@theofidry we recently updated most double quotes in Yaml to single quotes (for consistency).

Using double quotes (or advocating using them) leads to problems with certain strings that are valid in PHP double quotes. For instance,"AppBundle\Controller\StaticController" is a valid PHP value, but it would result in a parser error in Yaml as\C and\S are not valid character sequences.

tags:
- { name: controller.argument_value_resolver, priority: 50 }

.. code-block:: xml

<!-- app/config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<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="app.value_resolver.user" class="AppBundle\ArgumentValueResolver\UserValueResolver">
<argument type="service" id="security.token_storage">
<tag name="controller.argument_value_resolver" priority="50" />
</service>
</services>

</container>

.. code-block:: php

// app/config/services.php
use Symfony\Component\DependencyInjection\Definition;

$defintion = new Definition(
'AppBundle\ArgumentValueResolver\UserValueResolver',
array(new Reference('security.token_storage'))
);
$definition->addTag('controller.argument_value_resolver', array('priority' => 50));
$container->setDefinition('app.value_resolver.user', $definition);

Creating an Optional User Resolver
----------------------------------

When you want your user to be optional, e.g. when your page is behind a
firewall that also allows anonymous authentication, you might not always
have a security user. To get this to work, you only have to change your
method signature to `UserInterface $user = null`.

When you take the ``UserValueResolver`` from the previous example, you can
Copy link
Contributor

@HeahDudeHeahDudeApr 18, 2016
edited
Loading

Choose a reason for hiding this comment

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

Ok, thanks, it's definitely more clear. But the problem I was pointing (if there was one) still exists.

Currently, the "previous example" requires a user (no defaultnull in the signature), so theDefaultValueResolver is not called while theUserValueResolver does not support the resolving if the token storage returnsnull (or not an instance ofUser. Hence if I'm not wrong the controller call is broken. Is that what you mean by "there is no logic in case of failure to comply to the requirements"?.

I think you should either make the user optional in the signature of the example, or remove the test of the returned value from theTokenStorage insupports method and throw a proper exception inresolve() if not an instance ofUser.

What do you think ?

Question off topic, coming up while writing this: whysupports method takes an "s" and notresolve?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Regarding the name: "A supports B", hence I named it like that, is this incorrect?

Hence if I'm not wrong the controller call is broken. Is that what you mean by "there is no logic in case of failure to comply to the requirements"?.

I think you should either make the user optional in the signature of the example, or remove the test of the returned value from the TokenStorage in supports method and throw a proper exception in resolve() if not an instance of User.

If nothing can be resolved, theArgumentResolver will throw an except that nothing could resolve that argument.

What I could do is grab theUserInterface implementation I got a PR for, would that make it more clear?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok sorry, I missed theArgumentResolver exception. Maybe it could be enough to explicit it somewhere in this PR?

For the naming I used the same for formatters' interface insymfony/symfony#18450 but I was just wondering why this convention of an "s" forsupports(), maybe because it's passive (contrary to an action of supporting)?

see there is no logic in case of failure to comply to the requirements. Default
values are defined in the signature and are available in the ``ArgumentMetadata``.
When a default value is available and there are no resolvers that support
the given value, the ``DefaultValueResolver`` is triggered. This Resolver
takes the default value of your argument and yields it to the argument list::
Copy link
Contributor

Choose a reason for hiding this comment

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

would link the yield reference here too

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

There is no "yield" that I can use though, the only word here referring to it is "yields"


namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver;

Copy link
Contributor

Choose a reason for hiding this comment

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

use statements are missing

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

final class DefaultValueResolver implements ArgumentValueResolverInterface
{
public function supports(Request $request, ArgumentMetadata $argument)
{
return $argument->hasDefaultValue();
}

public function resolve(Request $request, ArgumentMetadata $argument)
{
yield $argument->getDefaultValue();
}
}

.. _`yield`: http://php.net/manual/en/language.generators.syntax.php
1 change: 1 addition & 0 deletionscookbook/controller/index.rst
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,3 +7,4 @@ Controller
error_pages
service
upload_file
argument_value_resolver
1 change: 1 addition & 0 deletionscookbook/map.rst.inc
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -56,6 +56,7 @@
* :doc:`/cookbook/controller/error_pages`
* :doc:`/cookbook/controller/service`
* :doc:`/cookbook/controller/upload_file`
* :doc:`/cookbook/controller/argument_value_resolver`

* **Debugging**

Expand Down

[8]ページ先頭

©2009-2025 Movatter.jp