Description
Preconditions
- Magento CE 2.1.0
- PHP 7.0.9
- MariaDB 10.0.25
- A lot of time to setup everything properly ;)
Steps to reproduce
-
Install Magento CE 2.1.0 using composer
-
Go to the backend
-
Create 2 simple products: 'Product 1' & 'Product 2', set a name, a price, make sure they have a possitive qty and are in stock and make sure you can see them on the frontend
-
Create 2 configurable products: 'Product 3' & 'Product 4', set a name, a price, add some configurations (with the color attribute for example)
-
Add 'Product 1' & 'Product 2' as upsells to the products 'Product 3' & 'Product 4'
-
Change the 'Stock Status' of 'Product 3' to 'Out of Stock'
-
Run
bin/magento indexer:reindex
-
Run
bin/magento cache:flush
-
Verify you can see 'Product 3' (http://example.com/product-3.html) and 'Product 4' (http://example.com/product-4.html) with their upsells
-
Create a custom frontend theme which inherits from 'Magento/blank': 'My/Theme'
-
Create a file:
app/design/frontend/My/Theme/Magento_Catalog/layout/catalog_product_view.xml
with this contents:<?xml version="1.0"?> <page layout="1column" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <move element="product.info.upsell" destination="product.info.form.options" before="-" /> </body> </page>
-
Go in the backend to Content => Design => Configuration
-
Change the Global theme to 'My Theme'
-
Run
bin/magento cache:flush
-
Refresh the 'Product 3' page (the page crashes)
-
Run
bin/magento cache:disable full_page
-
Refresh the 'Product 3' page (it works)
-
Run
bin/magento cache:enable full_page
-
Refresh the 'Product 3' page (it crashes again)
-
Go to 'Product 4' page (this one does work all the time)
Expected result
The page shouldn't crash with the full page cache enabled
Actual result
In step 15 and 19 we get this error:
Call to a member function getItems() on null in vendor/magento/module-catalog/Block/Product/ProductList/Upsell.php on line 146
Call Stack
# Time Memory Function Location
1 0.0003 372832 {main}( ) .../index.php:0
2 0.1488 12366368 Magento\Framework\App\Bootstrap->run( ) .../index.php:37
3 0.1496 12405744 Magento\Framework\App\Http->launch( ) .../Bootstrap.php:258
4 1.0836 55857816 Magento\Framework\View\Result\Page\Interceptor->renderResult( ) .../Http.php:139
5 1.0836 55858192 Magento\Framework\View\Result\Page\Interceptor->___callPlugins( ) .../Interceptor.php:130
6 1.0838 55861536 Magento\PageCache\Model\Controller\Result\BuiltinPlugin->aroundRenderResult( ) .../Interceptor.php:142
7 1.0838 55861536 Magento\Framework\View\Result\Page\Interceptor->Magento\Framework\Interception\{closure}( ) .../BuiltinPlugin.php:67
8 1.0838 55861912 Magento\Framework\Interception\Chain\Chain->invokeNext( ) .../Interceptor.php:138
9 1.0840 55865304 Magento\PageCache\Model\Controller\Result\VarnishPlugin->aroundRenderResult( ) .../Chain.php:67
10 1.0840 55865304 Magento\Framework\Interception\Chain\Chain->Magento\Framework\Interception\Chain\{closure}( ) .../VarnishPlugin.php:74
11 1.0840 55865680 Magento\Framework\Interception\Chain\Chain->invokeNext( ) .../Chain.php:63
12 1.0840 55865720 Magento\Framework\View\Result\Page\Interceptor->___callParent( ) .../Chain.php:70
13 1.0840 55865720 Magento\Framework\View\Result\Layout->renderResult( ) .../Interceptor.php:74
14 1.0840 55865720 Magento\Framework\View\Result\Page->render( ) .../Layout.php:164
15 1.1605 56954088 Magento\Framework\View\Layout\Interceptor->getOutput( ) .../Page.php:243
16 1.1605 56954144 Magento\Framework\View\Layout\Interceptor->___callPlugins( ) .../Interceptor.php:494
17 1.5502 61762680 Magento\PageCache\Model\Layout\LayoutPlugin->afterGetOutput( ) .../Interceptor.php:152
18 1.5972 62349280 Magento\Catalog\Block\Product\ProductList\Upsell\Interceptor->getIdentities( ) .../LayoutPlugin.php:71
19 1.5972 62349280 Magento\Catalog\Block\Product\ProductList\Upsell->getIdentities( ) .../Interceptor.php:141
20 1.5972 62349336 Magento\Catalog\Block\Product\ProductList\Upsell\Interceptor->getItems( ) .../Upsell.php:238
21 1.5972 62349336 Magento\Catalog\Block\Product\ProductList\Upsell->getItems( ) .../Interceptor.php:37
Discussion
Although my use case is not very clever (because the related products don't show up with the full_page cache disabled on 'Product 3'), I still believe this is an actual bug.
What I think happens (not debugged properly): 'Product 3' is out of stock, so the block product.info.form.options
doesn't get rendered because it makes no sense to show the options (right?).
This probably causes the Magento\Catalog\Block\Product\ProductList\Upsell::_beforeToHtml
to not being called, while the Magento\Catalog\Block\Product\ProductList\Upsell::getIdentities
does get called, and that method assumes the _beforeToHtml
method gets called first
The same thing happens with Related products btw, so not only Upsells.
So no idea what the actual bug is, either:
- the block
product.info.form.options
should get rendered - the method
getIdentities
shouldn't get called when a block isn't rendered - the method
getIdentities
should return an empty array when the block isn't rendered - something else...
My biggest issue with this is that we have a different behavior between fpc enabled or not, the result should be the same, and in this case, it isn't.
Thanks!
(thanks to my colleague @koenner01 for helping me debug this thing!)
Related ticket: #3362