Price calculation fails with a fatal error on zero quantity order items that have adjustments

Created on 1 July 2020, over 4 years ago
Updated 22 December 2024, 30 days ago

Problem/Motivation

When an order item's quantity is programmatically set to zero and there also exists a promotion (or an other adjustment) that applies to this order item, a fatal error occurs:

TypeError: Argument 1 passed to Drupal\commerce_price\Calculator::trim() must be of the type string, null given, called in /web/modules/contrib/commerce/modules/price/src/Calculator.php on line 97 in Drupal\commerce_price\Calculator::trim() (regel 236 van /web/modules/contrib/commerce/modules/price/src/Calculator.php)

This didn't happen in Commerce 8.x-2.18, from which I updated.

Additionally, the following PHP warning is logged:

Warning: bcdiv(): Division by zero in Drupal\commerce_price\Calculator::divide() (regel 96 van /web/modules/contrib/commerce/modules/price/src/Calculator.php)

Backtrace:

#0 /web/core/includes/bootstrap.inc(600): _drupal_error_handler_real(2, 'bcdiv(): Divisi...', '/data/sites/web...', 96, Array)
#1 [internal function]: _drupal_error_handler(2, 'bcdiv(): Divisi...', '/data/sites/web...', 96, Array)
#2 /web/modules/contrib/commerce/modules/price/src/Calculator.php(96): bcdiv('0.00', '0.00', 6)
#3 /web/modules/contrib/commerce/modules/price/src/Price.php(169): Drupal\commerce_price\Calculator::divide('0.00', '0.00')
#4 /web/modules/contrib/commerce/modules/order/src/Entity/OrderItem.php(247): Drupal\commerce_price\Price->divide('0.00')
#5 /web/modules/contrib/commerce/modules/promotion/src/Plugin/Commerce/PromotionOffer/OrderItemFixedAmountOff.php(36): Drupal\commerce_order\Entity\OrderItem->getAdjustedUnitPrice(Array)
#6 /web/modules/contrib/commerce/modules/promotion/src/Entity/Promotion.php(571): Drupal\commerce_promotion\Plugin\Commerce\PromotionOffer\OrderItemFixedAmountOff->apply(Object(Drupal\commerce_order\Entity\OrderItem), Object(Drupal\commerce_promotion\Entity\Promotion))
#7 /web/modules/contrib/commerce/modules/promotion/src/PromotionOrderProcessor.php(55): Drupal\commerce_promotion\Entity\Promotion->apply(Object(Drupal\commerce_order\Entity\Order))
#8 /web/modules/contrib/commerce/modules/order/src/OrderRefresh.php(161): Drupal\commerce_promotion\PromotionOrderProcessor->process(Object(Drupal\commerce_order\Entity\Order))
#9 /web/modules/contrib/commerce/modules/order/src/OrderStorage.php(131): Drupal\commerce_order\OrderRefresh->refresh(Object(Drupal\commerce_order\Entity\Order))
#10 /web/modules/contrib/commerce/modules/order/src/OrderStorage.php(108): Drupal\commerce_order\OrderStorage->doOrderPreSave(Object(Drupal\commerce_order\Entity\Order))
#11 /web/core/lib/Drupal/Core/Entity/EntityStorageBase.php(500): Drupal\commerce_order\OrderStorage->invokeHook('presave', Object(Drupal\commerce_order\Entity\Order))
#12 /web/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php(700): Drupal\Core\Entity\EntityStorageBase->doPreSave(Object(Drupal\commerce_order\Entity\Order))
#13 /web/core/lib/Drupal/Core/Entity/EntityStorageBase.php(454): Drupal\Core\Entity\ContentEntityStorageBase->doPreSave(Object(Drupal\commerce_order\Entity\Order))
#14 /web/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php(837): Drupal\Core\Entity\EntityStorageBase->save(Object(Drupal\commerce_order\Entity\Order))
#15 /web/core/lib/Drupal/Core/Entity/EntityBase.php(395): Drupal\Core\Entity\Sql\SqlContentEntityStorage->save(Object(Drupal\commerce_order\Entity\Order))
#16 /web/modules/contrib/commerce/modules/order/src/OrderStorage.php(164): Drupal\Core\Entity\EntityBase->save()
#17 /web/core/lib/Drupal/Core/Entity/EntityStorageBase.php(307): Drupal\commerce_order\OrderStorage->postLoad(Array)
#18 /web/modules/contrib/commerce/modules/cart/src/CartProvider.php(225): Drupal\Core\Entity\EntityStorageBase->loadMultiple(Array)
#19 /web/modules/contrib/commerce/modules/cart/src/CartProvider.php(177): Drupal\commerce_cart\CartProvider->loadCartData(Object(Drupal\Core\Session\AccountProxy))
#20 /web/modules/contrib/commerce/modules/cart/src/CartProvider.php(165): Drupal\commerce_cart\CartProvider->getCartIds(NULL)
#21 /web/modules/contrib/commerce/modules/cart/src/Plugin/Block/CartBlock.php(221): Drupal\commerce_cart\CartProvider->getCarts()
#22 /web/core/modules/block/src/BlockViewBuilder.php(47): Drupal\commerce_cart\Plugin\Block\CartBlock->getCacheTags()
#23 /web/core/modules/block/src/BlockViewBuilder.php(32): Drupal\block\BlockViewBuilder->viewMultiple(Array, 'full', NULL)
#24 /web/core/modules/block/src/Plugin/DisplayVariant/BlockPageVariant.php(151): Drupal\block\BlockViewBuilder->view(Object(Drupal\block\Entity\Block))
#25 /web/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php(259): Drupal\block\Plugin\DisplayVariant\BlockPageVariant->build()
#26 /web/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php(117): Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object(Symfony\Component\HttpFoundation\Request), Object(Drupal\Core\Routing\CurrentRouteMatch))
#27 /web/core/lib/Drupal/Core/EventSubscriber/MainContentViewSubscriber.php(90): Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object(Symfony\Component\HttpFoundation\Request), Object(Drupal\Core\Routing\CurrentRouteMatch))
#28 [internal function]: Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object(Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent), 'kernel.view', Object(Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher))
#29 /web/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php(111): call_user_func(Array, Object(Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent), 'kernel.view', Object(Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher))
#30 /vendor/symfony/http-kernel/HttpKernel.php(156): Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch('kernel.view', Object(Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent))
#31 /vendor/symfony/http-kernel/HttpKernel.php(68): Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object(Symfony\Component\HttpFoundation\Request), 1)
#32 /web/core/lib/Drupal/Core/StackMiddleware/Session.php(57): Symfony\Component\HttpKernel\HttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#33 /web/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(47): Drupal\Core\StackMiddleware\Session->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#34 /web/core/modules/page_cache/src/StackMiddleware/PageCache.php(106): Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#35 /web/core/modules/page_cache/src/StackMiddleware/PageCache.php(85): Drupal\page_cache\StackMiddleware\PageCache->pass(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#36 /web/core/modules/ban/src/BanMiddleware.php(50): Drupal\page_cache\StackMiddleware\PageCache->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#37 /web/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(47): Drupal\ban\BanMiddleware->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#38 /web/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(52): Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#39 /vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#40 /web/core/lib/Drupal/Core/DrupalKernel.php(708): Stack\StackedHttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#41 /web/index.php(19): Drupal\Core\DrupalKernel->handle(Object(Symfony\Component\HttpFoundation\Request))
#42 {main}

