Skip to content

Commit

Permalink
OP-291: Refactor AddProductsToCartHandler to handle exceptions in con…
Browse files Browse the repository at this point in the history
…troller
  • Loading branch information
hmfilar committed Jul 24, 2024
1 parent 2d538e5 commit cd5cc44
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 64 deletions.
62 changes: 25 additions & 37 deletions spec/CommandHandler/Wishlist/AddProductsToCartHandlerSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use BitBag\SyliusWishlistPlugin\Command\Wishlist\AddProductsToCartInterface;
use BitBag\SyliusWishlistPlugin\Command\Wishlist\WishlistItemInterface;
use BitBag\SyliusWishlistPlugin\CommandHandler\Wishlist\AddProductsToCartHandler;
use BitBag\SyliusWishlistPlugin\Exception\InsufficientProductStockException;
use BitBag\SyliusWishlistPlugin\Exception\InvalidProductQuantity;
use Doctrine\Common\Collections\ArrayCollection;
use PhpSpec\ObjectBehavior;
use Sylius\Bundle\OrderBundle\Controller\AddToCartCommandInterface;
Expand All @@ -23,23 +25,15 @@
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
use Sylius\Component\Inventory\Checker\AvailabilityCheckerInterface;
use Sylius\Component\Order\Modifier\OrderModifierInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Contracts\Translation\TranslatorInterface;

