DEV Community

Cover image for Removing Products from the Cart | Building a Shopping Cart with Symfony
Quentin Ferrer
Quentin Ferrer

Posted on

Removing Products from the Cart | Building a Shopping Cart with Symfony


So far, the Remove button has no effect. As we have multiple submit buttons and the default button is Save, we need to check if a Remove button has been clicked and reorder the cart by removing the product the user wants to remove from the cart.

To modify the form data (the Order entity in our case) based on the submitted data, the best practice is to use Form Events. To do that, we need to create an Event Subscriber class, subscribe to the FormEvents::POST_SUBMIT event, and reorder the cart.

Creating the Event Subscriber

Create a RemoveCartItemListener subscriber and subscribe to the FormEvents::POST_SUBMIT event:

<?php

namespace App\Form\EventListener;

use App\Entity\Order;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;

/**
 * Class RemoveCartItemListener
 * @package App\Form\EventListener
 */
class RemoveCartItemListener implements EventSubscriberInterface
{
    /**
     * @inheritDoc
     */
    public static function getSubscribedEvents(): array
    {
        return [FormEvents::POST_SUBMIT => 'postSubmit'];
    }

    /**
     * Removes items from the cart based on the data sent from the user.
     *
     * @param FormEvent $event
     */
    public function postSubmit(FormEvent $event): void
    {
        $form = $event->getForm();
        $cart = $form->getData();

        if (!$cart instanceof Order) {
            return;
        }

        // Removes items from the cart
        foreach ($form->get('items')->all() as $child) {
            if ($child->get('remove')->isClicked()) {
                $cart->removeItem($child->getData());
                break;
            }
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

The postSubmit() method will be invoked after the form is submitted. This will allow us to manipulate the form data mapped with the submitted data.

The current cart is available from the main form by using the FormEvent::getData() method. It should be an instance of an Order entity.

For each item field, we check if a Remove button has been clicked by the user with the button's isClicked() method. If so, we get the OrderItem entity by using the form field's getData() method and remove it from the current cart.

Now, we need to register this event subscriber to the CartType form.

Registering the Event Subscriber

To register the RemoveCartItemListener subscriber to the CartType form, use the addEventListener method of the builder in the form type class:

<?php

namespace App\Form;

use App\Entity\Order;
use App\Form\EventListener\RemoveCartItemListener;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class CartType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('items', CollectionType::class, [
                'entry_type' => CartItemType::class
            ])
            ->add('save', SubmitType::class)
            ->add('clear', SubmitType::class);

        $builder->addEventSubscriber(new RemoveCartItemListener());
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Order::class,
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

That's all. Nothing to do in the CartController because we added the logic in the form. Now you are familiar with the form events, let's do the same to clear the cart.

Top comments (8)

Collapse
 
isabellebengrine profile image
Isabelle Bengrine

Are you sure this works? I am only a beginner but if I am not mistaken, this does not modify the cart saved in the database, but shouldn't it? The remove button for the cart items is not working at all. And the clear button for the total cart removes the order items but as it does not remove it from the session or from the database, if I click on the cart again, I will find the cart as it was... I do appreciate your efforts in putting together this tutorial bit I would be very grateful if you could let me know if I am missing something here or if I am right? Thanks in advance!

Collapse
 
qferrer profile image
Quentin Ferrer

Hi Isabelle ! Yes I'm sure that it works. If you want to test, you can install the repository that contains all the code about this tutorial: github.com/qferr/happy-shop. Could you drop your code to take a look? Thanks!

Collapse
 
isabellebengrine profile image
Isabelle Bengrine

Hi Quentin! Thanks a lot for your quick reply! I am looking at the repository and working on my code to make it work! I hope I will find what is wrong with it! Thanks again for all the time you spent working on this tutorial (and sorry I asked you if this worked !)

Collapse
 
isabellebengrine profile image
Isabelle Bengrine

Hi Quentin! I am sorry to bother you again but actually, I am wondering how your database gets updated when I see no 'persist' or 'flush' anywhere? In my code, the cart page gets wiped out when I hit remove (for one item) or clear (for all items at once), but the cart is actually still the same in the database and I think this is why I get the cart back when I hit the cart button; but in your project, the cart is really updated and that is what I also want to do; so how come the database is modified just by clicking on the buttons? did I miss a step that may be so basic for you that you did not mention it perhaps (as I said, I am a beginner so it is totally possible)? Again, thanks in advance!

Thread Thread
 
qferrer profile image
Quentin Ferrer

Hello Isabelle. Let me explain how does cart clearing work:

  1. When the user click on the clear button, the form is submitted and the CartController::index() method is invoked thanks to the Symfony routing.
  2. We get the current cart, using the CartManager manager:
    • If the cart is already associated with the user in the session, we use it,
    • If the cart does not exist in the session, a new cart is created using the CartFactory factory and becomes the current cart. Note that it will be associated with the user in the session after being persisted in the database (see CartManager::save() method).
  3. The current cart is binded to the form using the FormFactory factory and then the submitted data are merged into the Order object thanks to the Form::handleRequest() method. Next, the form triggers a post submit event to let you manipulate the form data, the current cart in this case.
  4. As we have registered a ClearCartListener listener to the Cart Form, the ClearCartListener::postSubmit() method is invoked. So, we clear the cart using the form data (the current cart by reference) if the clear button has been clicked.
  5. At this time, the cart is not yet saved in the database so let's go back to the controller. If the form is submitted and valid, we save the cart modifications using the CartManager::save() method.
  6. Finally, we redirect the user to the cart page.

Let me know if you need more information.

Have a good day.

Thread Thread
 
isabellebengrine profile image
Isabelle Bengrine • Edited

Thanks for this reply! of course, I realized later that the persist and flush parts are in the save method of the cartmanager! this was silly of me to ask about that... now if you are curious, I finally found what was preventing the remove and clear buttons to work properly: (oddly this did not prevent your project from working just fine, whereas mine could not): in the twig file we had : form_end(form, {'render_rest': false})
and I found on stackoverflow some comments about a similar issue to mine that said:
"But to submit it separately you must ensure that you're building the "complete" child form in your view" and looking at the forms, I thought we are displaying all the fields anyway, so why do we need this? so I removed the ", {'render_rest': false}" part and now it works!!!
I still do not quite understand why I had some trouble and you did not but this just shows I have so much to learn!!! anyway, again thanks for your help and your patience!!! I love your tutorials!!!

Collapse
 
simschabs profile image
Simon Chabrier

Hello there is a problem because no csrf token was present in your form. So we need to add {{ form_row(form._token) }} in the form twig template.

if there are several products and one is deleted then it doesn't work without that.

Your project returns the same csrf token missing error which also represents a security flaw.

After some adaptations in particular of the removeItem method to keep a constant state between the session and the database everything works fine. Thank you for your work.

Collapse
 
parth1601 profile image
parth1601 • Edited

Hii ... I got an issue regarding removing items from the cart... such that iteams will not remove from cart when we clicked on remove button ... can you help me ...?