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

ParamConverter to inject the current user #327

Conversation

adamquaile
Copy link

When creating testable controllers, it's often desirable to not use the getUser function in the base controller, as this relies on the whole service container.

In this case, we might add a dependency on the SecurityContext to achieve the same result, replicating this code:

public function getUser()
{
    if (!$this->container->has('security.context')) {
        throw new \LogicException('The SecurityBundle is not registered in your application.');
    }

    if (null === $token = $this->container->get('security.context')->getToken()) {
        return;
    }

    if (!is_object($user = $token->getUser())) {
        return;
    }

    return $user;
}

This parameter converter streamlines the process and injects the current user into the controller method instead using the following format:

/**
 * @ParamConverter(converter="current_user", name="parameterName")
 */
public function viewInboxAction(User $currentUser)

* Does not require controllers to depend upon the entire
* SecurityContext object.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
Copy link
Member

Choose a reason for hiding this comment

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

Not sure if this is correct.

/**
* {@inheritdoc}
*
* @throws NotFoundHttpException When invalid date given
Copy link

Choose a reason for hiding this comment

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

It seems wrong :)

Copy link
Author

Choose a reason for hiding this comment

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

Good point. I used the DateTime converter as reference. Updated now, thanks.

{
if (null === $this->security) {
throw new \LogicException('The SecurityBundle is required for the current_user ParamConverter annotation');
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I see 2 better alternatives here:

A) Instead of making it optional and throwing an exception if missing, add this service/converter only if the security component is available
B) Move this to the supports() and just return false if non-existent.

I'm in favor of A.

Copy link
Author

Choose a reason for hiding this comment

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

@iltar This is actually intentional. If this converter is ignored because the SecurityBundle is not enabled, either the type-hinted $user object will not be injected, or it might be handled by another converter which wasn't the developer's intention.

This way forces a meaningful error message to the user, and tells them how to fix the problem.

Copy link
Contributor

Choose a reason for hiding this comment

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

You have a dependency on the SecurityContext(Interface). That means this converter should not be registered at the moment your application doesn't have it. This can be gracefully done by removing it when the security.context is not available. What if I register a second param converter that listens to that interface with a lower priority that doesn't need the SecurityContext for this?

In my opinion it's not up to the param converter to determine what should happen when the SecurityContext is not available, but the configuration.

Copy link
Author

Choose a reason for hiding this comment

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

I see your point, although there can always be conflicts between two converters, as I found when doctrine was trying to handle my User objects (when not using the UserInterface) for this converter.

I'll try it this way too, only defining the service if the SecurityBundle is enabled.

@hhamon
Copy link

hhamon commented Oct 9, 2014

Just nit picking but I don't really like the name CurrentUserParamConverter. Why not using a more meaningful name like  AuthenticatedUserParamConverter?

@adamquaile
Copy link
Author

@hhamon Completely open to better names, although isn't it possible that the current user isn't authenticated?

If you type-hint against the UserInterface, or an interface which extends UserInterface,
you may omit this annotation entirely, like this:

public function myAccountAction(\Symfony\Component\Security\Core\User\UserInterface $currentUser)
Copy link
Contributor

Choose a reason for hiding this comment

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

I would add a use statement to make this more readable:

use Symfony\Component\Security\Core\User\UserInterface;

// ...

public function myAccountAction(UserInterface $currentUser)
{
    // ...
}

Copy link
Author

Choose a reason for hiding this comment

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

@xabbuh Updated. Thanks.


// ...

public function myAccountAction(\Symfony\Component\Security\Core\User\UserInterface $currentUser)
Copy link
Member

Choose a reason for hiding this comment

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

This can be simplified to public function myAccountAction(UserInterface $currentUser) as you have a use statement above.

Copy link
Author

Choose a reason for hiding this comment

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

Done. 944ec39

@adamquaile
Copy link
Author

@fabpot Job failed on travis for transient composer network issue. Restarted and it's good again.

@adamquaile
Copy link
Author

@fabpot Updated from most recent master. Any other changes I should make?

@mickaelandrieu
Copy link

nice addition :)

*/
public function apply(Request $request, ParamConverter $configuration)
{
$param = $configuration->getName();
Copy link

Choose a reason for hiding this comment

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

@adamquaile $param is used after if+throw so the value could be fetched later (after if+throw)

Copy link
Author

Choose a reason for hiding this comment

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

@MacDada Yes, you're right. I wouldn't mind making this change, if this PR is still wanted - it was opened a year ago!

Any maintainers know if there is a plan to merge this, or should we close it? @fabpot @jakzal

Copy link

Choose a reason for hiding this comment

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

@adamquaile Well, typehinting UserInterface / EntityWithUserInterface is exactly what I'm looking for, so I hope it's gonna be resolved soon :)

@linaori
Copy link
Contributor

linaori commented Apr 8, 2016

@adamquaile I think this PR is no longer required. This functionality can be build either inside the SecurityBundle in symfony or in your app manually. This PR is actually the inspiration I've used to add a doc example: https://github.com/symfony/symfony-docs/pull/6438/files#diff-378394f006769e0d975a8e861369dc33R99

@fabpot
Copy link
Member

fabpot commented Apr 10, 2016

agreed

@fabpot fabpot closed this Apr 10, 2016
fabpot added a commit to symfony/symfony that referenced this pull request Jul 1, 2016
This PR was merged into the 3.2-dev branch.

Discussion
----------

Added a SecurityUserValueResolver for controllers

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | ~
| License       | MIT
| Doc PR        | ~

This PR uses the new `ArgumentResolver` to inject a security user when the signature implies so. This is based on the [docs code example](symfony/symfony-docs#6438 (comment)) and [existing pr on the SFEB](sensiolabs/SensioFrameworkExtraBundle#327).

With the new example you can do the following:
```php
// when a User is mandatory, e.g. behind firewall
public function fooAction(UserInterface $user)

// when a User is optional, e.g. where it can be anonymous
public function barAction(UserInterface $user = null)
```
This deprecates the `Controller::getUser()` method.

I have added it on a priority of 40 so it falls just under the `RequestValueResolver`. This is because it's already used and the initial performance is less of an impact.

There was a comment asking if the `controller_argument.value_resolver` tag name wasn't too long. If decided this tag should change before 3.1 is released, I will update it in here.

*`RequestValueResolver` contains a small codestyle consistency fix.*

Commits
-------

d341889 Added a SecurityUserValueResolver for controllers
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet