<p dir="auto"><a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/stof/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/stof">@stof</a> <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/weaverryan/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/weaverryan">@weaverryan</a> <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/fabpot/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/fabpot">@fabpot</a> <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/totophe/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/totophe">@totophe</a> @genexp <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/rquadling/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/rquadling">@rquadling</a> <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/nud/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/nud">@nud</a> I am wondering if Symfony 3 has fixed the problem with unique constraints on super- and subclasses since it does not work for me...</p>
<p dir="auto">For example, I have 2 sub-entities that are derived from an abstract parent entity and I gave the parent 2 unique constraints like this:</p>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="/**
 * User
 *
 * @ORM\Table(
 *     name=&quot;user&quot;,
 *     indexes={
 *          @ORM\Index(name=&quot;user_fn_ix&quot;, columns={&quot;first_name&quot;}),
 *          @ORM\Index(name=&quot;user_ln_ix&quot;, columns={&quot;last_name&quot;}),
 *          @ORM\Index(name=&quot;user_phone_ix&quot;, columns={&quot;phone_number&quot;}),
 *     }
 * )
 * @ORM\InheritanceType(&quot;JOINED&quot;)
 * @ORM\DiscriminatorColumn(name=&quot;type&quot;, type=&quot;string&quot;)
 * @ORM\DiscriminatorMap({
 *      &quot;courier&quot;: &quot;AppBundle\Entity\User\Courier&quot;,
 *      &quot;client&quot;: &quot;AppBundle\Entity\User\Client&quot;,
 * })
 * @ORM\Entity(repositoryClass=&quot;AppBundle\Repository\User\UserRepository&quot;)
 * @UniqueEntity(fields={&quot;email&quot;})
 * @UniqueEntity(fields={&quot;phoneNumber&quot;})
 * @Serializer\ExclusionPolicy(&quot;all&quot;)
 * @Serializer\Discriminator(field=&quot;type&quot;, map={
 *      &quot;courier&quot;: &quot;AppBundle\Entity\User\Courier&quot;,
 *      &quot;client&quot;: &quot;AppBundle\Entity\User\Client&quot;,
 * })
 * @Vich\Uploadable()
 */
abstract class AbstractUser implements UserInterface { ... }
*/"><pre class="notranslate"><span class="pl-c">/**</span>
<span class="pl-c"> * User</span>
<span class="pl-c"> *</span>
<span class="pl-c"> * @ORM\Table(</span>
<span class="pl-c"> *     name="user",</span>
<span class="pl-c"> *     indexes={</span>
<span class="pl-c"> *          @ORM\Index(name="user_fn_ix", columns={"first_name"}),</span>
<span class="pl-c"> *          @ORM\Index(name="user_ln_ix", columns={"last_name"}),</span>
<span class="pl-c"> *          @ORM\Index(name="user_phone_ix", columns={"phone_number"}),</span>
<span class="pl-c"> *     }</span>
<span class="pl-c"> * )</span>
<span class="pl-c"> * @ORM\InheritanceType("JOINED")</span>
<span class="pl-c"> * @ORM\DiscriminatorColumn(name="type", type="string")</span>
<span class="pl-c"> * @ORM\DiscriminatorMap({</span>
<span class="pl-c"> *      "courier": "AppBundle\Entity\User\Courier",</span>
<span class="pl-c"> *      "client": "AppBundle\Entity\User\Client",</span>
<span class="pl-c"> * })</span>
<span class="pl-c"> * @ORM\Entity(repositoryClass="AppBundle\Repository\User\UserRepository")</span>
<span class="pl-c"> * @UniqueEntity(fields={"email"})</span>
<span class="pl-c"> * @UniqueEntity(fields={"phoneNumber"})</span>
<span class="pl-c"> * @Serializer\ExclusionPolicy("all")</span>
<span class="pl-c"> * @Serializer\Discriminator(field="type", map={</span>
<span class="pl-c"> *      "courier": "AppBundle\Entity\User\Courier",</span>
<span class="pl-c"> *      "client": "AppBundle\Entity\User\Client",</span>
<span class="pl-c"> * })</span>
<span class="pl-c"> * @Vich\Uploadable()</span>
<span class="pl-c"> */</span>
<span class="pl-k">abstract</span> <span class="pl-k">class</span> <span class="pl-v">AbstractUser</span> <span class="pl-k">implements</span> <span class="pl-v">UserInterface</span> { ... }
*/</pre></div>
<p dir="auto">There are 2 subclasses that are derived from the superclass:</p>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="/**
 * Client
 *
 * @ORM\Entity(repositoryClass=&quot;AppBundle\Repository\User\ClientRepository&quot;)
 * @Serializer\ExclusionPolicy(&quot;all&quot;)
 */
