Magento2: Cant't get cart items in block - quote object empty

Created on 30 Aug 2016  路  25Comments  路  Source: magento/magento2

Preconditions

  1. Magento 2.1 CE, php 7.0

Steps to reproduce

  1. In custom module I added block, which should check if provided product is in cart.
  2. Block is added properly, although method doesn't work.
  3. When I try to check if my product is in cart I get info that cart is empty (but in fact it is not). That is my class:

namespace [vendor]\SkinHelper\Block\Catalog\Product;

use \Magento\Catalog\Api\Data\ProductInterface;
use \Magento\Checkout\Model\Session;
use \Magento\Framework\View\Element\Template;
use \Magento\Framework\View\Element\Template\Context;

/** Class provides method to check if given product is in cart
 * @universal
 */
class IsInCart extends Template
{

    /**
     * @var \Magento\Checkout\Model\Session
     */
    private $checkoutSession;



    /**
     * Di.
     * @param \Magento\Checkout\Model\Session $checkoutSession
     * @param \Magento\Framework\View\Element\Template\Context $context
     * @param array $data
     */
    public function __construct(
        Session $checkoutSession,
        Context $context,
        array $data
    )
    {
        parent::__construct($context, $data);

        $this->checkoutSession = $checkoutSession;
    }



    /**
     * Method check if provided product is in cart of current customer.
     * Doesn't work dynamically.
     * TODO: There is a bug actually in magento connected with session and full page cache
     * TODO: More details: https://github.com/magento/magento2/issues/3294
     *
     * @param \Magento\Catalog\Api\Data\ProductInterface $product
     *
     * @return bool $inCart
     */
    public function isInCart(ProductInterface $product)
    {
        $productId = $product->getId();
        $cartItems = $this->checkoutSession->getQuote()->getAllVisibleItems();
        $itemsIds = array();
        foreach ($cartItems as $cartItem) {
            $itemsIds[] = $cartItem->getProduct()->getId();
        }

        return in_array($productId, $itemsIds);
    }


