- Building Forms
- Creating the Controller
- Rendering the Cart Page
- Handling the Form
- Adding a Link to the Cart Page
The cart page will allow the user to manage the products it wants to purchase. The user will be able to:
- Update the quantity of products in the cart,
- Remove products from the cart,
- Clear the cart,
- See the list of products in the cart,
- See the quantity of products in the cart,
- See the summary of the cart.
Building Forms
The CartItemType Form
The CartItemType form will manage the form fields for an OrderItem object of an Order. It will contain the following fields:
- quantity: the number of quantity of the product the customer wants to purchase,
- remove: a submit button to remove the product from the cart.
Use the Maker to generate the form:
$ symfony console make:form CartItemType OrderItem
Customize it to have the fields we need:
<?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 CartItemType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('quantity')
->add('remove', SubmitType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => OrderItem::class
]);
}
}
The CartType Form
The CartType form will be the main form and manage all items in the cart. It will contain the following fields:
-
items: a collection of
CartItemTypeform type. It will allow us to modify allOrderItemitems of anOrderright inside the cart form itself, - save: a submit button to save the cart,
- clear: a submit button to clear the cart.
Use the Maker to generate this class:
$ symfony console make:form CartType Order
Customize it to have the fields we need:
<?php
namespace App\Form;
use App\Entity\Order;
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);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Order::class,
]);
}
}
Creating the Controller
Create the CartController controller via the Maker:
$ symfony console make:controller CartController
The command creates a CartController class under the src/Controller/ directory and a template file to templates/cart/index.html.twig.
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class CartController extends AbstractController
{
/**
* @Route("/cart", name="cart")
*/
public function index(): Response
{
return $this->render('cart/index.html.twig', [
'controller_name' => 'CartController',
]);
}
}
In the CartController controller, implement the index() method:
- Get the current cart using the
CartManagerand, - Create the
CartTypeform with the cart as form data and, - Pass the form view and the cart to the Twig template
cart/index.html.twig
<?php
namespace App\Controller;
use App\Form\CartType;
use App\Manager\CartManager;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* Class CartController
* @package App\Controller
*/
class CartController extends AbstractController
{
/**
* @Route("/cart", name="cart")
*/
public function index(CartManager $cartManager): Response
{
$cart = $cartManager->getCurrentCart();
$form = $this->createForm(CartType::class, $cart);
return $this->render('cart/index.html.twig', [
'cart' => $cart,
'form' => $form->createView()
]);
}
}
Rendering the Cart Page
In the cart/index.html.twig file, add the two-column layout grid below:
{% extends 'base.html.twig' %}
{% block title %}Cart{% endblock %}
{% block body %}
<div class="container mt-4">
<h1>Your Cart</h1>
{% if cart.items.count > 0 %}
<div class="row mt-4">
<!-- List of items -->
<div class="col-md-8"></div>
<!-- Summary -->
<div class="col-md-4"></div>
</div>
{% else %}
<div class="alert alert-info">
Your cart is empty. Go to the <a href="{{ path('home') }}">product list</a>.
</div>
{% endif %}
</div>
{% endblock %}
If the cart is empty, we add a link to the product list.
List of items
In the left column, render the cart form (list of items in the cart) by using the form_start(), form_end(), form_widget() and form_errors() Twig functions:
<div class="col-md-8">
{{ form_start(form) }}
<div class="card">
<div class="card-header bg-dark text-white d-flex">
<h5>Items</h5>
<div class="ml-auto">
{{ form_widget(form.save, {'attr': {'class': 'btn btn-warning'}}) }}
{{ form_widget(form.clear, {'attr': {'class': 'btn btn-light'}}) }}
</div>
</div>
<ul class="list-group list-group-flush">
{% for item in form.items %}
<li class="list-group-item d-flex">
<div class="flex-fill mr-2">
<img src="https://via.placeholder.com/200x150" width="64" alt="Product image">
</div>
<div class="flex-fill mr-2">
<h5 class="mt-0 mb-0">{{ item.vars.data.product.name }}</h5>
<small>{{ item.vars.data.product.description[:50] }}...</small>
<div class="form-inline mt-2">
<div class="form-group mb-0 mr-2">
{{ form_widget(item.quantity, {
'attr': {
'class': 'form-control form-control-sm ' ~ (item.quantity.vars.valid ? '' : 'is-invalid')
}
}) }}
<div class="invalid-feedback">
{{ form_errors(item.quantity) }}
</div>
</div>
{{ form_widget(item.remove, {'attr': {'class': 'btn btn-dark btn-sm'}}) }}
</div>
</div>
<div class="flex-fill mr-2 text-right">
<b>{{ item.vars.data.product.price }} €</b>
</div>
</li>
{% endfor %}
</ul>
</div>
{{ form_end(form, {'render_rest': false}) }}
</div>
As we don't use Javascript, we need to put the Save button at the beginning of the form to avoid submitting another submit button (such as the Delete button) when the customer press Enter. This is because when we have multiple submit buttons and you press Enter, the form will, by default, use the first submit button it finds.
The Cart summary
In the right column, add the cart summary:
<div class="col-md-4">
<div class="card mt-4 mt-md-0">
<h5 class="card-header bg-dark text-white">Summary</h5>
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between">
<div><b>Total</b></div>
<span><b>{{ cart.total }} €</b></span>
</li>
</ul>
<div class="card-body">
<a href="#" class="btn btn-warning w-100">Checkout</a>
</div>
</div>
</div>
You already can see the cart page on http://localhost:8000/cart.
Handling the Form
In the CartController controller, update the index() method and handle the form to map the submitted data to the form data, the cart as Order entity in this case.
<?php
namespace App\Controller;
use App\Form\CartType;
use App\Manager\CartManager;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* Class CartController
* @package App\Controller
*/
class CartController extends AbstractController
{
/**
* @Route("/cart", name="cart")
*/
public function index(CartManager $cartManager, Request $request): Response
{
$cart = $cartManager->getCurrentCart();
$form = $this->createForm(CartType::class, $cart);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$cart->setUpdatedAt(new \DateTime());
$cartManager->save($cart);
return $this->redirectToRoute('cart');
}
return $this->render('cart/index.html.twig', [
'cart' => $cart,
'form' => $form->createView()
]);
}
}
When the form is submitted, we have just to save the cart in session and database and then redirect the customer to the cart page. That's all.
In fact, the Order entity is automatically updated according to the submitted data. The data for the items field is used to construct an ArrayCollection of OrderItem entities. Each of them has been updated with the quantity chosen by the customer. The collection is then set on the items property of the Order.
For the moment, we only manage the edition of items in the cart. We will manage the deletion of each of them and the clearing of the cart later.
Adding a Link to the Cart Page
The customer needs a button to access the cart. Add a Cart button to the navbar in the base layout base.html.twig into the header block:
{% block header %}
<nav class="navbar navbar-dark bg-dark sticky-top">
{# ... #}
<div class="navbar-nav">
<a href="{{ path('cart') }}" class="btn btn-light">
Cart
</a>
</div>
</nav>
{% endblock %}
Now, the navbar contains a button to the cart page.
What's about the Remove and Clear buttons? We will manage them in the next two steps.


Top comments (1)
Good morning:
I am following your article but at the time of testing the cart it reflects this error
Cannot autowire argument $cartManager of "App\Controller\Frontend\CartController::index()": it references class "App\Manager\CartManager" but no such service exists.
I don't know if I forgot something