<p dir="auto">This is an alternative fix to <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="4242344" data-permission-text="Title is private" data-url="https://github.com/symfony/symfony/issues/4087" data-hovercard-type="issue" data-hovercard-url="/symfony/symfony/issues/4087/hovercard" href="https://github.com/symfony/symfony/issues/4087">#4087</a> (and therefore impacts <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="88717483" data-permission-text="Title is private" data-url="https://github.com/symfony/symfony/issues/15002" data-hovercard-type="pull_request" data-hovercard-url="/symfony/symfony/pull/15002/hovercard" href="https://github.com/symfony/symfony/pull/15002">#15002</a>, <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="50033482" data-permission-text="Title is private" data-url="https://github.com/symfony/symfony/issues/12573" data-hovercard-type="issue" data-hovercard-url="/symfony/symfony/issues/12573/hovercard" href="https://github.com/symfony/symfony/issues/12573">#12573</a> and <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="51930840" data-permission-text="Title is private" data-url="https://github.com/symfony/symfony/issues/12977" data-hovercard-type="pull_request" data-hovercard-url="/symfony/symfony/pull/12977/hovercard" href="https://github.com/symfony/symfony/pull/12977">#12977</a> too).</p>
<p dir="auto">I first posted it as a <a href="https://github.com/symfony/symfony/pull/15002#issuecomment-140495438" data-hovercard-type="pull_request" data-hovercard-url="/symfony/symfony/pull/15002/hovercard">comment</a> on <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="88717483" data-permission-text="Title is private" data-url="https://github.com/symfony/symfony/issues/15002" data-hovercard-type="pull_request" data-hovercard-url="/symfony/symfony/pull/15002/hovercard" href="https://github.com/symfony/symfony/pull/15002">#15002</a> . But I think it would be easier to think about it.</p>
<p dir="auto">Plus, since the design will probably requires several PRs, I was thinking of using this issue to centralize them.</p>
<p dir="auto">The basic idea is to add a new interface (and its implementation as a Trait) to allow class Constraints to be aware of the class on which they have been declared. This would then be used to make <code class="notranslate">UniqueEntity</code> aware of the class it's been declared on and use this in its Validator to pull the right Repository.</p>
<p dir="auto">Following here, I provided a detailed analysis of how the design and the fix would impact the current behavior.</p>
<h1 dir="auto">Target aware constraints</h1>
<p dir="auto">I thought we could create a new <code class="notranslate">TargetAwareInterface</code> (in <code class="notranslate">Symfony\Component\Validator</code>). The interface would actually be empty since Constraints options are handled through public properties, and a Trait to implement it easily.<br>
The setter would be called automatically by the Constraint Loaders (<code class="notranslate">if ($constraint instanceof TAI) { $constraint-&gt;target = $className; }</code>). This means minor adjustments to the <code class="notranslate">XmlLoader</code>, <code class="notranslate">YamlLoader</code> and <code class="notranslate">AnnotationLoader</code>. Developers wouldn't need to specify it themselves.</p>
<h1 dir="auto">Discussion</h1>
<p dir="auto">I think this would provide a completely generic approach (any constraint implementing the Interface could get access to its target). This is moreover seamless for the user.</p>
<h2 dir="auto">Backward Compatibiliy</h2>
<p dir="auto">As a transitory method, in case the loader isn't patched yet (or if the <code class="notranslate">StaticMethodLoader</code> is used and the target is not provided). The Validator could fallback to the current behavior and trigger a deprecation notice.</p>
<h2 dir="auto">Flexibility</h2>
<h3 dir="auto">Discussion WRT <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="88717483" data-permission-text="Title is private" data-url="https://github.com/symfony/symfony/issues/15002" data-hovercard-type="pull_request" data-hovercard-url="/symfony/symfony/pull/15002/hovercard" href="https://github.com/symfony/symfony/pull/15002">#15002</a></h3>
<p dir="auto">This design is a bit less flexible than the design of <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="88717483" data-permission-text="Title is private" data-url="https://github.com/symfony/symfony/issues/15002" data-hovercard-type="pull_request" data-hovercard-url="/symfony/symfony/pull/15002/hovercard" href="https://github.com/symfony/symfony/pull/15002">#15002</a>. But, IMHO the additional flexibility provided by the first PR get out of the semantic scope of <code class="notranslate">UniqueEntity</code> :</p>
<ul dir="auto">
<li>The behavior, which could be one of <code class="notranslate">root</code>, <code class="notranslate">current</code>, <code class="notranslate">real</code> with the first solution, is fixed in mine (equivalent to <code class="notranslate">current</code>).</li>
<li>My design doesn't allow to apply the constraint on a Class <code class="notranslate">A</code> while using a Repository for a class <code class="notranslate">B</code>.</li>
</ul>
<h4 dir="auto"><code class="notranslate">current</code> vs <code class="notranslate">real</code> Behaviors</h4>
<p dir="auto">If there are no class inheritance, there is no difference between <code class="notranslate">current</code> and <code class="notranslate">real</code>.</p>
<p dir="auto">If there is any kind of class inheritance, <code class="notranslate">real</code> doesn’t actually work (which is the source of the original bug).</p>
<h4 dir="auto">Separation between Validated Entities and Target Repository</h4>
<p dir="auto">This covers both an explicit <code class="notranslate">repository</code> or the <code class="notranslate">root</code> behavior, which is just a special case of it.</p>
<p dir="auto">Given the <code class="notranslate">Person</code>, <code class="notranslate">Student</code>, <code class="notranslate">Teacher</code> class hierarchy example,</p>
<p dir="auto">I see three possibilities when the constraint is applied on <code class="notranslate">A</code> while using <code class="notranslate">B</code>'s repository:</p>
<ol dir="auto">
<li><code class="notranslate">B</code> is outside the inheritance line (e.g. Sister/Cousin class) of <code class="notranslate">A</code> (e.g., Constraint is on class <code class="notranslate">Student</code>, but <code class="notranslate">repository</code> is <code class="notranslate">Teacher</code>)</li>
<li>B is a Child class of A (e.g., Constraint is on class <code class="notranslate">Person</code>, but <code class="notranslate">repository</code> is <code class="notranslate">Student</code>)</li>
<li>B is a Parent class of A (e.g., Constraint is on class <code class="notranslate">Student</code>, but  <code class="notranslate">repository</code> is <code class="notranslate">Person</code>, includes the <code class="notranslate">root</code> behavior)</li>
</ol>
<p dir="auto">IMO, these are not deep issues, If you feel like a long(er) read, here is a detailed analysis of each case, otherwise, you might want to skip the next couple of paragraphs...<br>
(<strong>TL;DR</strong>: it falls outside the scope of <code class="notranslate">UniqueEntity</code>’s semantic and should be addressed with a separate custom Constraint)</p>
<p dir="auto">In the first case (B is outside the inheritance line of A), it would mean that Students cannot have names used by Teachers, but the other way around wouldn’t be an issue. Additional care is required to ensure Students already existing do not conflict with a new Teacher name. This is a blacklisting semantic. While useful, we could argue this semantic isn’t covered by <code class="notranslate">UniqueEntity</code> and should be implemented using a custom Constraint.</p>
<p dir="auto">In the second case (B is a child class of A), it would mean that Persons cannot have names already used by Students, but names used by Teachers would be OK. This is very similar to the previous case and seems to carry the same Blacklisting semantic. Therefore, it falls outside of <code class="notranslate">UniqueEntity</code>’s semantic and requires a custom Constraint.</p>
<p dir="auto">In the third and last case (B is a child class of A), it would mean that Teachers can have duplicated names, but Students can’t have duplicated names, nor the same name as Teachers. Although the first part of Students’ restrictions seems to be matching the <code class="notranslate">UniqueEntity</code>’s semantic, the second part matches the Blacklisting semantic. So, IMO, it should be a combination of both.</p>
<h3 dir="auto">Multiple Table inheritance</h3>
<p dir="auto">In the case of Multiple Table inheritance, there are a few cases where the current implementation may not crash, since the UNIQUE Index may prove impossible to generate (For example, if the field is represented differently in some subclasses).</p>
<p dir="auto">The current implementation would simply fail to enforce the constraint. It's therefore another bug. Plus the semantic is ambiguous.</p>
<p dir="auto">If what we need is a per-table <code class="notranslate">UniqueEntity</code> behavior (similar to what separate UNIQUE Indices on separate tables would enforce), this should be enforced by using separate <code class="notranslate">UniqueEntity</code> Constraints on each class representing each table.</p>
<p dir="auto">If what we need is an actual cross-table <code class="notranslate">UniqueEntity</code> Constraint, using the Repository of the Root Entity <em>should</em> solve this problem. There might be some restrictions in the Repository and Doctrine themselves that makes it impractical. However, if that is the case, it's a different issue which should be solved by a different PR with a stricter scope and a more complex implementation. It may require a rather extensive MetaData analysis.</p>
<h1 dir="auto">Conclusion</h1>
<p dir="auto">Thanks for taking the time to read all that if you did. It’s rather long and a bit tedious if I may say so myself.</p>
<p dir="auto">The design is implemented through two separated PRs:</p>
<ol dir="auto">
<li>The <code class="notranslate">TargetAwareInterface</code> for class constraints (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="121788118" data-permission-text="Title is private" data-url="https://github.com/symfony/symfony/issues/16978" data-hovercard-type="pull_request" data-hovercard-url="/symfony/symfony/pull/16978/hovercard" href="https://github.com/symfony/symfony/pull/16978">#16978</a>)</li>
<li>The Target Aware <code class="notranslate">UniqueEntity</code> constraint (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="121815347" data-permission-text="Title is private" data-url="https://github.com/symfony/symfony/issues/16981" data-hovercard-type="pull_request" data-hovercard-url="/symfony/symfony/pull/16981/hovercard" href="https://github.com/symfony/symfony/pull/16981">#16981</a>) with the fixed behavior</li>
</ol>
<p dir="auto">The doctrine bundle, the validator and the annotation reader all support caching.</p>
<p dir="auto">The doctrine bundle supports memcache, apc, array and xcache.</p>
<p dir="auto">The annotations reader supports file and any mechanism that implements <code class="notranslate">Doctrine\Common\Cache\Cache</code> by providing a service in the configuration.</p>
<p dir="auto">The validator component only supports apc.</p>
<p dir="auto">It would be great to be able to define some cache providers in the framework configuration and use them from the configuration of those 3 components.<br>
That would require changing the configuration of the doctrine bundle and the annotation reader and implement a ClassMetadataFactory that would use an instance of  <code class="notranslate">Doctrine\Common\Cache\Cache</code>.</p>