Quentin Ferrer
Posted on December 21, 2020
- Creating the Entity
- Migrating the Database
- Generating Fake Products
- Creating the Product List Page
- Creating the Product Detail Page
- Adding a Link to the Product Page
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
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;
}
}
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
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');
}
}
We can now ready to update the local database schema:
$ symfony console doctrine:migrations:migrate
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
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();
}
}
Loading Data Fixtures
Load the fixtures by executing this command:
$ symfony console doctrine:fixtures:load
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 templateproducts/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(),
]);
}
}
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 %}
Now, you can see the list of products on the homepage http://localhost:8000.
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
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',
]);
}
}
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,
]);
}
}
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 %}
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.
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>
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.
Posted on December 21, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.