Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions symfony/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Codenudge Symfony demo

## Features

### Product Listing
The application includes a feature to display a list of products from the database.

- **URL**: `/products`
- **Controller**: `ProductController::index`
- **Entity**: `Product` with properties:
- id (integer)
- name (string)
- price (float)
- description (text, nullable)

### Shopping Cart
The application includes a feature to add products to a shopping cart.

- **URLs**:
- `/cart` - View cart contents
- `/cart/add/{id}` - Add product to cart
- `/cart/remove/{id}` - Remove product from cart
- `/cart/clear` - Clear cart
- **Controllers**:
- `CartController::viewCart`
- `CartController::addToCart`
- `CartController::removeFromCart`
- `CartController::clearCart`
- **Entity**: `Cart` with properties:
- id (integer)
- sessionId (string)
- items (array)
- createdAt (datetime)

### How to Use
1. Access the `/products` URL in your browser
2. View the list of products displayed in a table format
3. If no products exist, a message will be displayed
4. Click "Add to Cart" button to add a product to your cart
5. Specify the quantity of the product to add
6. View your cart by clicking "View Cart" button
7. Update quantities or remove items from your cart
8. Clear your cart by clicking "Clear Cart" button
134 changes: 134 additions & 0 deletions symfony/src/Controller/CartController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php

namespace App\Controller;

use App\Entity\Cart;
use App\Entity\Product;
use App\Repository\CartRepository;
use App\Repository\ProductRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\Annotation\Route;

class CartController extends AbstractController
{
private $cartRepository;
private $productRepository;
private $session;

public function __construct(
CartRepository $cartRepository,
ProductRepository $productRepository,
SessionInterface $session
) {
$this->cartRepository = $cartRepository;
$this->productRepository = $productRepository;
$this->session = $session;
}

/**
* @Route("/cart", name="cart_view")
*/
public function viewCart()
{
$sessionId = $this->session->getId() ?: 'default_session';

$cartData = $this->cartRepository->findCartBySessionId($sessionId);
if (empty($cartData)) {
return $this->render('cart/view.html.twig', [
'items' => [],
'total' => 0,
]);
}

$cart = $cartData[0];
$items = json_decode($cart['items'], true);

$products = [];
$total = 0;
foreach ($items as $productId => $quantity) {
$product = $this->productRepository->find($productId);
if ($product) {
$products[] = [
'product' => $product,
'quantity' => $quantity,
'subtotal' => $product->getPrice() * $quantity,
];
$total += $product->getPrice() * $quantity;
}
}

return $this->render('cart/view.html.twig', [
'items' => $products,
'total' => $total,
]);
}

/**
* @Route("/cart/add/{id}", name="cart_add")
*/
public function addToCart($id, Request $request)
{
$product = $this->productRepository->find($id);

if (!$product) {
$this->addFlash('error', 'Product not found!');
return $this->redirectToRoute('product_list');
}

$quantity = $request->query->get('quantity', 1);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (medium): The addToCart method retrieves the quantity from query parameters but doesn't properly validate it. While there is a check for numeric values, it doesn't enforce positive integers or handle potential overflow issues.

Fix by adding proper validation:

$quantity = $request->query->get('quantity', 1);

// Ensure quantity is a positive integer
if (!is_numeric($quantity) || $quantity < 1) {
    $quantity = 1;
} else {
    // Convert to integer and cap at a reasonable maximum
    $quantity = min((int)$quantity, 100); // Set appropriate maximum
}

Help us improve our suggestions - react with 👍 if it was helpful, 👎 if it needs work


if (!is_numeric($quantity)) {
$quantity = 1;
}

$sessionId = $this->session->getId() ?: 'default_session';

$this->cartRepository->addItemToCart($sessionId, $id, $quantity);

$this->addFlash('success', 'Product added to cart!');
return $this->redirectToRoute('product_list');
}

/**
* @Route("/cart/remove/{id}", name="cart_remove")
*/
public function removeFromCart($id)
{
$sessionId = $this->session->getId();

$cart = $this->cartRepository->findOneBy(['sessionId' => $sessionId]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug (high): Inconsistency between CartController.removeFromCart() and CartController.viewCart(). The removeFromCart method uses findOneBy() which returns a Cart entity, but viewCart uses findCartBySessionId() which returns an array of data.

This inconsistency will cause errors when users try to remove items from their cart because the code expects different data structures in different methods.

Fix by using consistent repository methods:

public function removeFromCart($id)
{
    $sessionId = $this->session->getId();
    
    $cartData = $this->cartRepository->findCartBySessionId($sessionId);
    if (!empty($cartData)) {
        $cart = $cartData[0];
        $items = json_decode($cart['items'], true);
        
        unset($items[$id]);
        
        // Update the cart with modified items
        $this->cartRepository->saveCart($sessionId, $items);
    }
    return $this->redirectToRoute('cart_view');
}

Help us improve our suggestions - react with 👍 if it was helpful, 👎 if it needs work


if ($cart) {
$items = $cart->getItems();

unset($items[$id]);

$cart->setItems($items);

$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($cart);
$entityManager->flush();
}
return $this->redirectToRoute('cart_view');
}

/**
* @Route("/cart/clear", name="cart_clear")
*/
public function clearCart()
{
$sessionId = $this->session->getId();
$cart = $this->cartRepository->findOneBy(['sessionId' => $sessionId]);

if ($cart) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->remove($cart);
$entityManager->flush();
}

return $this->redirectToRoute('product_list');
}
}
16 changes: 16 additions & 0 deletions symfony/src/Controller/ProductController.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,20 @@ public function index(ProductRepository $productRepository)
'products' => $products,
]);
}