class Client extends AbstractUser {...}"><pre class="notranslate"><span class="pl-c">/**</span>
<span class="pl-c"> * Client</span>
<span class="pl-c"> *</span>
<span class="pl-c"> * @ORM\Entity(repositoryClass="AppBundle\Repository\User\ClientRepository")</span>
<span class="pl-c"> * @Serializer\ExclusionPolicy("all")</span>
<span class="pl-c"> */</span>
<span class="pl-k">class</span> <span class="pl-v">Client</span> <span class="pl-k">extends</span> <span class="pl-v">AbstractUser</span> {...}</pre></div>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="/**
 * Courier
 *
 * @ORM\Entity(repositoryClass=&quot;AppBundle\Repository\User\CourierRepository&quot;)
 * @UniqueEntity(fields={&quot;email&quot;}, groups={&quot;registration&quot;, &quot;profile&quot;})
 * @UniqueEntity(fields={&quot;phoneNumber&quot;}, groups={&quot;registration&quot;, &quot;profile&quot;})
 * @Serializer\ExclusionPolicy(&quot;all&quot;)
 */
class Courier extends AbstractUser { ... }"><pre class="notranslate"><span class="pl-c">/**</span>
<span class="pl-c"> * Courier</span>
<span class="pl-c"> *</span>
<span class="pl-c"> * @ORM\Entity(repositoryClass="AppBundle\Repository\User\CourierRepository")</span>
<span class="pl-c"> * @UniqueEntity(fields={"email"}, groups={"registration", "profile"})</span>
<span class="pl-c"> * @UniqueEntity(fields={"phoneNumber"}, groups={"registration", "profile"})</span>
<span class="pl-c"> * @Serializer\ExclusionPolicy("all")</span>
<span class="pl-c"> */</span>
<span class="pl-k">class</span> <span class="pl-v">Courier</span> <span class="pl-k">extends</span> <span class="pl-v">AbstractUser</span> { ... }</pre></div>
<p dir="auto">I created a form type for <code class="notranslate">Courier</code></p>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="class CourierType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            -&gt;add('avatarFile', FileType::class, ['required' =&gt; true])
            -&gt;add('firstName', TextType::class, ['required' =&gt; true])
            -&gt;add('lastName', TextType::class, ['required' =&gt; true])
            -&gt;add('email', EmailType::class, ['required' =&gt; true])
            -&gt;add('password', PasswordType::class, ['required' =&gt; true])
            -&gt;add('phoneNumber', PhoneNumberType::class, [
                'widget' =&gt; PhoneNumberType::WIDGET_COUNTRY_CHOICE,
                'country_choices' =&gt; ['US'],
                'preferred_country_choices' =&gt; ['US'],
                'default_region' =&gt; 'US',
                'required' =&gt; true,
            ])
            -&gt;add('address', TextType::class, ['required' =&gt; true])
            -&gt;add('zipCode', TextType::class, ['required' =&gt; true])
            -&gt;add('biography', TextareaType::class, ['required' =&gt; true])
            -&gt;add('vehicleType', TextType::class, ['required' =&gt; true])
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver-&gt;setDefaults([
            'data_class' =&gt; Courier::class,
            'validation_groups' =&gt; ['Default', 'registration'],
        ]);
    }

    public function getName()
    {
        return 'courier';
    }
}"><pre class="notranslate"><span class="pl-k">class</span> <span class="pl-v">CourierType</span> <span class="pl-k">extends</span> <span class="pl-v">AbstractType</span>
{
    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">buildForm</span>(<span class="pl-smi"><span class="pl-smi">FormBuilderInterface</span></span> <span class="pl-s1"><span class="pl-c1">$</span>builder</span>, <span class="pl-smi">array</span> <span class="pl-s1"><span class="pl-c1">$</span>options</span>)
    {
        <span class="pl-s1"><span class="pl-c1">$</span>builder</span>
            -&gt;<span class="pl-en">add</span>(<span class="pl-s">'avatarFile'</span>, <span class="pl-v">FileType</span>::class, [<span class="pl-s">'required'</span> =&gt; <span class="pl-c1">true</span>])
            -&gt;<span class="pl-en">add</span>(<span class="pl-s">'firstName'</span>, <span class="pl-v">TextType</span>::class, [<span class="pl-s">'required'</span> =&gt; <span class="pl-c1">true</span>])
            -&gt;<span class="pl-en">add</span>(<span class="pl-s">'lastName'</span>, <span class="pl-v">TextType</span>::class, [<span class="pl-s">'required'</span> =&gt; <span class="pl-c1">true</span>])
            -&gt;<span class="pl-en">add</span>(<span class="pl-s">'email'</span>, <span class="pl-v">EmailType</span>::class, [<span class="pl-s">'required'</span> =&gt; <span class="pl-c1">true</span>])
            -&gt;<span class="pl-en">add</span>(<span class="pl-s">'password'</span>, <span class="pl-v">PasswordType</span>::class, [<span class="pl-s">'required'</span> =&gt; <span class="pl-c1">true</span>])
            -&gt;<span class="pl-en">add</span>(<span class="pl-s">'phoneNumber'</span>, <span class="pl-v">PhoneNumberType</span>::class, [
                <span class="pl-s">'widget'</span> =&gt; <span class="pl-v">PhoneNumberType</span>::<span class="pl-c1">WIDGET_COUNTRY_CHOICE</span>,
                <span class="pl-s">'country_choices'</span> =&gt; [<span class="pl-s">'US'</span>],
                <span class="pl-s">'preferred_country_choices'</span> =&gt; [<span class="pl-s">'US'</span>],
                <span class="pl-s">'default_region'</span> =&gt; <span class="pl-s">'US'</span>,
                <span class="pl-s">'required'</span> =&gt; <span class="pl-c1">true</span>,
            ])
            -&gt;<span class="pl-en">add</span>(<span class="pl-s">'address'</span>, <span class="pl-v">TextType</span>::class, [<span class="pl-s">'required'</span> =&gt; <span class="pl-c1">true</span>])
            -&gt;<span class="pl-en">add</span>(<span class="pl-s">'zipCode'</span>, <span class="pl-v">TextType</span>::class, [<span class="pl-s">'required'</span> =&gt; <span class="pl-c1">true</span>])
            -&gt;<span class="pl-en">add</span>(<span class="pl-s">'biography'</span>, <span class="pl-v">TextareaType</span>::class, [<span class="pl-s">'required'</span> =&gt; <span class="pl-c1">true</span>])
            -&gt;<span class="pl-en">add</span>(<span class="pl-s">'vehicleType'</span>, <span class="pl-v">TextType</span>::class, [<span class="pl-s">'required'</span> =&gt; <span class="pl-c1">true</span>])
        ;
    }

    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">configureOptions</span>(<span class="pl-smi"><span class="pl-smi">OptionsResolver</span></span> <span class="pl-s1"><span class="pl-c1">$</span>resolver</span>)
    {
        <span class="pl-s1"><span class="pl-c1">$</span>resolver</span>-&gt;<span class="pl-en">setDefaults</span>([
            <span class="pl-s">'data_class'</span> =&gt; <span class="pl-v">Courier</span>::class,
            <span class="pl-s">'validation_groups'</span> =&gt; [<span class="pl-s">'Default'</span>, <span class="pl-s">'registration'</span>],
        ]);
    }

    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">getName</span>()
    {
        <span class="pl-k">return</span> <span class="pl-s">'courier'</span>;
    }
}</pre></div>
<p dir="auto">Validation Code:</p>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="class AppController extends Controller
{
    public function indexAction(Request $request)
    {
       $form = $this-&gt;createForm(CourierType::class, new Courier());
       $form-&gt;handleRequest($request);

       if ($form-&gt;isValid()) {
          echo 'yes';
       } else {
         echo 'invalid';
       }
    }
}"><pre class="notranslate"><span class="pl-k">class</span> <span class="pl-v">AppController</span> <span class="pl-k">extends</span> <span class="pl-v">Controller</span>
{
    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">indexAction</span>(<span class="pl-smi"><span class="pl-smi">Request</span></span> <span class="pl-s1"><span class="pl-c1">$</span>request</span>)
    {
       <span class="pl-s1"><span class="pl-c1">$</span>form</span> = <span class="pl-s1"><span class="pl-c1">$</span><span class="pl-smi">this</span></span>-&gt;<span class="pl-en">createForm</span>(<span class="pl-v">CourierType</span>::class, <span class="pl-k">new</span> <span class="pl-v">Courier</span>());
       <span class="pl-s1"><span class="pl-c1">$</span>form</span>-&gt;<span class="pl-en">handleRequest</span>(<span class="pl-s1"><span class="pl-c1">$</span>request</span>);

       <span class="pl-k">if</span> (<span class="pl-s1"><span class="pl-c1">$</span>form</span>-&gt;<span class="pl-en">isValid</span>()) {
          <span class="pl-k">echo</span> <span class="pl-s">'yes'</span>;
       } <span class="pl-k">else</span> {
         <span class="pl-k">echo</span> <span class="pl-s">'invalid'</span>;
       }
    }
}</pre></div>
<p dir="auto">Running the controller always gives me 'yes' even if I pass a duplicate email or phone number.  This means the <code class="notranslate">@UniqueConstraint</code> constraints are not working.<br>
I thought this should be fixed now as this issue has been raised by many.</p>
<p dir="auto">What's wrong with the code?</p>
<p dir="auto">Currently, the UniqueEntity constraint can fail when used on an inherited Entity, depending on where the validated field was declared in the Inheritance chain. Let me give you an example, suppose we have defined the following classes, where both <code class="notranslate">Student</code> and <code class="notranslate">Teacher</code> extend <code class="notranslate">Person</code> and we have used Doctrine's Class Table Inheritance:</p>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="&lt;?php

