Een winkelmantje slaat tijdelijk gegevens op terwijl de gebruiker door de webshop bladert. Symfony gebruikt sessions om data per gebruiker te bewaren tussen pagina-aanvragen. Je bouwt een CartSessionStorage service die de winkelmandje-logica beheert, en koppelt dit aan een checkout die de bestelling in de database opslaat.
📐 Architectuur van het winkelmantje
1 Sessions in Symfony
Een session slaat gegevens op die aan één gebruiker zijn gekoppeld — ook als die meerdere pagina-aanvragen doet. Symfony gebruikt RequestStack om bij de huidige sessie te komen.
use Symfony\Component\HttpFoundation\RequestStack;
class MijnService
{
private $session;
public function __construct(RequestStack $requestStack)
{
// Haal de huidige sessie op
$this->session = $requestStack->getSession();
}
public function opslaanInSessie(): void
{
// Sla een waarde op in de sessie
$this->session->set('naam', 'Jan');
$this->session->set('winkelmandje', [1 => 2, 3 => 1]); // product_id => aantal
}
public function lezenUitSessie(): mixed
{
// Lees een waarde uit de sessie (met standaardwaarde)
return $this->session->get('naam', 'onbekend');
}
public function verwijderenUitSessie(): void
{
// Verwijder een sleutel uit de sessie
$this->session->remove('naam');
}
}
set('key', $val)
Waarde opslaan
get('key', default)
Waarde ophalen
remove('key')
Waarde verwijderen
2 Fixtures: testdata laden
Fixtures zijn PHP-klassen die testdata in de database laden. Handig voor ontwikkeling: je hoeft niet telkens handmatig producten aan te maken.
# Installeer de fixtures-bundle
composer require --dev orm-fixtures
# Laad de fixtures (wist bestaande data!)
php bin/console doctrine:fixtures:load
<?php
namespace App\DataFixtures;
use App\Entity\Product;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
class ProductFixtures extends Fixture
{
public function load(ObjectManager $manager): void
{
// Maak 5 testproducten aan
$producten = [
['naam' => 'Laptop Pro', 'prijs' => 999.99, 'beschrijving' => 'Krachtige laptop voor ontwikkelaars'],
['naam' => 'Muis Wireless', 'prijs' => 29.95, 'beschrijving' => 'Ergonomische draadloze muis'],
['naam' => 'Toetsenbord', 'prijs' => 79.00, 'beschrijving' => 'Mechanisch toetsenbord'],
['naam' => 'Monitor 27"', 'prijs' => 349.00, 'beschrijving' => '4K UHD monitor'],
['naam' => 'USB-C Hub', 'prijs' => 49.95, 'beschrijving' => '7-in-1 USB-C hub'],
];
foreach ($producten as $data) {
$product = new Product();
$product->setNaam($data['naam']);
$product->setPrijs($data['prijs']);
$product->setBeschrijving($data['beschrijving']);
$manager->persist($product);
}
$manager->flush();
}
}
doctrine:fixtures:load wist standaard alle bestaande data. Gebruik --append om te voorkomen dat bestaande data wordt verwijderd.
3 CartSessionStorage service
Je bouwt een service die alle winkelmantje-logica bevat. Een service is een herbruikbare PHP-klasse die je via dependency injection in controllers en andere services kunt gebruiken. Symfony maakt de service automatisch beschikbaar via autowiring.
<?php
namespace App\Service;
use App\Entity\Product;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class CartSessionStorage
{
private const CART_KEY = 'shopping_cart';
private $session;
private EntityManagerInterface $em;
public function __construct(RequestStack $requestStack, EntityManagerInterface $em)
{
$this->session = $requestStack->getSession();
$this->em = $em;
}
// Voeg een product toe aan het winkelmantje
public function addProductToCart(int $productId): void
{
$cart = $this->getCart();
// Als product al in cart: verhoog het aantal
$cart[$productId] = ($cart[$productId] ?? 0) + 1;
$this->session->set(self::CART_KEY, $cart);
}
// Verwijder een product uit het winkelmantje
public function removeProductFromCart(int $productId): void
{
$cart = $this->getCart();
unset($cart[$productId]);
$this->session->set(self::CART_KEY, $cart);
}
// Geef het winkelmantje terug als [Product => aantal] array
public function getShoppingCart(): array
{
$result = [];
foreach ($this->getCart() as $productId => $aantal) {
$product = $this->em->getRepository(Product::class)->find($productId);
if ($product) {
$result[] = ['product' => $product, 'aantal' => $aantal];
}
}
return $result;
}
// Aantal items in het winkelmantje
public function getNumberOfProductsInCart(): int
{
return array_sum($this->getCart());
}
// Totaalprijs berekenen
public function getTotalPrice(): float
{
$total = 0;
foreach ($this->getCart() as $productId => $aantal) {
$product = $this->em->getRepository(Product::class)->find($productId);
if ($product) {
$total += $product->getPrijs() * $aantal;
}
}
return $total;
}
// Winkelmantje leegmaken
public function clearShoppingCart(): void
{
$this->session->remove(self::CART_KEY);
}
private function getCart(): array
{
return $this->session->get(self::CART_KEY, []);
}
}
src/Service/ automatisch als service. Je hoeft niets in services.yaml te configureren. Voeg de service gewoon toe als parameter in de constructor van een controller.
4 CartController — winkelmantje tonen en beheren
De CartController gebruikt de CartSessionStorage service om het winkelmantje te tonen en producten toe te voegen of te verwijderen.
<?php
namespace App\Controller;
use App\Service\CartSessionStorage;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class CartController extends AbstractController
{
// Symfony injecteert CartSessionStorage automatisch via autowiring
public function __construct(private CartSessionStorage $cart) {}
#[Route('/winkelmandje', name: 'cart_index')]
public function index(): Response
{
return $this->render('cart/index.html.twig', [
'items' => $this->cart->getShoppingCart(),
'totaal' => $this->cart->getTotalPrice(),
'aantal' => $this->cart->getNumberOfProductsInCart(),
]);
}
#[Route('/winkelmandje/toevoegen/{id}', name: 'cart_add')]
public function add(int $id): Response
{
$this->cart->addProductToCart($id);
$this->addFlash('success', 'Product toegevoegd aan het winkelmandje!');
return $this->redirectToRoute('cart_index');
}
#[Route('/winkelmandje/verwijderen/{id}', name: 'cart_remove')]
public function remove(int $id): Response
{
$this->cart->removeProductFromCart($id);
return $this->redirectToRoute('cart_index');
}
}
{% for item in items %}
<div class="product-rij">
<span>{{ item.product.naam }}</span>
<span>{{ item.aantal }}x</span>
<span>€ {{ (item.product.prijs * item.aantal)|number_format(2, ',', '.') }}</span>
<a href="{{ path('cart_remove', {id: item.product.id}) }}">Verwijder</a>
</div>
{% else %}
<p>Je winkelmandje is leeg.</p>
{% endfor %}
<p><strong>Totaal: € {{ totaal|number_format(2, ',', '.') }}</strong></p>
<a href="{{ path('cart_checkout') }}" class="btn btn-primary">Afrekenen</a>
5 Entities: Purchase en OrderLine
Om een bestelling permanent op te slaan heb je twee entities nodig die een OneToMany relatie hebben: één Purchase (de bestelling) heeft meerdere OrderLines (één per product).
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Purchase
{
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
private int $id;
#[ORM\Column(type: 'datetime')]
private \DateTime $datum;
#[ORM\Column(type: 'float')]
private float $totaalprijs;
// Eén bestelling heeft meerdere orderregels
#[ORM\OneToMany(targetEntity: OrderLine::class, mappedBy: 'purchase', cascade: ['persist'])]
private Collection $orderLines;
public function __construct()
{
$this->datum = new \DateTime();
$this->orderLines = new ArrayCollection();
}
public function addOrderLine(OrderLine $line): void
{
$this->orderLines->add($line);
$line->setPurchase($this);
}
// ... getters en setters
}
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class OrderLine
{
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
private int $id;
// Elke orderregel verwijst naar één product
#[ORM\ManyToOne(targetEntity: Product::class)]
private Product $product;
// Elke orderregel hoort bij één bestelling
#[ORM\ManyToOne(targetEntity: Purchase::class, inversedBy: 'orderLines')]
private Purchase $purchase;
#[ORM\Column]
private int $aantal;
#[ORM\Column(type: 'float')]
private float $prijs;
// ... getters en setters
}
6 Checkout — bestelling opslaan
Bij de checkout zet je de sessie-data om naar echte database-records. Je maakt een Purchase aan, voegt voor elk cart-item een OrderLine toe en slaat alles op via Doctrine.
use App\Entity\OrderLine;
use App\Entity\Purchase;
use Doctrine\ORM\EntityManagerInterface;
#[Route('/winkelmandje/afrekenen', name: 'cart_checkout', methods: ['POST'])]
public function checkout(EntityManagerInterface $em): Response
{
$items = $this->cart->getShoppingCart();
if (empty($items)) {
$this->addFlash('error', 'Je winkelmandje is leeg!');
return $this->redirectToRoute('cart_index');
}
// Maak een nieuwe bestelling aan
$purchase = new Purchase();
$purchase->setTotaalprijs($this->cart->getTotalPrice());
// Voeg voor elk product een orderregel toe
foreach ($items as $item) {
$orderLine = new OrderLine();
$orderLine->setProduct($item['product']);
$orderLine->setAantal($item['aantal']);
$orderLine->setPrijs($item['product']->getPrijs());
$purchase->addOrderLine($orderLine);
}
// Sla de bestelling op (cascade persist zorgt voor de orderlines)
$em->persist($purchase);
$em->flush();
// Winkelmantje leegmaken
$this->cart->clearShoppingCart();
$this->addFlash('success', 'Bestelling geplaatst! Bedankt voor je aankoop.');
return $this->redirectToRoute('cart_confirmation', ['id' => $purchase->getId()]);
}
persist($purchase) is nodig — Doctrine slaat alle gekoppelde OrderLines automatisch op.
7 Oefenen: schrijf addProductToCart
Schrijf de methode addProductToCart(int $productId) voor de CartSessionStorage service. De methode moet het product aan de sessie toevoegen (of het aantal verhogen als het er al in zit).
🛒 Demo: winkelmantje simulatie
Klik op "Toevoegen" om te zien hoe een winkelmantje werkt. De data wordt opgeslagen in localStorage (simulatie van een sessie).
🛒 Winkelmandje (0 items)
Nog geen producten
📋 Samenvatting
- ✓ Sessions bewaren data per gebruiker:
session->set(),get(),remove()viaRequestStack - ✓ Fixtures laden testdata in de database:
composer require --dev orm-fixtures - ✓
CartSessionStorageis een service die alle winkelmandje-logica bevat via sessions - ✓ Services worden automatisch geïnjecteerd via Symfony autowiring (constructor parameter)
- ✓
Purchase(OneToMany) enOrderLine(ManyToOne) slaan de bestelling permanent op - ✓ Bij checkout: cart-items omzetten naar Doctrine entities,
persist()+flush(), cart leegmaken
Gefeliciteerd!
Je hebt alle 8 lessen van SymfonyLearn doorlopen! Van installatie en routes tot CRUD en een volledig winkelmantje — je hebt de basisconcepten van Symfony in de vingers.
Klaar voor een echt project? Combineer alles wat je geleerd hebt en bouw je eigen Symfony-webshop!
Kennischeck
Test of je de stof begrepen hebt
Laatste les! Markeer hem als voltooid.