PHP Shopping Website with Cart - Returning a HTML String Edit


If there is a better way to ask this question to avoid negative votes, please let me know.
I have looked into https://codereview.stackexchange.com/help/how-to-ask


How would I implement a class dedicated to returning html strings for a list of products/cart items?


Written as part of an interview assignment.
Simply this is a shopping website with some items a user may add or remove from their shopping cart.
This function is responsible for returning the html to display for the list of cart items.
Currently there is no dedicated class for returning html strings.
I feel I would be moving most of this code into another class, defeating the point.


Code:  

function listItems()
{
    $formatter = new Formatter();
    $html = "";
    $id = 0;
    foreach ($_SESSION["Cart"] as $item) {
        if ($item["quantity"] &gt; 0) {
            $html = $html .
                '&lt;pre&gt;
                    &lt;div class="name"&gt;'.$this&gt;getName($item) .'&lt;/div&gt;&lt;div class="price"&gt;Price: $' . $formatter-&gt;formatPrice($item) . '&lt;/div&gt;
                    &lt;div class="quantity"&gt;Quantity: ' . $item["quantity"] . '&lt;/div&gt;
                    &lt;div class="total"&gt;Total: $' . $this-&gt;getTotal($item) . '&lt;/div&gt;
                    &lt;form class="removeForm" method="POST"&gt;
                        &lt;input class="removeInput" type="hidden" name="removeId" value=' . $id . ' /&gt;
                        &lt;input class="removeInput"  type="submit" name="removeButton" value="Remove from Cart" /&gt;
                    &lt;/form&gt;
                &lt;/pre&gt;';
        }
        $id++;
    }

    $html = $html .
        '&lt;pre&gt;
            &lt;div class="overallTotal"&gt;Overall Total: $' . $this-&gt;getOverallTotal() . '&lt;/div&gt;
        &lt;/pre&gt;';

    return $html;
}


Formatter class method returns a number with 2 decimal point format.

Full Repository here if you want more to critique: 


https://github.com/NathanielPather/Basic-Shopping-Cart/blob/Post-Code-Review/ShoppingCart.php
You might want to decompose it into smaller pieces that will be easier to follow and eventually modify.

You could also learn a bit about dependency injection. Creating new instances all around anytime you need them is not very good.

Here is a very simple rewrite of your code and a lot of pieces are missing but I hope it gives some general guideline.

It uses PHP 7.4 features, if you can't use those, of course ignore all the typehints, etc..

Also it might make sense to depend on interfaces instead of classes directly, but didn't want to go this far as I think it could be too much for you at one go.



Dont be shy to define structures.

class Product
{
  public int $id;
  public string $name;
  public float $price;

  public function __construct(int $id, string $name, float $price)
  {
    $this-&gt;id =  $id;
    $this-&gt;name = $name;
    $this-&gt;price = $price;
  }
}




Create product repository

class ProductsRepository
{
    /**
     * @param array&lt;int&gt; $ids
     * @return array&lt;int, Product&gt;
     */ 
    function getProducts(array $ids): array
    {
        // get it from somewhere
    }
}




Structure for item in cart holds a bit more.

class CartItem
{
  public int $productId;
  public string $name;
  public float $unitPrice;
  public int $quantity;
  public function __construct(int $productId, string $name, float $unitPrice, int $quantity = 1)
  {
    $this-&gt;productId = $productId;
    $this-&gt;name = $name;
    $this-&gt;unitPrice = $unitPrice;
    $this-&gt;quantity = $quantity;
  }
  public function getTotalPrice(): float
  {
    return $this-&gt;unitPrice * $this-&gt;quantity;
  }
}




Cart holds many items, allows to iterate them, and add them and it knows the product repository to allow it doing so.

class Cart
{
  private ProductsRepository $products;
  /** @var array&lt;CartItem&gt; */
  private array $items;
  public function __construct(ProductsRepository $products, array $items = [])
  {
    $this-&gt;products = $products;
    $this-&gt;items = $items;
  }
  public function getItems(): array
  {
    return $this-&gt;items;
  }

  public function getTotalPrice(): int
  {
    // count it here, or have it in property and make sure it is updated while adding items.
  }

  public function add(int $productId, int $quantity = 1)
  {
    if (isset($this-&gt;items[$productId])) {
      // already in cart =&gt; increase quatity
    } else {
      $product = $this-&gt;products-&gt;getProduct($productId);
      //handle product not exists, not on stock, etc.
      $this-&gt;items[$productId] = new CartItem($product-&gt;id, $product-&gt;name, $product-&gt;price, $quantity);
    }
  }
}




We will store cart in session, this will take care of it.

class CartSessionStorage
{
  // we need it for the Cart instances
  // and maybe we wont store product names in session but load them from products repo?
  private ProductsRepository $products;

  public function loadCart(): Cart
  {
    // load from session
  }
  public function saveCart(Cart $cart): void
  {
    // save to session
  }
}




Renderer knows the html structure and some localization/formatting rules.

class CartRenderer
{
  private Formatter $formatter;
  public function __construct(Formatter $formatter)
  {
    $this-&gt;formatter = $formatter;
  }
  public function renderCart(Cart $cart): string
  {
    $html = '';
    // your rendering here;
    foreach ($cart-&gt;getItems() as $item) {
    //you can use heredoc, but add some html escaping, nor heredoc nor your code handle it
    $html .= 
      &lt;&lt;&lt;HTML
      &lt;div&gt;write html here&lt;div&gt;and dont mind if u use &lt;span&gt;{$item-&gt;getTotalPrice()}&lt;/span&gt;
      HTML;
    }
    return $html;
  }
}




Put it all together.

$products = new ProductsRepository();
$storage = new SessionCartStorage(products);
$formatter = new Formatter();
$renderer = new CartRenderer($formatter);




And there you go.

$cart = $storage-&gt;loadCart();

// form submission?
$cart-&gt;add($productId, $quantity);
$storage-&gt;saveCart($cart);

// render it
echo $renderer-&gt;renderCart($cart);