Class Person
{
    /**
     * @UniqueEntity(&quot;name&quot;)
     */
    private $name;
}

Class Student extends Person { }

Class Teacher extends Person { }
"><pre class="notranslate"><span class="pl-ent">&lt;?php</span>

<span class="pl-k">Class</span> <span class="pl-v">Person</span>
{
    <span class="pl-c">/**</span>
<span class="pl-c">     * @UniqueEntity("name")</span>
<span class="pl-c">     */</span>
    <span class="pl-k">private</span> <span class="pl-c1"><span class="pl-c1">$</span>name</span>;
}

<span class="pl-k">Class</span> <span class="pl-v">Student</span> <span class="pl-k">extends</span> <span class="pl-v">Person</span> { }

<span class="pl-k">Class</span> <span class="pl-v">Teacher</span> <span class="pl-k">extends</span> <span class="pl-v">Person</span> { }</pre></div>
<p dir="auto">Now let's insert a new <code class="notranslate">Teacher</code> named "Fabien", the validator kicks in, see that no Teacher with that name exists and "Fabien" is happily inserted on the database (inside the person table).</p>
<p dir="auto">Trying to insert another Teacher with the same name works as expected, stating that our unique constraint was violated.</p>
<p dir="auto">Now, if we try to insert a new Student whose name is "Fabien" as well the validator fails to do its work, letting this violation pass unseen and then when Doctrine tries to persist the new Student (assuming your name is a unique field, which probably should) the database will yell back at you saying that another record with the same name already exists in the person table.</p>
<p dir="auto">This happens because the validator uses the repository of the entity in which the validation was triggered, not the repository in which the field was defined. So a SQL similar to this is generated:</p>
<div class="highlight highlight-source-sql notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="SELECT * FROM student INNER JOIN person ON student.id = person.id WHERE person.name = 'Fabien'"><pre class="notranslate"><span class="pl-k">SELECT</span> <span class="pl-k">*</span> <span class="pl-k">FROM</span> student <span class="pl-k">INNER JOIN</span> person <span class="pl-k">ON</span> <span class="pl-c1">student</span>.<span class="pl-c1">id</span> <span class="pl-k">=</span> <span class="pl-c1">person</span>.<span class="pl-c1">id</span> <span class="pl-k">WHERE</span> <span class="pl-c1">person</span>.<span class="pl-c1">name</span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">'</span>Fabien<span class="pl-pds">'</span></span></pre></div>
<p dir="auto">And because of the fact that no Student with the name "Fabien" exists (we have added a Teacher with the name "Fabien") the validation will not work properly.</p>
<p dir="auto">I'm currently working on a fix for this issue so a PR will follow shortly.</p>