DEV Community

Cover image for Adding Products to the Cart | Building a Shopping Cart with Symfony
Quentin Ferrer
Quentin Ferrer

Posted on

Adding Products to the Cart | Building a Shopping Cart with Symfony


In this step, we will handle the form on the product detail page:

Capture d’écran 2020-12-21 à 10.39.06

Building a AddToCartType form

Use the Maker bundle to generate a form class:

$ symfony console make:form AddToCartType OrderItem
Enter fullscreen mode Exit fullscreen mode

The AddToCartType class defines a form for the OrderItem entity. Customize it to have the following fields:

  • quantity: the number of quantity of products the user wants to purchase,
  • add: the submit button to add the product to the cart.
<?php

namespace App\Form;

use App\Entity\OrderItem;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class AddToCartType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('quantity');
        $builder->add('add', SubmitType::class, [
            'label' => 'Add to cart'
        ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => OrderItem::class,
        ]);
    }
}

Enter fullscreen mode Exit fullscreen mode

The form fields are bound to the model. The submitted data will be automatically mapped to the model class properties.

Displaying the Form

To display the form to the user, create the form in the ProductController controller using the FormFactory factory and pass it to the template:

<?php
namespace App\Controller;
use App\Entity\Product;
use App\Form\AddToCartType;
use App\Repository\ProductRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

/**
 * Class ProductController
 * @package App\Controller
 */
class ProductController extends AbstractController
{
    /**
     * @Route("/product/{id}", name="product.detail")
     */
    public function detail(Product $product)
    {
        $form = $this->createForm(AddToCartType::class);

        return $this->render('product/detail.html.twig', [
            'product' => $product,
            'form' => $form->createView()
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

In the template product/detail.html.twig, display the form by using the form Twig function:

<div class="col-md-8">
    <h1 class="mt-4 mt-md-0">{{ product.name }}</h1>
    <h2>{{ product.price }}</h2>
    <hr>
    <b>Description: </b>{{ product.description }}
    {{ form_start(form, {'attr': {'class': 'mt-4 p-4 bg-light'}}) }}
    <div class="form-group">
        {{ form_label(form.quantity) }}
        {{ form_widget(form.quantity, {
            'attr': {
                'class': 'form-control ' ~ (form.quantity.vars.valid ? '' : 'is-invalid')
            }
        }) }}
        <div class="invalid-feedback">
            {{ form_errors(form.quantity) }}
        </div>
    </div>
    {{ form_widget(form.add, {'attr': {'class': 'btn btn-warning w-25'}}) }}
    {{ form_end(form) }}
</div>
Enter fullscreen mode Exit fullscreen mode

Validating the Model

We need to add some validation constraints on the OrderItem model to ensure that the submitted quantity is:

  • not empty and,
  • greater than or equal to 1.
<?php

namespace App\Entity;

use App\Repository\OrderItemRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass=OrderItemRepository::class)
 */
class OrderItem
{
    // ...

    /**
     * @ORM\Column(type="integer")
     * @Assert\NotBlank()
     * @Assert\GreaterThanOrEqual(1)
     */
    private $quantity;

    // ...
}
Enter fullscreen mode Exit fullscreen mode

Handling the Form

Now, we can handle the form submission and add the product to the cart by using the CartManager manager.

<?php

namespace App\Controller;

use App\Entity\Product;
use App\Form\AddToCartType;
use App\Manager\CartManager;
use App\Repository\ProductRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

/**
 * Class ProductController
 * @package App\Controller
 */
class ProductController extends AbstractController
{
    // ...

    /**
     * @Route("/product/{id}", name="product.detail")
     */
    public function detail(Product $product, Request $request, CartManager $cartManager)
    {
        $form = $this->createForm(AddToCartType::class);

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $item = $form->getData();
            $item->setProduct($product);

            $cart = $cartManager->getCurrentCart();
            $cart
                ->addItem($item)
                ->setUpdatedAt(new \DateTime());

            $cartManager->save($cart);

            return $this->redirectToRoute('product.detail', ['id' => $product->getId()]);
        }

        return $this->render('product/detail.html.twig', [
            'product' => $product,
            'form' => $form->createView()
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

When the form is submitted, the OrderItem object is updated according to the submitted data. We retrieve it by using the Form::getData() method and we don't forget to link the product to the OrderItem object. Then, we add the item to the current cart and persist it in the session and database by using the CartManager::save() method. Finally, we redirect the user to the product page.

If the form is not valid, we display the page with the form containing the submitted values and error messages.

For the moment, the user can add products to the cart but cannot review their cart because the page does not yet exist. Let's create the cart page in the next step.

Top comments (4)

Collapse
 
mrgamingfrench profile image
SHINZO WO SASAGEYO

I think there is a problem here

The productManager is called without arguments, thus throwing an exception when trying to acces 'product.detail' route.

The exception is :

Type error: Too few arguments to function App\Manager\CartManager::__construct(), 0 passed in C:\Projets Symfony\projetValentin\var\cache\dev\ContainerEq4lpi1\getServiceLocator_Frm8o4fService.php on line 9 and exactly 3 expected

Collapse
 
qferrer profile image
Quentin Ferrer

Hello. I use the autowiring of Symfony, so I don't need to manage service dependencies.

Autowiring allows you to manage services in the container with minimal configuration. It reads the type-hints on your constructor (or other methods) and automatically passes the correct services to each method.

To enable it, make sure that your config/services.yaml file configuration is like that:

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.

    App\:
        resource: '../src/'
        exclude:
            - '../src/DependencyInjection/'
            - '../src/Entity/'
            - '../src/Kernel.php'
            - '../src/Tests/'
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mrgamingfrench profile image
SHINZO WO SASAGEYO

Of course !

How did I not think about that before ?

Been doing symfony for 4 years now and still forgetting basics lol .

Thx for the help. Much appreciated !

Collapse
 
sc8 profile image
SC

Hey Quentin, I'm really enjoying your tutorial!

I encountered a question while working on creating a form for the "OrderItem" entity. In the "AddToCartType.php" file, my form structure looked like this:

$builder
    ->add('quantity')
    ->add('product')
    ->add('orderRef')
;
Enter fullscreen mode Exit fullscreen mode

However, I ran into some errors and later realized that your implementation of the "buildForm" function is different:

public function buildForm(FormBuilderInterface $builder, array $options): void
{
    $builder->add('quantity');
    $builder->add('add', SubmitType::class, [
        'label' => 'Add to cart'
    ]);
}
Enter fullscreen mode Exit fullscreen mode

I'm curious why the structure is different in your tutorial. Did you manually modify this function?