<p dir="auto">In one application I am having a second twig environment where I only use a different loader, to be precise a string loader for some advance rendering. In the second environment I want to be able to leverage the same options to have access to the same settings as in the main environment.</p>
<p dir="auto">Therefore I had this service definition:</p>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="    app.core.twig_string_loader:
        class:        &quot;Twig_Loader_String&quot;

    app.core.twig_string:
        class:        &quot;%twig.class%&quot;
        arguments:    [@app.core.twig_string_loader, %twig.options%]"><pre class="notranslate">    app.core.twig_string_loader:
        <span class="pl-k">class</span>:        "<span class="pl-s">Twig_Loader_String</span>"

    app.core.twig_string:
        <span class="pl-k">class</span>:        "<span class="pl-s">%twig.class%</span>"
        arguments:    [@app.core.twig_string_loader, %twig.options%]</pre></div>
<p dir="auto">Actually it seems that many were using this parameter in their application. A GitHub search shows 3.855 occurences for the term "%twig.options%".</p>
<p dir="auto">I understand as per <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="54976062" data-permission-text="Title is private" data-url="https://github.com/symfony/symfony/issues/13476" data-hovercard-type="pull_request" data-hovercard-url="/symfony/symfony/pull/13476/hovercard" href="https://github.com/symfony/symfony/pull/13476">#13476</a> that there is no BC promise on parameters, however as it looks like there were many using that parameter at least we should state a recommended upgrade path in the upgrade documentation.</p>
<p dir="auto">Related 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></p>
<p dir="auto">I encountered kind of the same issue today.</p>
<p dir="auto">Here is the inheritance scheme of my use case:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="FOSUser
 └─ User
     ├─ Customer
     └─ Society"><pre class="notranslate"><code class="notranslate">FOSUser
 └─ User
     ├─ Customer
     └─ Society
</code></pre></div>
<p dir="auto">The <code class="notranslate">BaseUser</code>, handled by <code class="notranslate">FOSUserBundle</code>, defines an <code class="notranslate">UniqueEntity</code> constraint on both <code class="notranslate">emailCanonical</code> and <code class="notranslate">usernameCanonical</code> fields. But this constraint use the entity repository depending of the real child type of the user I want to register (i.e <code class="notranslate">CustomerRepository</code> when trying to register a new <code class="notranslate">Customer</code>).</p>
<p dir="auto">Therefore, trying to register a new <code class="notranslate">Customer</code> with the same email or username as a <code class="notranslate">Society</code> will not fail the validation, but fails at database level.</p>
<p dir="auto">Given the fact that a new <code class="notranslate">repositoryMethod</code> option was added in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="5713336" data-permission-text="Title is private" data-url="https://github.com/symfony/symfony/issues/4979" data-hovercard-type="pull_request" data-hovercard-url="/symfony/symfony/pull/4979/hovercard" href="https://github.com/symfony/symfony/pull/4979">#4979</a>, an easy workaround is to create a dedicated method in both Customer and Society repositories, calling internally the <code class="notranslate">UserRepository</code>, as for example:</p>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="# CustomerRepository | SocietyRepository

    /**
     * @param array $criteria
     * @return array
     */
    public function findByInRootUser(array $criteria)
    {
        return $this-&gt;_em-&gt;getRepository('ACMEUserBundle:User')
            -&gt;findBy($criteria);
    }"><pre class="notranslate"><span class="pl-c"># CustomerRepository | SocietyRepository</span>

    <span class="pl-c">/**</span>
<span class="pl-c">     * @param array $criteria</span>
<span class="pl-c">     * @return array</span>
<span class="pl-c">     */</span>
    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">findByInRootUser</span>(<span class="pl-smi">array</span> <span class="pl-s1"><span class="pl-c1">$</span>criteria</span>)
    {
        <span class="pl-k">return</span> <span class="pl-s1"><span class="pl-c1">$</span><span class="pl-smi">this</span></span>-&gt;<span class="pl-c1">_em</span>-&gt;<span class="pl-en">getRepository</span>(<span class="pl-s">'ACMEUserBundle:User'</span>)
            -&gt;<span class="pl-en">findBy</span>(<span class="pl-s1"><span class="pl-c1">$</span>criteria</span>);
    }</pre></div>
<p dir="auto">and use it in <code class="notranslate">UniqueEntity</code> constraint definition in both entities :</p>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="#ACME\UserBundle\Entity\Customer
/**
 * @ORM\Entity(repositoryClass=&quot;ACME\UserBundle\Entity\Repositories\CustomerRepository&quot;)
 *
 * @UniqueEntity(fields=&quot;usernameCanonical&quot;, repositoryMethod=&quot;findByInRootUser&quot;,
 * message=&quot;fos_user.username.already_used&quot;, errorPath=&quot;username&quot;, groups={&quot;Registration&quot;, &quot;Profile&quot;})
 * @UniqueEntity(fields=&quot;emailCanonical&quot;, repositoryMethod=&quot;findByInRootUser&quot;,
 * message=&quot;fos_user.email.already_used&quot;, errorPath=&quot;email&quot;, groups={&quot;Registration&quot;, &quot;Profile&quot;})
 *
 */
class Customer extends User
{
}

#ACME\UserBundle\Entity\Society
/**
 * @ORM\Entity(repositoryClass=&quot;ACME\UserBundle\Entity\Repositories\SocietyRepository&quot;)
 *
 * @UniqueEntity(fields=&quot;usernameCanonical&quot;, repositoryMethod=&quot;findByInRootUser&quot;,
 * message=&quot;fos_user.username.already_used&quot;, errorPath=&quot;username&quot;, groups={&quot;Registration&quot;, &quot;Profile&quot;})
 * @UniqueEntity(fields=&quot;emailCanonical&quot;, repositoryMethod=&quot;findByInRootUser&quot;,
 * message=&quot;fos_user.email.already_used&quot;, errorPath=&quot;email&quot;, groups={&quot;Registration&quot;, &quot;Profile&quot;})
 *
 */