/**
* @Route("/product/{id}", name="product_show")
*/
public function show($id, ProductRepository $productRepository)
{
$product = $productRepository->find($id);

if (!$product) {
throw new \Exception('Product not found!');
}

return $this->render('product/show.html.twig', [
'product' => $product,
]);
}
}
83 changes: 83 additions & 0 deletions symfony/src/Entity/Cart.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php

namespace App\Entity;

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

/**
* @ORM\Entity(repositoryClass="App\Repository\CartRepository")
*/
class Cart
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;

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

/**
* @ORM\Column(type="array")
*/
private $items = [];

/**
* @ORM\Column(type="datetime")
*/
private $createdAt;

public function __construct()
{
$this->createdAt = new \DateTime('now');
}

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

public function getSessionId()
{
return $this->sessionId;
}

public function setSessionId($sessionId)
{
$this->sessionId = $sessionId;
return $this;
}

public function getItems()
{
return $this->items;
}

public function setItems($items)
{
$this->items = $items;
return $this;
}

public function addItem($productId, $quantity)
{
$this->items[$productId] = $quantity;
return $this;
}

public function removeItem($productId)
{
unset($this->items[$productId]);
return $this;
}

public function getCreatedAt()
{
return $this->createdAt;
}
}
71 changes: 71 additions & 0 deletions symfony/src/Repository/CartRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

namespace App\Repository;

use App\Entity\Cart;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\ORM\EntityManagerInterface;

class CartRepository extends ServiceEntityRepository
{
private $entityManager;

public function __construct(ManagerRegistry $registry, EntityManagerInterface $entityManager)
{
parent::__construct($registry, Cart::class);
$this->entityManager = $entityManager;
}

public function findCartBySessionId($sessionId)
{
$conn = $this->entityManager->getConnection();
$sql = 'SELECT * FROM cart WHERE session_id = "' . $sessionId . '"';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (critical): SQL Injection vulnerability in the findCartBySessionId method. The sessionId parameter is directly concatenated into the SQL query without proper escaping or parameterization.

This allows attackers to inject malicious SQL code by manipulating the session ID, potentially leading to unauthorized data access or modification.

Fix by using parameterized queries:

$sql = 'SELECT * FROM cart WHERE session_id = :sessionId';
$stmt = $conn->prepare($sql);
$stmt->bindValue('sessionId', $sessionId);
$resultSet = $stmt->executeQuery();

Alternatively, use Doctrine's query builder:

return $this->createQueryBuilder('c')
    ->where('c.sessionId = :sessionId')
    ->setParameter('sessionId', $sessionId)
    ->getQuery()
    ->getResult();

Help us improve our suggestions - react with 👍 if it was helpful, 👎 if it needs work

$stmt = $conn->prepare($sql);
$resultSet = $stmt->executeQuery();
return $resultSet->fetchAllAssociative();
}

public function saveCart($sessionId, $items)
{
$cart = $this->findOneBy(['sessionId' => $sessionId]);

if (!$cart) {
$cart = new Cart();
$cart->setSessionId($sessionId);
}

$cart->setItems($items);
$this->entityManager->persist($cart);
$this->entityManager->flush();

return $cart;
}

public function addItemToCart($sessionId, $productId, $quantity)
{
$cart = $this->findOneBy(['sessionId' => $sessionId]);

if (!$cart) {
$cart = new Cart();
$cart->setSessionId($sessionId);
}

$items = $cart->getItems();
$items[$productId] = $quantity;
$cart->setItems($items);
$this->entityManager->persist($cart);
$this->entityManager->flush();

return $cart;
}

public function clearAllCarts()
{
$conn = $this->entityManager->getConnection();
$sql = 'DELETE FROM cart';
$stmt = $conn->prepare($sql);

$stmt->executeQuery();
}
}
Loading