Use Symfony UniqueEntity with Doctrine

2019-02-19 18:29发布

问题:

I am trying to get Symfony's UniqueEntity validator working for my Doctrine entities. The Symfony validator is already hooked up and working, the UniqueEntity from Symfony\Bridge, however, is more challenging, displaying this error:

PHP Fatal error:  Class 'doctrine.orm.validator.unique' not found in /app/vendor/symfony/validator/Symfony/Component/Validator/ConstraintValidatorFactory.php on line 46

It appears as if the ValidatorFactory is requesting the UniqueEntity validator from the Symfony container, which I don't have because I am not using full stack symfony.

My entity looks like this:

<?php
namespace App\Model;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints as AssertDoctrine;

/**
 * User
 *
 * @ORM\Table(name="users", uniqueConstraints={@ORM\UniqueConstraint(name="id_UNIQUE", columns={"id"}), @ORM\UniqueConstraint(name="username_UNIQUE", columns={"username"})})
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 * @AssertDoctrine\UniqueEntity("username")
 */
class User extends AbstractEntity
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    protected $id;

    /**
     * @var string
     *
     * @ORM\Column(name="username", type="string", length=45, unique=true, nullable=false)
     * @Assert\Length(min=3, max=45)
     */
    protected $username;

    /** ... */
}

where AbstractEntity is a mapped superclass including a Trait that provides validation functionality for all entities:

<?php
namespace App\Model\Traits;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Validation;
use App\Utility\Exception\ConstraintViolationException;

/**
 * A trait enabling validation of Doctrine entities to ensure invalid entities
 * don't reach the persistence layer.
 */
trait ValidatorAwareTrait
{
    public function validate()
    {
        $validator = Validation::createValidatorBuilder()
            ->enableAnnotationMapping()
            ->getValidator();

        $violations = $validator->validate($this);

        // Count is used as this is not an array but a ConstraintViolationList
        if (count($violations) !== 0) {
            $message = $violations[0]->getPropertyPath() . ': ' . $violations[0]->getMessage();
            throw new ConstraintViolationException($message);
        }
        return true;
    }
}

回答1:

Ref:

  1. DoctrineBridge\Validator\Constraints\UniqueEntity
  2. Symfony\Component\Validator\Constraint

I had this same issue a couple of months ago.

Short answer: You will not be able to use the UniqueEntity Validator without also using some type of DI which lists a service of 'doctrine.orm.validator.unique' and translates this into Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator.

Long answer: The error message you are seeing is the result of the fact that DoctrineBridge\Validator\Constraints\UniqueEntity reimplements Symfony\Component\Validator\Constraint::validatedBy(). In fact if you view the comment above DoctrineBridge\Validator\Constraints\UniqueEntity::validatedBy() you will see that it states "the validator must be defined as a service with this name" which is a lead to the fact that this validator is expected to be used with the Symfony DependencyInjection Component.

What isn't immediately apparent is that the service name that is being displayed within the error message is actually being defined within Doctrine\DoctrineBundle\Resources\Config\Orm.xml

Apologies for not providing the link to the orm.xml and the dbal.xml configuration files. My reputation does not currently allow for it.



回答2:

First you should check if validation is enabled in your config.yml:

framework:
    validation:      { enable_annotations: true }

Next, make sure you provide the required attribute fields in your unique annotation:

@AssertDoctrine\UniqueEntity(fields={"username"})

Last, if the previous steps still fail, you can provide your own unique validator service (See original service for reference) in the annotation as such:

@AssertDoctrine\UniqueEntity(fields={"username"}, 
service="use.my.unique.validator.service") 

You can even provide entity manager via the em-option in this annotation.



回答3:

 $validator = Validation::createValidatorBuilder()
        ->setConstraintValidatorFactory($this->validatorFactory)
        ->enableAnnotationMapping()
        ->getValidator();

    $violations = $validator->validate($user);

    if (count($violations) !== 0) {
        $message = $violations[0]->getPropertyPath() . ': ' . $violations[0]->getMessage();
        throw new \Exception($message);
    }

    $this->entityManager->persist($user);
    $this->entityManager->flush();

Where $this->validatorFactory is service '@validator.validator_factory'