0

I'm trying to add to cart multiple simple products with different custom options at once. So far, I've overridden the checkout/add controller and replaced the line

$this->cart->addProduct($product, $params) // around line 125

with following code:


foreach($multipleItems as $productId = $productData) {
    $product = $this->_initProduct($productId);
    $newParams = $params;
    $newParams['product'] = $productId;
    $newParams['qty'] = $productData['qty'];
    $newParams['options'] = $productData['options'];
    $this->cart->addProduct($product, $newParams);
}

With this code I am able to add the first product correctly with its options but the subsequent ones are added without the custom options.

Does anyone know how can I add all my simple products with their different custom options?

3
  • Please add data of $newParams (for each item) to question, maybe this will help to answer. Commented Jun 14, 2022 at 13:39
  • Where does '$params' get set? Commented Jun 14, 2022 at 17:46
  • @MagePalExtensions hi, $params is simply magento standard parameters request that you get on every action by calling $this->getRequest()->getParams(). I did not included because it's magento standard array with form values (uenc, form_key, etc)
    – Lou Nik
    Commented Jun 16, 2022 at 5:59

2 Answers 2

0

Try

namespace MagePal\CustomCart\Helper;
 
use Magento\Framework\App\Helper\AbstractHelper;
use Magento\Framework\App\Helper\Context;
use Magento\Checkout\Model\Cart;
use Magento\Catalog\Model\ProductFactory;
 
class Data extends AbstractHelper
{
    private $cart;
    private $productFactory;
    
    public function __construct(Context $context, Cart $cart, ProductFactory $productFactory)
    {
 
        $this->productFactory = $productFactory;
        $this->cart = $cart;
        parent::__construct($context);
 
    }
 
    public function getAddCustomProduct($productId, $qty, $option)
    {
        $product = $this->productFactory->load($productId);
 
        $cart = $this->cart;
 
        $params = [
            'qty' => $qty
            'product' => $productId
        ];
 
        $params['options'] = $options;
        $cart->addProduct($product, $params);
        $cart->save();
    }

    public function addItems() 
    {
        foreach($multipleItems as $productId = $productData) {
            $this->getAddCustomProduct($productId, $productData['qty'], $productData['options']);
        }
    }


}
0

I finally did it. In case somebody else needs it, I'll explain here what I did.

What puzzles me is that magento could already have this feature but for some reason that I cannot understand they decided not to include it. Basically:

if you check the file Magento\Checkout\Controller\Cart\Add::execute(), you can see that magento gets the parameters from the request (around line 105), checks and normalizes the quantity that has to be added to the cart (around line 108), then does 3 things:

  • loads the product using the id passed from the request ($product = $this->_initProduct(); around line 119)
  • adds this product to cart ($this->cart->addProduct($product, $params); around line 126)
  • then (and this is the important part) checks the value of the related_product parameter in the request and, if that parameter is not empty, then adds these products to the cart at the same time as the main product ($this->cart->addProductsByIds(explode(',', $related)); around line 131)

This third step, unfortunately passes only the id of the products, not the options, but it's easily fixable because I, just for testing purposes, changed the Magento\Checkout\Model\Cart::addProductsByIds() to receive indeed the options:

    $request = $this->getQtyRequest($product); // this method gets the parameters from the request and return the qty only
    try {
       $request['options'] = $this->getOptionsFromRequest(); // I added this line to add again options to the request. This is similar to the getQtyRequest() but returns options instead of qty
       $this->getQuote()->addProduct($product, $request);
    } catch (\Exception $e) {
       $allAdded = false;
    }

And it works: the options are added to the related_item.

You don't even have to temper with the $options ($this->getRequest()->getParam('options')) because they come as an array of option_title_id => option_value_id, meaning that the option will be passed to the cart only ti the product that has that option id assigned. So let's say I have two products:

  • product A with an option with id 1
  • product B with an option with id 2

I add product A as a normal product by passing to the cart the product id and the quantity, then I pass product B as a related_item and use my changed addProductsByIds() to pass the options ({1:4, 2:7} where 1 and 2 are the id of the option while 4 and 7 are id of the value of that option):

    $params = $this->getRequest()->getParams();
    $newParams = ['uenc' => $params['uenc'], 'form_key' => $params['form_key'], 'qty' => $params['qty'], 'options' => $params['options'], 'product' => $productAid, 'item' => $productAid, 'related_product' => $productBid];
    $product = $this->_initProduct(); // this gets product id from $params['product'] and load the product
    $this->cart->addProduct($product, $params); // this $params contains all the options but because productA has only one option with id 1, only the option with id 1 will be passed to the cart for this product
   $this->cart->addProductsByIds([$productBid], $params['options']); // I added the $options. It is not really necessary as addProductsByIds() calls parameters directly from the request but this is just a test so I wanted to pass options quickly. Despite passing all the options here, because product B contains only one option with id 2, then only the option with id 2 will be saved on this product when it's added to cart

This code works as expected because in the cart, I can see my two products:

  • product A with option 1 assigned
  • product B with option 2 assigned

Honestly, I don't know why magento is making such a fuss to add multiple products with options when they already wrote the code, they just needed to implement it.

Anyway, at this point, if you simply need to add the options to the related_items, then you'd better intercept the addProductsByIds() public method and call the options there. As I said, this method already calls the request to fetch the quantity, so you can just simply call the options as well.

Unfortunately, I had to do a lot more on the request before actually add products to cart: because I had to modify the controller file anyway, it seemed redundant to me to change both files, so I left the addProductsByIds() as it is and only changed the controller using an aroundExecute plugin:

  • Vendor\Module\etc\frontend\di.xml:
<type name="Magento\Checkout\Controller\Cart\Add">
        <plugin name="vendor_module_checkout_cart_add_plugin" type="Vendor\Module\Plugin\ControllerCartAddPlugin" sortOrder="10" disabled="false" />
    </type>
  • Vendor\Module\Plugin\ControllerCartAddPlugin.php:
... // here is a bunch of code that is irrelevant to this example

$newParams = [
 'uenc'                         => $request->getParam('uenc'),
 'form_key'                     => $request->getParam('form_key'),
 'selected_configurable_option' => $request->getParam('selected_configurable_option'),
 'related_product'              => $request->getParam('related_product'),
 'product'                      => $mainProductId,
 'item'                         => $mainProductId,
 'options'                      => $options,
 'qty'                          => $mainQty,
];

// adding the first product
$product = $this->getProductById($mainProductId);
$this->cart->addProduct($product, $newParams);

// adding second product
$relatedParams = $newParams;
$relatedParams['product'] = $secondPorductId;
$relatedParams['qty'] = $secondQty;
$subject->getRequest()->setParams($relatedParams);

... // here is a bunch of code that is irrelevant to this example

return $proceed();

So basically what I did is:

  • load the first product and add it to cart
  • set parameters for second product so that the original method ($proceed()) will add second product to cart when called.

I try to avoid around methods as much as I can but this time I couldn't find any other way as I needed to intercept the method before and after its execution.

I don't know it there are better ways to do it, suggestions are always welcome.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.