class Society extends User
{
}
"><pre class="notranslate"><span class="pl-c">#ACME\UserBundle\Entity\Customer</span>
<span class="pl-c">/**</span>
<span class="pl-c"> * @ORM\Entity(repositoryClass="ACME\UserBundle\Entity\Repositories\CustomerRepository")</span>
<span class="pl-c"> *</span>
<span class="pl-c"> * @UniqueEntity(fields="usernameCanonical", repositoryMethod="findByInRootUser",</span>
<span class="pl-c"> * message="fos_user.username.already_used", errorPath="username", groups={"Registration", "Profile"})</span>
<span class="pl-c"> * @UniqueEntity(fields="emailCanonical", repositoryMethod="findByInRootUser",</span>
<span class="pl-c"> * message="fos_user.email.already_used", errorPath="email", groups={"Registration", "Profile"})</span>
<span class="pl-c"> *</span>
<span class="pl-c"> */</span>
<span class="pl-k">class</span> <span class="pl-v">Customer</span> <span class="pl-k">extends</span> <span class="pl-v">User</span>
{
}

<span class="pl-c">#ACME\UserBundle\Entity\Society</span>
<span class="pl-c">/**</span>
<span class="pl-c"> * @ORM\Entity(repositoryClass="ACME\UserBundle\Entity\Repositories\SocietyRepository")</span>
<span class="pl-c"> *</span>
<span class="pl-c"> * @UniqueEntity(fields="usernameCanonical", repositoryMethod="findByInRootUser",</span>
<span class="pl-c"> * message="fos_user.username.already_used", errorPath="username", groups={"Registration", "Profile"})</span>
<span class="pl-c"> * @UniqueEntity(fields="emailCanonical", repositoryMethod="findByInRootUser",</span>
<span class="pl-c"> * message="fos_user.email.already_used", errorPath="email", groups={"Registration", "Profile"})</span>
<span class="pl-c"> *</span>
<span class="pl-c"> */</span>
<span class="pl-k">class</span> <span class="pl-v">Society</span> <span class="pl-k">extends</span> <span class="pl-v">User</span>
{
}</pre></div>
<p dir="auto">Although it works, this induces useless code duplication (beside the fact that the FOSUser defined constraints are useless, but triggered, because we cannot overload the <code class="notranslate">validation/orm.xml</code>. But that is another story :) )<br>
I certainly could have define the <code class="notranslate">UniqueEntity</code> constraint into my own <code class="notranslate">User</code> class. But then, when adding a new user type, I must not forget to create the <code class="notranslate">findByInRootUser</code> method into the new repository (or create a repository class to extend).</p>
<p dir="auto">Here comes my suggestion:<br>
Add a <code class="notranslate">repository</code> option to the <code class="notranslate">UniqueEntity</code> validator.</p>
<p dir="auto">This will ensure the proper repository will be used by the constraint, and I could define it only at the <code class="notranslate">User</code> entity level:</p>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="#ACME\UserBundle\Entity\User
/**
 * @ORM\Entity(repositoryClass=&quot;ACME\UserBundle\Entity\Repositories\UserRepository&quot;)
 *
 * @UniqueEntity(fields=&quot;usernameCanonical&quot;, repository=&quot;ACME\UserBundle\Entity\Repositories\UserRepository&quot;,
 * message=&quot;fos_user.username.already_used&quot;, errorPath=&quot;username&quot;, groups={&quot;Registration&quot;, &quot;Profile&quot;})
 * @UniqueEntity(fields=&quot;emailCanonical&quot;, repository=&quot;ACME\UserBundle\Entity\Repositories\UserRepository&quot;,
 * message=&quot;fos_user.email.already_used&quot;, errorPath=&quot;email&quot;, groups={&quot;Registration&quot;, &quot;Profile&quot;})
 *
 */
class User extends FOSUser
{
}"><pre class="notranslate"><span class="pl-c">#ACME\UserBundle\Entity\User</span>
<span class="pl-c">/**</span>
<span class="pl-c"> * @ORM\Entity(repositoryClass="ACME\UserBundle\Entity\Repositories\UserRepository")</span>
<span class="pl-c"> *</span>
<span class="pl-c"> * @UniqueEntity(fields="usernameCanonical", repository="ACME\UserBundle\Entity\Repositories\UserRepository",</span>
<span class="pl-c"> * message="fos_user.username.already_used", errorPath="username", groups={"Registration", "Profile"})</span>
<span class="pl-c"> * @UniqueEntity(fields="emailCanonical", repository="ACME\UserBundle\Entity\Repositories\UserRepository",</span>
<span class="pl-c"> * message="fos_user.email.already_used", errorPath="email", groups={"Registration", "Profile"})</span>
<span class="pl-c"> *</span>
<span class="pl-c"> */</span>
<span class="pl-k">class</span> <span class="pl-v">User</span> <span class="pl-k">extends</span> <span class="pl-v">FOSUser</span>
{
}</pre></div>
<p dir="auto">If a value for the <code class="notranslate">repository</code> option isn't provided, the behavior remains the same: The <code class="notranslate">UniqueConstraintValidator</code> will get the repository from the real class at runtime.<br>
Do you see any issue with that approach ?</p>
<p dir="auto">I'm not sure of the component  ability to guess the class on which the constraint was defined, but if it is possible, it could not be considered, because of the BC promise. That's why a suggested another approach.<br>
Maybe for the 3.0 ? But it might lead to more unwanted behaviors than benefits.</p>