Griphon
Griphon

Reputation: 29

Using cart class with sessions

I've started to learn PHP classes, interfaces, extends and things related to those.

Before this i've worked only with functions and it started to be quite a mess with hundreads of separate functions.

There will be multiple classes for my project based on my needs:

  1. Cart class. This should be quite generic because at some point i might need to save data into database instead of sessions.
  2. Calculations class. There will be around 15 products and around 5 different type of calculations. (Based on amount, base price, price per person etc.)
  3. Database class. This would be used to get my prices from database for my calculations class.
  4. Create a pdf with MPDF from the submitted data through the html form (Which is also used to update cart amounts and return calculated prices in real time)

However, that was just to give a hint on what i'm trying to achieve, so it might give you better understanding on how should i approach things.

Now for the actual questions. Here's my Cart class:

interface CartInterface {
    public function addItem($id, $amount, $months);
    public function updateItem($id, $amount, $months);
    public function deleteItem($id);
    public function countItems();
    public function getCart();
    public function ifIdExists($id);    
}

class Cart implements CartInterface {
public $cart = array();

public function addItem($id, $amount, $months) {

    if($this->ifIdExists($id)){
        $this->updateItem($id, $amount, $months);                                   
    }

    else {
        $this->cart[$id]['id'] = $id;
        $this->cart[$id]['amount'] = $amount; 
        $this->cart[$id]['months'] = $months;
    }

}

public function updateItem($id, $amount, $months) {
        $this->cart[$id]['id'] = $id;
        $this->cart[$id]['amount'] = $amount; 
        $this->cart[$id]['months'] = $months;   
}

public function deleteItem($id) {
    unset($this->cart[$id]);        
}

public function countItems() {
    return count($this->cart);
}

public function getCart() {
    return $this->cart;
}

public function ifIdExists($id) {
    $exists = false;
    if(array_key_exists($id, $this->cart)) {
        $exists = true;
    }
    else {
        $exists = false;
    }
    return $exists;
}

public function showCart() {
    foreach($this->cart as $value) {
        echo "<br />";
        echo 'Id:'. $value['id'] ;
        echo "<br />";
        echo 'Amount:'.$value['amount'];
        echo "<br />";          
        echo 'Months:'.$value['months'];
        echo "<br />";
    }
}
}

That has pretty much the functions my cart would need but here are the questions:

  1. My html form has input field for each item, which determinates the amount of the corresponding item to be ordered. Do i need separate addItem() and updateItem() in this case? Because the newer value would anyway rewrite the older.

  2. What determinates on which methods should i include in CartInterface? I know that any class using that CartInterface is forced to use those methods but what is the reason that i would want to force a class to use those methods?

  3. The most important question is adapting this class to use sessions. As we know, cart isn't much of a use without sessions. How can i use $cart->deleteItem() to also delete item from session? Do i need to create a separate SessionCart class, which has same methods than Cart but deals with sessions?

    $cart = new Cart();
    $cart->addItem('1', '30', '2');
    $cart->addItem('2', '306', '12');
    $cart->addItem('6', '306', '12');
    $cart->deleteItem('1');
    
    // Here i create a session array from the cart but cart's class methods won't work
    $cart = $cart->getCart();
    
    $_SESSION['cart'] = $cart;
    $cart->deleteItem('1'); // This won't effect the session cart
    print_r($_SESSION['cart']); 
    

I appreciate all help and also please point out bad coding habits, so everyone reading this post would learn something.

Long story short: What approach should i use to get this Cart class work with sessions?

Upvotes: 1

Views: 1090

Answers (2)

apokryfos
apokryfos

Reputation: 40730

As I commented above, don't expose your inner workings in such a direct way. Your cart is working with an array internally, however the array is part of the cart and it's not the whole cart. Putting the array only in the session means you're giving up on the object oriented design. I would suggest this modification for starters.

$cart = new Cart();
$cart->addItem('1', '30', '2');
$cart->addItem('2', '306', '12');
$cart->addItem('6', '306', '12');

//$cart = $cart->getCart(); //This is not necessary

$_SESSION['cart'] = $cart; //This will put the object itself in the session. 
$cart->deleteItem('1'); // This should now affect the object
var_dump($_SESSION['cart']); 

At the end of the request, all objects in the session are serialized using serialize() so you shouldn't worry about how PHP does it you just need to know that PHP does it.

Upvotes: 0

hasumedic
hasumedic

Reputation: 2167

  1. Your implementations of adding and item and updating one are identical. Is it really relevant for you to know whether items are being added for the first time or being replaced? If the answer is yes, then you should definitely keep two separate methods. There will be people that will tell you that you're violating the DRY rule (Don't Repeat Yourself), but that rule only applies semantically. Things that write the same but don't mean the same are allowed for repetition.

  2. Interfaces are a way to ensuring that a certain contract is met. Imagine that you have a class that is able to generate an invoice from your cart. And also imagine that you implement another Cart which is the FreebieCart, which gives you a random item from the Cart for free. The Invoicer class would look something like this:

    class Invoicer {
    
        /**
        * @return Invoice
        */
        public function invoiceFromCart(CartInterface $cart) {
            //in here you know exactly what to expect from the $cart object
            $cart->getItems();
            $cart->countItems();
        }
    ...
    }
    

    As you can see, providing an interface is very convenient for the code that is going to be using that class, because it doesn't need to know which concrete implementation of a CartInterface is receiving, it just needs to know its contract to call its methods and communicate with it. This also allows you to interchange functionality without having to modify big amounts of code.

    To answer your comment, it'd be very weird to make sense of the class Invoicer extending Cart in OOP. The meaning of the keyword "extends" is that the class that is extending is a more specialized version of a more abstract class. Think on Car extends Vehicle or Earth extends Planet.

  3. There are a couple of ways you can go about this. The most important thing for me would be to wrap the session into its own abstraction. Something like a CartStorage. This class would implement an interface like this:

    interface CartStorage {
        public function store(CartInterface $cart);
        public function retrieve(SessionId $sessionId);
    }
    

    Then you'd need to implement a SessionCartStorage class which stores and allows you to retrieve carts from the session. If later on you'd like to store your carts in a Database or somewhere else, you will be able to implement a PostGresCartStorage or CassandraCartStorage which would have the means of storing your cart in those storage systems without the need of changing too much code.

    To answer to your comment, it'd depend on the way and the level of granularity that you want when storing a cart in the session. If you store item by item, you could indeed have a method like SessionCartStorage->addItem(CartItem $item). But this would complicate the way of retrieving a cart from the session, so I would keep it simple storing the whole cart instead.

Hope this helps.

Upvotes: 1

Related Questions