<p dir="auto"><strong>Symfony version(s) affected</strong>: 4.1.0</p>
<p dir="auto"><strong>Description</strong><br>
I'm using the form component to map request data to a DTO (not an entity), and I'm doing some validation in the form component. One of the validations I'm doing is on an email field. I'm using the UniqueEntity constraint to do that. But in order to keep my domain logic separated from the Validation component, I'm registering the constraints in the form itself. Apparently the UniqueEntity constraint does not work in that scenario.</p>
<p dir="auto"><strong>How to reproduce</strong><br>
First, create a dummy User entity, just with email.</p>
<p dir="auto">Then, create this DTO class outside the entity folder:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="&lt;?php

namespace App\Domain\Register;

/**
 * Class RegisterStoreCommand
 * @package App\Domain\Register
 * @author Matías Navarro Carter &lt;mnavarro@option.cl&gt;
 */
class RegisterNewStoreCommand
{
    /**
     * @var string
     */
    private $name;
    /**
     * @var string
     */
    private $email;
    /**
     * @var string
     */
    private $plainPassword;
    /**
     * @var string
     */
    private $storeName;
    /**
     * @var string
     */
    private $storeWebsite;

    public function __construct(string $name, string $email, string $plainPassword, string $storeName, string $storeWebsite)
    {
        $this-&gt;name = $name;
        $this-&gt;email = $email;
        $this-&gt;plainPassword = $plainPassword;
        $this-&gt;storeName = $storeName;
        $this-&gt;storeWebsite = $storeWebsite;
    }

    /**
     * @return string
     */
    public function getName(): string
    {
        return $this-&gt;name;
    }

    /**
     * @return string
     */
    public function getEmail(): string
    {
        return $this-&gt;email;
    }

    /**
     * @return string
     */
    public function getPlainPassword(): string
    {
        return $this-&gt;plainPassword;
    }

    /**
     * @return string
     */
    public function getStoreName(): string
    {
        return $this-&gt;storeName;
    }

    /**
     * @return string
     */
    public function getStoreWebsite(): string
    {
        return $this-&gt;storeWebsite;
    }
}"><pre class="notranslate"><code class="notranslate">&lt;?php

namespace App\Domain\Register;

/**
 * Class RegisterStoreCommand
 * @package App\Domain\Register
 * @author Matías Navarro Carter &lt;mnavarro@option.cl&gt;
 */
class RegisterNewStoreCommand
{
    /**
     * @var string
     */
    private $name;
    /**
     * @var string
     */
    private $email;
    /**
     * @var string
     */
    private $plainPassword;
    /**
     * @var string
     */
    private $storeName;
    /**
     * @var string
     */
    private $storeWebsite;

    public function __construct(string $name, string $email, string $plainPassword, string $storeName, string $storeWebsite)
    {
        $this-&gt;name = $name;
        $this-&gt;email = $email;
        $this-&gt;plainPassword = $plainPassword;
        $this-&gt;storeName = $storeName;
        $this-&gt;storeWebsite = $storeWebsite;
    }

    /**
     * @return string
     */
    public function getName(): string
    {
        return $this-&gt;name;
    }

    /**
     * @return string
     */
    public function getEmail(): string
    {
        return $this-&gt;email;
    }

    /**
     * @return string
     */
    public function getPlainPassword(): string
    {
        return $this-&gt;plainPassword;
    }

    /**
     * @return string
     */
    public function getStoreName(): string
    {
        return $this-&gt;storeName;
    }

    /**
     * @return string
     */
    public function getStoreWebsite(): string
    {
        return $this-&gt;storeWebsite;
    }
}
</code></pre></div>
<p dir="auto">After that, the FormType:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="&lt;?php

namespace App\Form;

use App\Domain\Register\RegisterNewStoreCommand;
use App\Entity\User;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Url;

/**
 * Class RegisterStoreType
 * @package App\Form
 * @author Matías Navarro Carter &lt;mnavarro@option.cl&gt;
 */
class RegisterStoreType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            -&gt;add('name', TextType::class, [
                'constraints' =&gt; [
                    new NotBlank()
                ]
            ])
            -&gt;add('email', EmailType::class, [
                'constraints' =&gt; [
                    new NotBlank()
                ]
            ])
            -&gt;add('plainPassword', RepeatedType::class, [
                'type' =&gt; PasswordType::class,
                'constraints' =&gt; [
                    new NotBlank()
                ]
            ])
            -&gt;add('storeName', TextType::class, [
                'constraints' =&gt; [
                    new NotBlank()
                ]
            ])
            -&gt;add('storeWebsite', TextType::class, [
                'constraints' =&gt; [
                    new NotBlank(),
                    new Url()
                ]
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver-&gt;setDefaults([
            'constraints' =&gt; [
                new UniqueEntity(['fields' =&gt; ['email'], 'entityClass' =&gt; User::class])
            ],
            'data_class' =&gt; RegisterNewStoreCommand::class,
            'empty_data' =&gt; function(FormInterface $form) {
                return new RegisterNewStoreCommand(
                    $form-&gt;get('name')-&gt;getData(),
                    $form-&gt;get('email')-&gt;getData(),
                    $form-&gt;get('plainPassword')-&gt;getData(),
                    $form-&gt;get('storeName')-&gt;getData(),
                    $form-&gt;get('storeWebsite')-&gt;getData()
                );
            }
        ]);
    }
}"><pre class="notranslate"><code class="notranslate">&lt;?php

namespace App\Form;

