Just encountered this today. So here is an explanation what happens.
If you want to programatically add something to the cart, you can use the addEntity method of the cart manager. Doing so, will save a new order item. And saving a new order item will trigger a save of the order. And saving the order will trigger order refresh. And triggering the order refresh will process all order processors. For example commerce.availability_checker services, called by by the manager, required by commerce_order.availability_order_processor which is an order processor. phew.
So, to reproduce this issue, consider this simple code (replace those magic numbers with real IDs):
<?php
// Magic numbers that works for me right now.
$store_id = 1;
$variation_id = 551634;
/** @var \Drupal\commerce_cart\CartProviderInterface $cart_manager */
$cart_provider = \Drupal::service('commerce_cart.cart_provider');
/** @var \Drupal\commerce_cart\CartManagerInterface $cart_manager */
$cart_manager = \Drupal::service('commerce_cart.cart_manager');
$variation = \Drupal\commerce_product\Entity\ProductVariation::load($variation_id);
$store = \Drupal\commerce_store\Entity\Store::load($store_id);
$user = \Drupal\user\Entity\User::load(1);
$cart = $cart_provider->getCart('default', $store, $user);
if (!$cart) {
$cart = $cart_provider->createCart('default', $store, $user);
}
$cart_manager->emptyCart($cart);
$cart_manager->addEntity($cart, $variation, 1);
And consider a super simple availability checker like so:
/**
* {@inheritdoc}
*/
public function applies(PurchasableEntityInterface $entity) {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function check(PurchasableEntityInterface $entity, $quantity, Context $context) {
return FALSE;
}
Now try to run the php code for adding the entity, for example by saving it as test.php and invoking it with drush scr test.php
This will now create a cart for user 1, with one item in the cart, which is deleted while adding it. This gives a range of problems on different checkout steps, but ignoring that, it also gives a completely empty cart page, which looks weird :)
$order->hasItems also returns true for this order.
Could be several fixes for this, but that is the gist of it at least.
Comments
Comment #2
eiriksmComment #3
eiriksmComment #4
eiriksmFor now, I solved this by injecting the availability manager, and doing a ::check before I try to add something. I still feel like this is a bug though :p
Comment #5
starlight-sparkle#3088597: Add a constraint to purchased_entity which checks the availability manager makes it possible to call
::validate()on the order item entity after it is created, but there are two ways where this is bypassed and still causes the problem stated in IS to occur.PurchasedEntityAvailableConstraintviolation is filtered out byContentEntityForm::validateForm()CartManager::addEntity(), it will internally callCartManager::createOrderItem()followed byCartManager::addOrderItem(), which doesn't validate the newly created order item before it is saved.