    /**
     * Method counts items now available in cart.
     * TODO: There is a bug actually in magento connected with session and full page cache
     * TODO: More details: https://github.com/magento/magento2/issues/3294
     *
     * @return int $cartDataCount
     */
    public function countItemsInCart()
    {
        $cartItems = $this->checkoutSession->getQuote()->getAllVisibleItems();
        $cartItemsCount = count($cartItems);

        return $cartItemsCount;
    }
  1. That's my call for these methods:

<?php echo $block->isInCart($_product); ?> <?php echo $this->helper([vendor]\SkinHelper\Helper\Test')->isInCart($_product) ?>

As you can see I was trying the same thing with helper (called from helper), but it failed.

  1. I was trying also the same way on \Magento\Checkout\Model\Cart and it also didn't worked.
  2. The strangest thing is that the same logic placed and called as controller (by url) works and returns correct output.

Expected result

  1. Of course, method in block called inside template returns correct result.

Actual result

  1. When I dump $itemsIds it is empty, so no elements can be retrieved. Products are present in minicart, as well as in quote table as well.
  2. Method works in controller but not inside block or helper called from template. What is even more strange the same method in helper called from controller works.
Catalog Format is valid bug report

Most helpful comment

I think problem may lay in cache, especially Full page cache.

All 25 comments

I think problem may lay in cache, especially Full page cache.

@bartek9007 there is a problem in your code

$cartItems = $this->checkoutSession->getQuoteId();
...
foreach ($cartItems as $cartItem) {...

$cartItems never will be an array. This code causes an exception for me. Could you specify the code version which works from controller?

@SerhiyShkolyarenko you are right. I updated my question, I posted chunk of code I have been testing later. Of course, it will cause exception. Now there is a code in question which actually works only when full page cache disabled.

@bartek9007 please also provide DownloadableSampleUrlProviderInterface code for clean experiment.

@SerhiyShkolyarenko
DownloadableSampleUrlProviderInterface defines another method, which is implementing this interface. This method helps to get downloadable sample for provided downloadable product and has nothing to do with IsInCart() method - they are not called together.

I will update it to the code I have now - and it is whole class and also doesn't work.

@bartek9007 I made some minor changes to have your code compiled and injected to controller. Patch with all my changes is attached. Also I disabled page cache to check how it works in debugger.
I ran compilation and the code worked properly: I opened product page twice to check.
The code returned false when cart was empty and the code returned true, when I added product to cart. This check was performed on the latest develop branch. Does it work the same for you?
Github6392.zip

@SerhiyShkolyarenko Controller was just another place to test code.
The code is placed in custom block and this block is injected into a few places, using <referenceBlock> in layout xmls in custom theme. Are you testing on 2.1.1 or on 2.1? I had that bug on 2.1, but not tested yet on 2.1.1.

@bartek9007 that check 5 days ago was performed on latest develop branch. Does the same code in standard Magento themes(Blank or Luma) works properly?

I have checked it on 2.1.1. and with FPC disabled and enabled - checking if product is in cart (using checkout session works)

But with full page cache doesn't work block checking if product is in currently logged in customer wishlist. Should I reopen this one or open new issue?

Let's continue here. Please attach all required changes as a patch and describe all steps to reproduce from clean magento installation to avoid different reproducing flows for you and me.

Hm, I am not sure how to make a patch, so for now I will post a code of block:

namespace [VENDOR]\SkinHelper\Block\Catalog\Product

use \Magento\Catalog\Api\Data\ProductInterface;
use \Magento\Framework\View\Element\Template;
use \Magento\Framework\View\Element\Template\Context;
use \Magento\Wishlist\Helper\Data;


class IsInWishlist extends Template
{



    private $customerSession;

    private $registry;

    private $wishlistHelper;

    public function __construct(
        Context $context,
        Data $wishlistHelper,
        array $data
    )
    {
        parent::__construct($context, $data);

        $this->wishlistHelper = $wishlistHelper;
    }


    private function getCustomerWishlistItemsCollection()
    {
        $itemsCollection = $this->wishlistHelper->getWishlist()->getItemCollection();

        return $itemsCollection;
    }


    public function isInWishlist(ProductInterface $product)
    {
        $productId = $product->getId();

        $itemsCollection = $this->getCustomerWishlistItemsCollection();
        $itemsIds = $itemsCollection->getColumnValues('product_id');

        return in_array($productId, $itemsIds);
    }

and it is added inside catalog_category_view.xml:

<referenceBlock name="category.products.list"> <block class="[VENDOR]\SkinHelper\Block\Catalog\Product\IsInWishlist" name="name_catalog_isinwishlist" cacheable="false"/> </referenceBlock>

Works for me only with cacheable="false", having FPC enabled.
Part of list.phtml:

$IsInWishlist = $block->getChildBlock('name_catalog_isinwishlist');
...

<?php foreach ($_productCollection as $_product): ?> <?php $is_in_wishlist = $IsInWishlist->isInWishlist($_product) ?> <?php echo $is_in_wishlist?>
......
<php? endforeach; ?>
...

tested on clean Magento instance - 2.1.2 and doesn't work if it's not marked as cacheable="false" or FPC is not disabled.

Your $data parameter is required. What is di.xml config for this class?

Which way $_productCollection is assigned?

I haven't added any configuration for this class in di.xml. Should I add something? What is more I haven't encountered error about $data. $productCollection is assigned natively, I haven't changed the behavior. Template is placed in custom theme: Magento_Catalog\templates\product\list.phtml and only changes are provided above.

@bartek9007 could you please create a separate module with that block and corresponding layout update to activate it on category page? Attaching that module as archive would be handy here. I need it to be sure we have the same code changes.
Your block has obligatory parameter $data in constructor and for me Object Manager fails to instantiate it. It means there are some code/config changes which impact your and my Magento instance in a different way.

Test.zip
Ok, here there is module with two block - unfortunately block checking if product is in cart doesn't work. I was mislead by setting another block as not cacheable. I have tested it on Luma, Magento 2.1.2.

Here with FPC enabled, both blocks cachebale:
image

And here after disabling FPC or setting any of block as not cacheable in xml, as they make all page not cacheable:
image

@bartek9007
I unpacked the archive to "app/code" folder, ran ./bin/magento setup:upgrade for enabling new module, removed content of 'var' directory and opened catalog category in browser. I've got and error

1 exception(s):
Exception #0 (Magento\Framework\Config\Dom\ValidationException): Element 'referenceBlock', attribute 'template': The attribute 'template' is not allowed.
Line: 764


Exception #0 (Magento\Framework\Config\Dom\ValidationException): Element 'referenceBlock', attribute 'template': The attribute 'template' is not allowed.
Line: 764

and stack trace.
I found the line in code/Test/Block/view/frontend/layout/catalog_category_view.xml

<referenceBlock name="category.products.list" template="Test_Block::product/list.phtml">

'template' attributes are not allowed for 'referenceBlock' elements in layouts. How do you make that code run? Am I doing anything wrong?

Test.zip
Of course it is my mistake, apollogies! I could have forgotten about setting template for category grid - I was doing it late at friday.

@bartek9007 I added cacheable="false" to your blocks and everything worked fine:

            <block class="Test\Block\Block\IsInCart" name="test.is.in.cart" cacheable="false"/>
            <block class="Test\Block\Block\IsInWishlist" name="test.is.in.wishlist" cacheable="false"/>

Generally, it is a common rule: blocks with personal data have to be not cacheable.
I'm closing the issue, but feel free to leave your comments if you need something to clarify on this topic.

Yes, that is true - they work if I match them as not 'cacheable'. So I understand it's like this by design. The problem in such a situation is that the whole page is not cacheable. Is there any way to omit that? Shouldn't be this designed other way - to allow not cache part of page? Or is it hard/impossible to achieve?

@bartek9007 the general rule is to move private content in browser. When Magento renders public content (should not contain any personal data), it removes everything from session in order to prevent information leaks.

Take a look on this - http://devdocs.magento.com/guides/v2.1/config-guide/cache/cache-priv-priv.html. "Customer data sections" is preferable way to achieve your goal. If you don't want to rewrite your code to JS, you can try deprecated approach - mark your block as private using _isScopePrivate property. This will tell Magento to load content using separate AJAX call. In this case cache will be invalidated on any POST request.

you need to add cacheable="false" in layout xml to access cart quote in custom module

I am using magento 2.1.8. I my custom theme appdesignfrontendSampletheme-frontend-shopMagento_Catalogtemplatesproductviewform.phtml I have done

`$quoteId = $objectManager->get('MagentoCheckoutModelSession')->getQuoteId();
$currentItemCount=0;
if(!empty($quoteId)){
$cartItems = $objectManager->get('MagentoCheckoutModelSession')->getQuote()->getAllVisibleItems();
$itemsIds = array();
foreach ($cartItems as $cartItem) {
$itemsIds[] = $cartItem->getProduct()->getId();
}

$currentItemCount = count($itemsIds);

}

echo $currentItemCount;`

and in my appdesignfrontendSampletheme-frontend-shopMagento_Cataloglayoutcatalog_product_view.xml I have added
<referenceBlock name="product.info.addtocart" class="Magento\Checkout\Block\Cart" before="-" template="Blumeidealnew_shop::product/view/form.phtml" cacheable="false" />

But this is not working properly when full page cache is enabled.

Need help to fix this issue.

Was this page helpful?
0 / 5 - 0 ratings