Background / Use case for setting a order item's quantity to zero

I've setup a store where customers can register for events. Providing the registrants happens during checkout. At the checkout step 'Event registration', customers can add, edit and remove registrants for the event. When no registrants are added for an event yet, the order item's quantity should be zero at that point, but not removed from the cart. I'm using my sandbox module Commerce RNG for that.

Proposed resolution

Do not divide the order item's total price when the order item's quantity is zero.

Remaining tasks

User interface changes

API changes

Data model changes

Possibly related issues:

🐛 don't show the 'added to your cart' message if the item quantity is unchanged or 0; make CartEntityAddEvent aware of the original order item Closed: won't fix
🐛 Submitting add to cart form with empty quantity value results in an exception Fixed
#3030247: When last item is removed from cart, shippings and coupons are not removed

Patch will follow.

🐛 Bug report
Status

Needs review

Version

2.40

Component

Price

Created by

🇳🇱Netherlands megachriz

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • 🇩🇪Germany Anybody Porta Westfalica

    Just ran into a similar issue in latest 2.40! This shouldn't happen.
    We're going to have a closer look after the holidays!

    DivisionByZeroError: Division by zero in bcdiv() (Zeile 96 in /web/modules/contrib/commerce/modules/price/src/Calculator.php).

    #0 /web/modules/contrib/commerce/modules/price/src/Calculator.php(96): bcdiv()
    #1 /web/modules/contrib/commerce/modules/price/src/Price.php(169): Drupal\commerce_price\Calculator::divide()
    #2 /web/modules/contrib/commerce/modules/order/src/Entity/OrderItem.php(256): Drupal\commerce_price\Price->divide()
    #3 /web/modules/contrib/google_tag/src/Plugin/GoogleTag/Event/Commerce/AddToCartEvent.php(38): Drupal\commerce_order\Entity\OrderItem->getAdjustedUnitPrice()
    #4 /web/modules/contrib/google_tag/google_tag.module(147): Drupal\google_tag\Plugin\GoogleTag\Event\Commerce\AddToCartEvent->getData()
    #5 /web/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php(311): google_tag_page_attachments()
    #6 /web/core/lib/Drupal/Core/Extension/ModuleHandler.php(395): Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}()
    #7 /web/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php(308): Drupal\Core\Extension\ModuleHandler->invokeAllWith()
    #8 /web/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php(285): Drupal\Core\Render\MainContent\HtmlRenderer->invokePageAttachmentHooks()
    #9 /web/core/lib/Drupal/Core/Render/Renderer.php(638): Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}()
    #10 /web/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php(284): Drupal\Core\Render\Renderer->executeInRenderContext()
    #11 /web/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php(128): Drupal\Core\Render\MainContent\HtmlRenderer->prepare()
    #12 /web/core/lib/Drupal/Core/EventSubscriber/MainContentViewSubscriber.php(90): Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse()
    #13 [internal function]: Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray()
    #14 /web/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php(111): call_user_func()
    #15 /vendor/symfony/http-kernel/HttpKernel.php(186): Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch()
    #16 /vendor/symfony/http-kernel/HttpKernel.php(76): Symfony\Component\HttpKernel\HttpKernel->handleRaw()
    #17 /web/core/lib/Drupal/Core/StackMiddleware/Session.php(53): Symfony\Component\HttpKernel\HttpKernel->handle()
    #18 /web/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\Core\StackMiddleware\Session->handle()
    #19 /web/core/lib/Drupal/Core/StackMiddleware/ContentLength.php(28): Drupal\Core\StackMiddleware\KernelPreHandle->handle()
    #20 /web/core/modules/big_pipe/src/StackMiddleware/ContentLength.php(32): Drupal\Core\StackMiddleware\ContentLength->handle()
    #21 /web/core/modules/page_cache/src/StackMiddleware/PageCache.php(116): Drupal\big_pipe\StackMiddleware\ContentLength->handle()
    #22 /web/core/modules/page_cache/src/StackMiddleware/PageCache.php(90): Drupal\page_cache\StackMiddleware\PageCache->pass()
    #23 /web/core/modules/ban/src/BanMiddleware.php(50): Drupal\page_cache\StackMiddleware\PageCache->handle()
    #24 /web/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\ban\BanMiddleware->handle()
    #25 /web/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle()
    #26 /web/core/lib/Drupal/Core/StackMiddleware/AjaxPageState.php(36): Drupal\Core\StackMiddleware\NegotiationMiddleware->handle()
    #27 /web/modules/contrib/remove_http_headers/src/StackMiddleware/RemoveHttpHeadersMiddleware.php(49): Drupal\Core\StackMiddleware\AjaxPageState->handle()
    #28 /web/core/lib/Drupal/Core/StackMiddleware/StackedHttpKernel.php(51): Drupal\remove_http_headers\StackMiddleware\RemoveHttpHeadersMiddleware->handle()
    #29 /web/core/lib/Drupal/Core/DrupalKernel.php(741): Drupal\Core\StackMiddleware\StackedHttpKernel->handle()
    #30 /web/index.php(19): Drupal\Core\DrupalKernel->handle()
    #31 {main}
  • 🇩🇪Germany Grevil

    The issue from @Anybody and I would be solved in 🐛 Error: Call to a member function getEntityTypeId() on null Active . We wrongly allowed to set a quantity of "0", which resulted in the error.

  • 🇮🇱Israel jsacksick

    @grevil: But the issue linked is a Commerce API issue, not a Commerce one.

  • 🇩🇪Germany Grevil

    @jsacksick Indeed! But we ended up in commerce, since we unintentionally allowed a quantity of "0". The original entry point was "commerce_api".

Production build 0.71.5 2024