Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony2: How to validate an entity by taking into account the currently logged in user?

I'm doing a custom validation using callback inside the User entity.

Therefore I need to get the User object of the currently logged user.

In controller I can do it like this:

$user= $this->get('security.context')->getToken()->getUser();

But how should I do it inside the User entity?

like image 331
rvaliev Avatar asked Mar 28 '16 15:03

rvaliev


1 Answers

If you want to validate an object with any logic related to an external dependency (in your case the @security.context service to get the current user) ... you should:

  • create a custom validation-constraint
  • create a custom validator service to be used by the constraint
  • inject the @security.context service into the validator
  • use this newly created validation constraint to validate your entity

The solution is described in the documentation chapter Validators with Dependencies


This video shows why it's absolutely necessary to have the following RedBull validator.

Model/Entity

use FamilyGuy\Validator\Constraints\Peter as PeterIsNotAllowedToOrder;

class Order 
{
    /** @PeterIsNotAllowedToOrder/RedBull */
    public $drink;

Config

# app/config/services.yml
services:
    validator.red_bull:
        class: FamilyGuy\Validator\Constraints\Peter\RedBullValidator
        # for symfony < 2.6 use @security.context
        arguments: ["@security.token_storage"]
        tags:
            - name: "validator.constraint_validator"
              alias: "peter_red_bull_constraint_validator"

Constraint

use Symfony\Component\Validator\Constraint;

namespace FamilyGuy\Validator\Constraints\Peter;

/**
 * @Annotation
 */
class RedBull extends Constraint
{
    /** @var string */
    public $message = 'Peter... You are not allowed to order %drink%.';

    /** @return string */
    public function validatedBy()
    {
        // has to match the validator service's alias !
        return 'peter_red_bull_constraint_validator';
    }
}

Validator:

// For symfony < 2.6 use SecurityContextInterface
// use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

namespace FamilyGuy\Validator\Constraints\Peter;

class RedBullValidator extends ConstraintValidator
{

    /** @var TokenStorageInterface|SecurityContextInterface */
    protected $tokenStorage;

    /** @param TokenStorageInterface|SecurityContextInterface $token_storage */
    public function __construct(TokenStorageInterface $token_storage)
    {
        $this->tokenStorage = $token_storage;
    }

    public function validate($drink, Constraint $constraint)
    {
        $currentUser = $this->tokenStorage->getToken()->getUser()->getName();

        if ($currentUser !== "Peter") {
            return;
        }

        if ( $drink !== "RedBull" ) {
             return
        }

        $this->context->buildViolation($constraint->message)
            ->setParameter('%drink%', $drink)
            ->addViolation()
        ;
    }

You can't and you shouldn't use the Callback constraint to validate against any external dependency.

Don't try to inject any validation dependency into your domain model or entities directly.

Validation logic should be kept out of entities in general.

Annotations are already some sort of soft-coupling between entities and the symfony validator. That's why it's recommended to use xml configuration for doctrine- and validation-mappings usually.

like image 67
Nicolai Fröhlich Avatar answered Oct 08 '22 01:10

Nicolai Fröhlich