Order of how arguments are merged in multiple di.xml-files causes unexpected results #8647
Labels
Fixed in 2.2.x
The issue has been fixed in 2.2 release line
Issue: Clear Description
Gate 2 Passed. Manual verification of the issue description passed
Issue: Confirmed
Gate 3 Passed. Manual verification of the issue completed. Issue is confirmed
Issue: Format is not valid
Gate 1 Failed. Automatic verification of issue format is failed
Issue: Ready for Work
Gate 4. Acknowledged. Issue is added to backlog and ready for development
Reproduced on 2.2.x
The issue has been reproduced on latest 2.2 release
Reproduced on 2.3.x
The issue has been reproduced on latest 2.3 release
Ok, so bear with me on this one. I have to explain some things first because I have no other way on explaining what's this issue is about, although in my opinion it's quite an important one.
How Magento handles constructor arguments
Take a look at the following example about 2 modules, module A and B, that want to add some arguments to an argument in module X:
Module A
Module B
The above 2 configurations both manipulate the argument named
bar
and add their own values to it. The result of this is that when classFoo
of Module X is instantiated, the$bar
-argument in it's constructor call will be:Under the hood
Let me explain some code that's currently in Magento 2: Take a look at the method
\Magento\Framework\ObjectManager\Config\Config::_collectConfiguration()
:This code is (or one of the parts of) responsible for smashing down the various
di.xml
-files scattered around and merge their arguments to use in dependency injection. The magic ingredient in this method that's responsible for this isarray_replace_recursive
.However... This could have some unexpected side-effects.
A theoretical example
Let's take a look about order of preference / order of loading. We all know that we can use the
<sequence>
-tag in ourmodule.xml
to determine the loading order of modules. So let's say we have the following setup:So the order of loading would be:
Module X -> Module A -> Module B
.This means that the order of dependency injection using the
<type>
-tag indi.xml
would follow the same pattern. So when$bar
is set in Module X you would expect it to be:However... It's not! It's actually:
Explanation
Like I said, this is due to how the arrays are merged in
\Magento\Framework\ObjectManager\Config\Config::_collectConfiguration()
. It's this line:array_replace_recursive
is the one with theHello
-message. So the returning array (where$arguments
is set to), only has this array. It's then later on set to$this->_arguments[$type]
.array_replace_recursive
is the on with theWorld
-message. However, due to the nature ofarray_replace_recursive
, the array returned will have this array as it's first key.So in short: loading a module later on will cause it's added values to appear earlier. This is kind of the opposite what you would expect.
A real world example
Not a big deal you might ask. However, this is a big deal if you're trying to do something where the order of the array matters. For example, I've recently was given the task to add a new renderer to the webapi module (your can read more about it here, and more about my implementation here).
By default, the renderers that the webapi module has are
default
,application_json
,text_xml
,application_xml
andapplication_xhtml_xml
. This results in an array with the following keys:Now, I've added my own custom renderer to this list (
text_plain
), but after what I've explained above (and I discovered today, the resulting array was not the above one with my renderer added to the end of it, but instead:Now take a look at
\Magento\Framework\Webapi\Rest\Response\RendererFactory::_getRendererClass()
, and specifically at the part where determined where the renderer is used:If I make an request with
Accept: */*
, this wholeforeach
-loop will eventually find the OR-statement|| $acceptType == '*/*'
and it would just say "Whatever! I'll return my first entry". Now this would be perfect since the webapi sets it's first item asdefault
(which is the JSON renderer by the way), but because I've added a renderer to this list the first result was mytext/plain
-renderer.The result? All API-requests that had the
Accept
-header set to*/*
expected a JSON result, but instead they broke.Conclusion
The way I see it this ticket can go in 2 directions:
default
-renderer whenAccept = */*
. However, this is not the reason of this bug, but more of a result.Perhaps both deserve attention.
Sorry for the long ticket, but I don't know how else I could explain this.
The text was updated successfully, but these errors were encountered: