Managing Products | Building a Shopping Cart with Symfony

qferrer

Quentin Ferrer

Posted on December 21, 2020

Managing Products | Building a Shopping Cart with Symfony

A shopping cart without products does not make sense. In this part, to simplify the project, we will load fake products instead of managing them from an admin backend. We will also create a product list page and a product details page to allow users to add products to the cart.

We will not manage product stocks.

Creating the Entity

A Product can be described with a few properties:

  • Name
  • Description
  • Price

Use the Maker bundle to generate a class that represents a Product:

$ symfony console make:entity Product
Enter fullscreen mode Exit fullscreen mode

The command is interactive, let's add the fields we need:

  • name, string, 255, no
  • description, text, no
  • price, float, no

A Product entity has been stored under the App\Entity namespace and a ProductRepository repository under the App\Repository.

<?php

namespace App\Entity;

use App\Repository\ProductRepository;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass=ProductRepository::class)
 */
class Product
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $name;

    /**
     * @ORM\Column(type="text")
     */
    private $description;

    /**
     * @ORM\Column(type="float")
     */
    private $price;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getDescription(): ?string
    {
        return $this->description;
    }

    public function setDescription(string $description): self
    {
        $this->description = $description;

        return $this;
    }

    public function getPrice(): ?float
    {
        return $this->price;
    }

    public function setPrice(float $price): self
    {
        $this->price = $price;

        return $this;
    }
}
Enter fullscreen mode Exit fullscreen mode

Migrating the Database

For now, the database is empty. We need to create the database tables related to the entity.

One more time, use the Maker bundle to generate a migration file:

$ symfony console make:migration
Enter fullscreen mode Exit fullscreen mode

A migration file has been stored under the migrations/ directory.

<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
 * Auto-generated Migration: Please modify to your needs!
 */
final class Version20201215122946 extends AbstractMigration
{
    public function getDescription() : string
    {
        return '';
    }

    public function up(Schema $schema) : void
    {
        // this up() migration is auto-generated, please modify it to your needs
        $this->addSql('CREATE TABLE product (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, description LONGTEXT NOT NULL, price DOUBLE PRECISION NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
    }

    public function down(Schema $schema) : void
    {
        // this down() migration is auto-generated, please modify it to your needs
        $this->addSql('DROP TABLE product');
    }
}
Enter fullscreen mode Exit fullscreen mode

We can now ready to update the local database schema:

$ symfony console doctrine:migrations:migrate
Enter fullscreen mode Exit fullscreen mode

Generating Fake Products

Creating the Product Fixtures

To add some fake products to your database, create a ProductFixtures class via the Maker bundle:

$ symfony console make:fixtures ProductFixtures
Enter fullscreen mode Exit fullscreen mode

In the load() method, add ten products with fake data:

<?php

namespace App\DataFixtures;

use App\Entity\Product;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;

/**
 * Class ProductFixtures
 */
class ProductFixtures extends Fixture
{
    public function load(ObjectManager $manager): void
    {
        for ($i = 1; $i <= 10; $i++) {
            $product = new Product();
            $product
                ->setName('Product ' . $i)
                ->setDescription('Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua')
                ->setPrice(mt_rand(10, 600));

            $manager->persist($product);
        }

        $manager->flush();
    }
}
Enter fullscreen mode Exit fullscreen mode

Loading Data Fixtures

Load the fixtures by executing this command:

$ symfony console doctrine:fixtures:load
Enter fullscreen mode Exit fullscreen mode

Creating the Products List Page

The list of products page will be the homepage.

Updating the Home Controller

In the HomeController controller, implement the index() method:

  • Fetch all products by using the ProductRepository and pass them to the Twig template products/index.html.twig.
<?php

namespace App\Controller;

use App\Repository\ProductRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class HomeController extends AbstractController
{
    /**
     * @Route("/", name="home")
     */
    public function index(ProductRepository $productRepository): Response
    {
        return $this->render('home/index.html.twig', [
            'products' => $productRepository->findAll(),
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

Rendering the Product List Page

The last step is to update the templates/home/index.html.twig file:

{% extends 'base.html.twig' %}

{% block body %}
<div class="container">
    <h1 class="mt-3 mb-4">Products</h1>
    <div class="row">
        {% for product in products %}
            <div class="col-md-4">
                <div class="card mb-4">
                    <img src="https://via.placeholder.com/200x150" alt="{{ product.name }}" class="card-img-top">
                    <div class="card-body">
                        <h5 class="card-title">{{ product.name }}</h5>
                        <p class="card-text">{{ product.description }}</p>
                        <div class="d-flex justify-content-between align-item-center">
                            <a href="#" class="btn btn-dark">View details</a>
                            <span class="h5 mt-auto">{{ product.price }}</span>
                        </div>
                    </div>
                </div>
            </div>
        {% endfor %}
    </div>
</div>
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

Now, you can see the list of products on the homepage http://localhost:8000.

Alt Text

Creating the Product Detail Page

The detail page will be used to view the details of a product and allow a user to add one or more items to the cart.

Creating the Product Controller

Create the ProductController controller via the Maker bundle:

$ symfony console make:controller ProductController
Enter fullscreen mode Exit fullscreen mode

The command creates a ProductController class under the src/Controller/ directory and a template file to templates/product/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 ProductController extends AbstractController
{
    /**
     * @Route("/product", name="product")
     */
    public function index(): Response
    {
        return $this->render('product/index.html.twig', [
            'controller_name' => 'ProductController',
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

Rename the index() method with a detail() method in the ProductController and implement it:

  • Define a route with a product ID parameter,
  • Get the product and pass it to the Twig template product/detail.html.twig.
<?php

namespace App\Controller;

use App\Entity\Product;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class ProductController extends AbstractController
{
    /**
     * @Route("/product/{id}", name="product.detail")
     */
    public function index(Product $product): Response
    {
        return $this->render('product/detail.html.twig', [
            'product' => $product,
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

Thanks to the ParamConverters, Symfony will automatically query for the Product entity and pass it as an argument to the controller. It will also show a 404 page if no entity can be found.

Rendering the Product Detail Page

Rename the template product/index.html.twig file with product/detail.html.twig and design the product page:

{% extends 'base.html.twig' %}

{% block title %}{{ product.name }}{% endblock %}

{% block body %}
    <div class="container">
        <div class="row mt-3 mb-4">
            <div class="col-md-4">
                <img src="https://via.placeholder.com/600x400" alt="{{ product.name }}" class="img-fluid">
            </div>
            <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 class="mt-4 p-4 bg-light" method="post">
                    <div class="form-group">
                        <label for="quantity">Quantity</label>
                        <input type="number" class="form-control" id="quantity">
                    </div>
                    <button class="btn btn-warning">Add to Cart</button>
                </form>
            </div>
        </div>
    </div>
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

Now you can see the detail of the products. For example, to see the Product 1, go to the page http://localhost:8000/product/1.

Alt Text

Note that the form will be handled later.

Adding a Link to the Product Page

Updating the Home Page

The View details button gets nowhere. Open the template home/index.html.twig and replace the hyperlink with the route to the product page:

<a href="{{ path('product.detail', {id: product.id}) }}" class="btn btn-dark">
    View details
</a>
Enter fullscreen mode Exit fullscreen mode

It's done! Go to the homepage and click on the View details button of a product and take a look at your product page.

Let's manage the cart to the next step.

💖 💪 🙅 🚩
qferrer
Quentin Ferrer

Posted on December 21, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related