use App\Domain\Register\RegisterNewStoreCommand;
use App\Entity\User;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Url;

/**
 * Class RegisterStoreType
 * @package App\Form
 * @author Matías Navarro Carter &lt;mnavarro@option.cl&gt;
 */
class RegisterStoreType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            -&gt;add('name', TextType::class, [
                'constraints' =&gt; [
                    new NotBlank()
                ]
            ])
            -&gt;add('email', EmailType::class, [
                'constraints' =&gt; [
                    new NotBlank()
                ]
            ])
            -&gt;add('plainPassword', RepeatedType::class, [
                'type' =&gt; PasswordType::class,
                'constraints' =&gt; [
                    new NotBlank()
                ]
            ])
            -&gt;add('storeName', TextType::class, [
                'constraints' =&gt; [
                    new NotBlank()
                ]
            ])
            -&gt;add('storeWebsite', TextType::class, [
                'constraints' =&gt; [
                    new NotBlank(),
                    new Url()
                ]
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver-&gt;setDefaults([
            'constraints' =&gt; [
                new UniqueEntity(['fields' =&gt; ['email'], 'entityClass' =&gt; User::class])
            ],
            'data_class' =&gt; RegisterNewStoreCommand::class,
            'empty_data' =&gt; function(FormInterface $form) {
                return new RegisterNewStoreCommand(
                    $form-&gt;get('name')-&gt;getData(),
                    $form-&gt;get('email')-&gt;getData(),
                    $form-&gt;get('plainPassword')-&gt;getData(),
                    $form-&gt;get('storeName')-&gt;getData(),
                    $form-&gt;get('storeWebsite')-&gt;getData()
                );
            }
        ]);
    }
}
</code></pre></div>
<p dir="auto"><strong>Possible Solution</strong><br>
I think that the solution may be to give more consideration to the 'entityClass' setting in the constraint definition. Could be as simple as doing a <code class="notranslate">getManagerForClass($constraint-&gt;entityClass)</code> in the validator. I don't know the side effects of that, but if a user is setting an entity class, I think that is what should be used to get the manager.</p>
<p dir="auto"><strong>Additional context</strong><br>
I'm doing all this DTO thing because I'm working on a DDD project, and I have a command bus that will handle the resulting object from the form.</p>
<table role="table">
<thead>
<tr>
<th>Q</th>
<th>A</th>
</tr>
</thead>
<tbody>
<tr>
<td>Bug report?</td>
<td>yes/no</td>
</tr>
<tr>
<td>Feature request?</td>
<td>no</td>
</tr>
<tr>
<td>BC Break report?</td>
<td>yes/no</td>
</tr>
<tr>
<td>RFC?</td>
<td>yes</td>
</tr>
<tr>
<td>Symfony version</td>
<td>3.2.0</td>
</tr>
</tbody>
</table>
<p dir="auto">I wonder how this should work for very common example using DTOs for forms. I have applied validation to my DTO</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="AppBundle\Command\Register:
    constraints:
            - Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity:
                fields: email
                entityClass: AppBundle\Entity\User
                em: default"><pre class="notranslate"><code class="notranslate">AppBundle\Command\Register:
    constraints:
            - Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity:
                fields: email
                entityClass: AppBundle\Entity\User
                em: default
</code></pre></div>
<p dir="auto">With this configuration I get error: "The class 'AppBundle\Command\Register' was not found in the chain configured namespaces AppBundle\Entity"</p>
<p dir="auto">The flow currently works like this I set <code class="notranslate">em</code> for my entity:  <code class="notranslate">AppBundle\Entity\User</code> but in validator we have getting current class metadata for main entity (in my example AppBundle\Command\Register this is not an entity) $em-&gt;getClassMetadata(get_class($entity)); so this is why it fails.</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="# Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator

        if ($constraint-&gt;em) {
            $em = $this-&gt;registry-&gt;getManager($constraint-&gt;em);

            if (!$em) {
                throw new ConstraintDefinitionException(sprintf('Object manager &quot;%s&quot; does not exist.', $constraint-&gt;em));
            }
        } else {
            $em = $this-&gt;registry-&gt;getManagerForClass(get_class($entity));

            if (!$em) {
                throw new ConstraintDefinitionException(sprintf('Unable to find the object manager associated with an entity of class &quot;%s&quot;.', get_class($entity)));
            }
        }

        $class = $em-&gt;getClassMetadata(get_class($entity));
        /* @var $class \Doctrine\Common\Persistence\Mapping\ClassMetadata */"><pre class="notranslate"><code class="notranslate"># Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator

        if ($constraint-&gt;em) {
            $em = $this-&gt;registry-&gt;getManager($constraint-&gt;em);

            if (!$em) {
                throw new ConstraintDefinitionException(sprintf('Object manager "%s" does not exist.', $constraint-&gt;em));
            }
        } else {
            $em = $this-&gt;registry-&gt;getManagerForClass(get_class($entity));

            if (!$em) {
                throw new ConstraintDefinitionException(sprintf('Unable to find the object manager associated with an entity of class "%s".', get_class($entity)));
            }
        }

        $class = $em-&gt;getClassMetadata(get_class($entity));
        /* @var $class \Doctrine\Common\Persistence\Mapping\ClassMetadata */
</code></pre></div>
<p dir="auto">IMHO if we specify entityClass we should expect that everything will concern this entityClass. Another example of failure is when two entities: entity1 and entity2 belong to different entity managers. If this is an expected behaviour (IMHO it should work like this) we should at least add this information to documentation ?</p>