final class AddProductsToCartHandlerSpec extends ObjectBehavior
{
public function let(
RequestStack $requestStack,
TranslatorInterface $translator,
OrderModifierInterface $orderModifier,
OrderRepositoryInterface $orderRepository,
AvailabilityCheckerInterface $availabilityChecker,
): void {
$this->beConstructedWith(
$requestStack,
$translator,
$orderModifier,
$orderRepository,
$availabilityChecker,
Expand All @@ -61,10 +55,6 @@ public function it_adds_products_from_wishlist_to_cart(
AddToCartCommandInterface $addToCartCommand,
OrderModifierInterface $orderModifier,
OrderRepositoryInterface $orderRepository,
RequestStack $requestStack,
Session $session,
FlashBagInterface $flashBag,
TranslatorInterface $translator,
): void {
$collection = new ArrayCollection([$wishlistProduct->getWrappedObject()]);
$addProductsToCart->getWishlistProducts()->willReturn($collection);
Expand All @@ -80,51 +70,49 @@ public function it_adds_products_from_wishlist_to_cart(

$availabilityChecker->isStockSufficient($productVariant, 1)->willReturn(true);

$requestStack->getSession()->willReturn($session);
$session->getFlashBag()->willReturn($flashBag);
$flashBag->has('success')->willReturn(false);

$translator->trans('bitbag_sylius_wishlist_plugin.ui.added_to_cart')->willReturn('Test translation');
$flashBag->add('success', 'Test translation')->shouldBeCalled();

$this->__invoke($addProductsToCart);
}

public function it_doesnt_add_products_from_wishlist_to_cart_if_stock_is_insufficient(
public function it_throws_exception_when_stock_is_insufficient(
AvailabilityCheckerInterface $availabilityChecker,
ProductVariantInterface $productVariant,
OrderItemInterface $orderItem,
WishlistItemInterface $wishlistProduct,
OrderInterface $order,
AddProductsToCartInterface $addProductsToCart,
AddToCartCommandInterface $addToCartCommand,
OrderModifierInterface $orderModifier,
OrderRepositoryInterface $orderRepository,
RequestStack $requestStack,
Session $session,
FlashBagInterface $flashBag,
TranslatorInterface $translator,
): void {
$collection = new ArrayCollection([$wishlistProduct->getWrappedObject()]);
$addProductsToCart->getWishlistProducts()->willReturn($collection);

$wishlistProduct->getCartItem()->willReturn($addToCartCommand);
$addToCartCommand->getCartItem()->willReturn($orderItem);
$orderItem->getVariant()->willReturn($productVariant);
$orderItem->getQuantity()->willReturn(0);
$addToCartCommand->getCart()->willReturn($order);
$availabilityChecker->isStockSufficient($productVariant, 0)->willReturn(false);
$orderItem->getQuantity()->willReturn(1);

$availabilityChecker->isStockSufficient($productVariant, 1)->willReturn(false);
$orderItem->getProductName()->willReturn('Tested Product');

$orderModifier->addToOrder($order, $orderItem)->shouldNotBeCalled();
$orderRepository->add($order)->shouldNotBeCalled();
$this->shouldThrow(InsufficientProductStockException::class)->during('__invoke', [$addProductsToCart]);
}

$requestStack->getSession()->willReturn($session);
$session->getFlashBag()->willReturn($flashBag);
$translator->trans('Tested Product does not have sufficient stock.')->willReturn('Translation test');
$flashBag->add('error', 'Translation test')->shouldBeCalled();
public function it_throws_exception_when_quantity_is_not_positive(
AvailabilityCheckerInterface $availabilityChecker,
ProductVariantInterface $productVariant,
OrderItemInterface $orderItem,
WishlistItemInterface $wishlistProduct,
AddProductsToCartInterface $addProductsToCart,
AddToCartCommandInterface $addToCartCommand,
): void {
$collection = new ArrayCollection([$wishlistProduct->getWrappedObject()]);
$addProductsToCart->getWishlistProducts()->willReturn($collection);

$this->__invoke($addProductsToCart);
$wishlistProduct->getCartItem()->willReturn($addToCartCommand);
$addToCartCommand->getCartItem()->willReturn($orderItem);
$orderItem->getVariant()->willReturn($productVariant);
$orderItem->getQuantity()->willReturn(0);

$availabilityChecker->isStockSufficient($productVariant, 0)->willReturn(true);

$this->shouldThrow(InvalidProductQuantity::class)->during('__invoke', [$addProductsToCart]);
}
}
34 changes: 10 additions & 24 deletions src/CommandHandler/Wishlist/AddProductsToCartHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,22 @@
use BitBag\SyliusWishlistPlugin\Command\Wishlist\AddProductsToCartInterface;
use BitBag\SyliusWishlistPlugin\Command\Wishlist\WishlistItem;
use BitBag\SyliusWishlistPlugin\Command\Wishlist\WishlistItemInterface;
use BitBag\SyliusWishlistPlugin\Exception\InsufficientProductStockException;
use BitBag\SyliusWishlistPlugin\Exception\InvalidProductQuantity;
use Doctrine\Common\Collections\Collection;
use Sylius\Bundle\OrderBundle\Controller\AddToCartCommandInterface;
use Sylius\Component\Core\Model\OrderItemInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
use Sylius\Component\Inventory\Checker\AvailabilityCheckerInterface;
use Sylius\Component\Order\Modifier\OrderModifierInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Contracts\Translation\TranslatorInterface;

#[AsMessageHandler]
final class AddProductsToCartHandler
{
public function __construct(
private RequestStack $requestStack,
private TranslatorInterface $translator,
private OrderModifierInterface $orderModifier,
private OrderRepositoryInterface $orderRepository,
private ?AvailabilityCheckerInterface $availabilityChecker = null,
Expand Down Expand Up @@ -69,6 +66,9 @@ private function productCanBeProcessed(WishlistItemInterface $wishlistProduct):
return $this->productIsStockSufficient($cartItem) && $this->productHasPositiveQuantity($cartItem);
}

/**
* @throws InsufficientProductStockException
*/
private function productIsStockSufficient(OrderItemInterface $product): bool
{
/** @var ?ProductVariantInterface $variant */
Expand All @@ -86,25 +86,19 @@ private function productIsStockSufficient(OrderItemInterface $product): bool
return true;
}

$message = sprintf('%s does not have sufficient stock.', $product->getProductName());

/** @var Session $session */
$session = $this->requestStack->getSession();
$session->getFlashBag()->add('error', $this->translator->trans($message));

return false;
throw new InsufficientProductStockException((string) $product->getProductName());
}

/**
* @throws InvalidProductQuantity
*/
private function productHasPositiveQuantity(OrderItemInterface $product): bool
{
if (0 < $product->getQuantity()) {
return true;
}
/** @var Session $session */
$session = $this->requestStack->getSession();
$session->getFlashBag()->add('error', $this->translator->trans('bitbag_sylius_wishlist_plugin.ui.increase_quantity'));

return false;
throw new InvalidProductQuantity();
}

private function addProductToWishlist(WishlistItemInterface $wishlistProduct): void
Expand All @@ -121,13 +115,5 @@ private function addProductToWishlist(WishlistItemInterface $wishlistProduct): v

$this->orderModifier->addToOrder($cart, $cartItem);
$this->orderRepository->add($cart);

/** @var Session $session */
$session = $this->requestStack->getSession();
$flashBag = $session->getFlashBag();

if (false === $flashBag->has('success')) {
$flashBag->add('success', $this->translator->trans('bitbag_sylius_wishlist_plugin.ui.added_to_cart'));
}
}
}
31 changes: 30 additions & 1 deletion src/Controller/Action/AddProductsToCartAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,42 @@
namespace BitBag\SyliusWishlistPlugin\Controller\Action;

use BitBag\SyliusWishlistPlugin\Command\Wishlist\AddProductsToCart;
use BitBag\SyliusWishlistPlugin\Exception\InsufficientProductStockException;
use BitBag\SyliusWishlistPlugin\Exception\InvalidProductQuantity;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Messenger\Exception\HandlerFailedException;

final class AddProductsToCartAction extends BaseWishlistProductsAction
{
protected function handleCommand(FormInterface $form): void
{
/** @var Session $session */
$session = $this->requestStack->getSession();
$flashBag = $session->getFlashBag();

$command = new AddProductsToCart($form->get('items')->getData());
$this->messageBus->dispatch($command);

try {
$this->messageBus->dispatch($command);
if (false === $flashBag->has('success')) {
$flashBag->add('success', $this->translator->trans('bitbag_sylius_wishlist_plugin.ui.added_to_cart'));
}
} catch (HandlerFailedException $exception) {
$flashBag->add('error', $this->getExceptionMessage($exception));
}
}

private function getExceptionMessage(HandlerFailedException $exception): string
{
$previous = $exception->getPrevious();
if ($previous instanceof InsufficientProductStockException) {
return $this->translator->trans('bitbag_sylius_wishlist_plugin.ui.insufficient_stock', ['%productName%' => $previous->getProductName()]);
}
if ($previous instanceof InvalidProductQuantity) {
return $this->translator->trans('bitbag_sylius_wishlist_plugin.ui.increase_quantity');
}

return $exception->getMessage();
}
}
25 changes: 25 additions & 0 deletions src/Exception/InsufficientProductStockException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

/*
* This file has been created by developers from BitBag.
* Feel free to contact us once you face any issues or want to start
* You can find more information about us on https://bitbag.io and write us
* an email on hello@bitbag.io.
*/

declare(strict_types=1);

namespace BitBag\SyliusWishlistPlugin\Exception;

final class InsufficientProductStockException extends \Exception
{
public function __construct(private string $productName)
{
parent::__construct();
}

public function getProductName(): string
{
return $this->productName;
}
}
16 changes: 16 additions & 0 deletions src/Exception/InvalidProductQuantity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

/*
* This file has been created by developers from BitBag.
* Feel free to contact us once you face any issues or want to start
* You can find more information about us on https://bitbag.io and write us
* an email on hello@bitbag.io.
*/

declare(strict_types=1);

namespace BitBag\SyliusWishlistPlugin\Exception;

final class InvalidProductQuantity extends \Exception
{
}
2 changes: 0 additions & 2 deletions src/Resources/config/services/message_handler.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

<services>
<service id="bitbag_sylius_wishlist_plugin.command_handler.wishlist.add_products_to_wishlist_handler" class="BitBag\SyliusWishlistPlugin\CommandHandler\Wishlist\AddProductsToCartHandler">
<argument type="service" id="request_stack"/>
<argument type="service" id="translator"/>
<argument type="service" id="sylius.order_modifier"/>
<argument type="service" id="sylius.repository.order"/>
<argument type="service" id="sylius.availability_checker.default"/>
Expand Down
1 change: 1 addition & 0 deletions src/Resources/translations/messages.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ bitbag_sylius_wishlist_plugin:
added_to_cart: Wishlist items have been added to your cart.
save_wishlist: Save wishlist
variant_not_unique: You already have this variant on your wishlist
insufficient_stock: '%productName% does not have sufficient stock.'
increase_quantity: Increase the quantity of at least one item.
select_products: Select at least one item.
added_selected_wishlist_items_to_cart: Selected wishlist items have been added to your cart.
Expand Down
1 change: 1 addition & 0 deletions src/Resources/translations/messages.pl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ bitbag_sylius_wishlist_plugin:
added_to_cart: Produkty z listy życzeń zostały dodane do koszyka.
save_wishlist: Zapisz listę życzeń
variant_not_unique: Ten wariant jest już na Twojej liście życzeń
insufficient_stock: '%productName% nie ma wystarczającego stanu magazynowego.'
increase_quantity: Zwiększ ilość przynajmniej jednego z produktów.
select_products: Wybierz przynajmniej jeden produkt.
added_selected_wishlist_items_to_cart: Wybrane produkty zostały dodane do koszyka.
Expand Down

0 comments on commit cd5cc44

Please sign in to comment.