<p dir="auto">This change made in version 3.4.19 by <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/nicolas-grekas/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/nicolas-grekas">@nicolas-grekas</a> created bug breaking few forms in our project.</p>
<p dir="auto"><a class="commit-link" href="https://github.com/symfony/property-access/commit/15c463a508a2fefde439fd4291fd9140f4cdc5c5#diff-5ef851f935a573b3b27e674ac708651b">symfony/property-access@<tt>15c463a</tt>#diff-5ef851f935a573b3b27e674ac708651b</a></p>
<p dir="auto">If model/entity contains 2 methods <code class="notranslate">addX</code> and <code class="notranslate">removeX</code> everything is ok. But if model/entity contains all 3 methods (<code class="notranslate">addX</code>, <code class="notranslate">removeX</code> and <code class="notranslate">setX</code>) 'set' method will be used instead of 'add' as it is higher in <code class="notranslate">if/elseif</code> hierarchy.<br>
Before change (3.4.18) script was determining if model/entity has add/remove methods before entering <code class="notranslate">if/elseif</code> section.</p>
<p dir="auto"><strong>Deleted code:</strong></p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="        if (\is_array($value) || $value instanceof \Traversable) {
            $methods = $this-&gt;findAdderAndRemover($reflClass, $singulars);
             if (null !== $methods) {
                $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_ADDER_AND_REMOVER;
                $access[self::ACCESS_ADDER] = $methods[0];
                $access[self::ACCESS_REMOVER] = $methods[1];
            }
        }"><pre class="notranslate"><code class="notranslate">        if (\is_array($value) || $value instanceof \Traversable) {
            $methods = $this-&gt;findAdderAndRemover($reflClass, $singulars);
             if (null !== $methods) {
                $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_ADDER_AND_REMOVER;
                $access[self::ACCESS_ADDER] = $methods[0];
                $access[self::ACCESS_REMOVER] = $methods[1];
            }
        }
</code></pre></div>
<p dir="auto">Because of lack of this code first accessor found is <code class="notranslate">set</code> instead of <code class="notranslate">add</code>. This is breaking backward compatibility and forms request handling behavior since forever.</p>
<p dir="auto">Unit tests don't provide check for such situation. Probably this is the reason why such error crept in.</p>
<p dir="auto">With regards.</p>
<p dir="auto"><strong>Symfony version(s) affected</strong>: 4.1.8</p>
<p dir="auto"><strong>Description</strong><br>
<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="373431057" data-permission-text="Title is private" data-url="https://github.com/symfony/symfony/issues/28966" data-hovercard-type="pull_request" data-hovercard-url="/symfony/symfony/pull/28966/hovercard" href="https://github.com/symfony/symfony/pull/28966">#28966</a> fixing an issue where a model has 2 properties, the 2nd property name being the plural form of the first.</p>
<p dir="auto">The fix introduced a change in behaviour when a model has a collection property, that has a setter <strong>and</strong> an adder+remover defined.</p>
<div class="highlight highlight-text-html-php notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="class Foo
{
    private $invoices = [];

    public function setInvoices(array $invoices): void
    {
        //...
    }

    public function addInvoice(Invoice $invoice): void
    {
        //...
    }

    public function removeInvoice(Invoice $item): void
    {
        //...
    }
}"><pre class="notranslate"><span class="pl-k">class</span> <span class="pl-v">Foo</span>
{
    <span class="pl-k">private</span> <span class="pl-c1"><span class="pl-c1">$</span>invoices</span> = [];

    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">setInvoices</span>(<span class="pl-smi">array</span> <span class="pl-s1"><span class="pl-c1">$</span>invoices</span>): <span class="pl-smi">void</span>
    {
        <span class="pl-c">//...</span>
    }

    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">addInvoice</span>(<span class="pl-smi"><span class="pl-smi">Invoice</span></span> <span class="pl-s1"><span class="pl-c1">$</span>invoice</span>): <span class="pl-smi">void</span>
    {
        <span class="pl-c">//...</span>
    }

    <span class="pl-k">public</span> <span class="pl-k">function</span> <span class="pl-en">removeInvoice</span>(<span class="pl-smi"><span class="pl-smi">Invoice</span></span> <span class="pl-s1"><span class="pl-c1">$</span>item</span>): <span class="pl-smi">void</span>
    {
        <span class="pl-c">//...</span>
    }
}</pre></div>
<p dir="auto">If setting the invoices collection:</p>
<ul dir="auto">
<li>before <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="373431057" data-permission-text="Title is private" data-url="https://github.com/symfony/symfony/issues/28966" data-hovercard-type="pull_request" data-hovercard-url="/symfony/symfony/pull/28966/hovercard" href="https://github.com/symfony/symfony/pull/28966">#28966</a>, the adder/remover is used over the setter.</li>
<li>after <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="373431057" data-permission-text="Title is private" data-url="https://github.com/symfony/symfony/issues/28966" data-hovercard-type="pull_request" data-hovercard-url="/symfony/symfony/pull/28966/hovercard" href="https://github.com/symfony/symfony/pull/28966">#28966</a>, the setter is used over the added/remover.</li>
</ul>
<p dir="auto">It could be said that if your model is depending on an order here, your model probably has a bug.<br>
In fact, the change of behaviour did indeed highlighted a bug in my model, so I'm grateful for that!</p>
<p dir="auto">Regardless, is this an intended, acceptable behaviour change for collection properties?</p>