Class: Cart

  • A user can add a record to his shopping cart (we call it basket)
  • Because every user has his own basket, we store the information in a session variable, called cart
  • Because we need different actions (add item to cart, remove item from cart, clear the cart, ...) on different places (shop detail page, basket overview, ...), we'll make a new Cart class that handles all these actions
    • We use the same technique as with the Json class earlier in this project
    • We can call these actions inside a view and inside a controller

What are sessions?

  • A session can be defined as a server-side storage of information that is desired to persist throughout the user's interaction with the web site or web application

How are sessions stored?

  • There are different ways to store a session
  • Most of the time, we use file based sessions and that's also the default setting in Laravel
  • Take a look at the session configuration file config/session.php
    • The driver refers to the variable SESSION_DRIVER inside the .env file
    • The lifetime refers to the variable SESSION_LIFETIME inside the .env file
    • The expire_on_close is set to false, so the session is still active even if you close the browser
'driver' => env('SESSION_DRIVER', 'file'),
...
'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => false,
1
2
3
4
  • Now, take a look at the session variables inside .env
    • SESSION_DRIVER is set to file
    • SESSION_LIFETIME is set to 120 minutes, or 2 hours
SESSION_DRIVER=file
SESSION_LIFETIME=120
1
2
  • When you login to the site, you'll find one or more files with a random name inside the folder storage/framework/sessions
  • You can open a file to see what's inside a session

Handle sessions

Action Laravel (helper) Laravel (facade) Pure PHP
set session()->put('key', 'val') Session::put('key', 'val') $_SESSION['key'] = 'val'
get session()->get('key') Session::get('key') $_SESSION['key']
remove key session()->forget('key') Session::forget('key') unset($_SESSION['key'])
remove keys session()->forget(['key1', 'key2']) Session::->forget(['key1', 'key2'])
remove all keys session()->flush() Session::flush() session_destroy()

The Cart class

  • Before we proceed to the Cart class itself, first take a look at the cart logic
  • Below you find an example of what's stored inside the session variable cart
    • 3 times the record with $id = 6 ( 3 * 19.99 € = 59.97 €)
    • 1 time the record with $id = 3 ( 1 * 24.99 € = 24.99 €)
    • Total items inside your basket = 4
    • Total price for your basket = 59.97 € + 24.99 € = 84.96

Session cart

  • The cart is an array with three keys:
    • records contains an associative array where the key represents the record id and the value contains (an associative array with) all the fields we need in the orderlines table
    • totalQty contains the number of items in our cart
    • totalPrice contains the total price of our cart
  • If we know the result, we can construct our Cart class:
    • We start with an empty (private) property $cart built according to the structure we just discussed
    • Then we have the constructor
      • If the session variable cart exists, copy the session variable to the $cart variable
      • Else, do nothing
    • After the constructor you'll find three methods to modify the cart
      • Add a record to the cart (logic comes later)
      • Delete a record from the cart (logic comes later)
      • Empty the cart by removing the session variable cart
    • With the six getters we can retrieve all the information (getCart()) or pieces of the information from the cart
      • We provide all possible combinations here, even if we may not need them (yet)
    • The last (private) method calculates the total price and the number of items in our cart

Create the class

  • Create a new PHP Class (not a new file) Cart.php inside the folder app/Helpers
    (In PhpStorm: right-click on the folder and choose New -> PHP Class)
  • Replace the content of the class with this code:
<?php

namespace App\Helpers;

class Cart
{
    private $cart = [
        'records' => [],
        'totalQty' => 0,
        'totalPrice' => 0
    ];

    // Cart constructor
    public function __construct()
    {
        if (session()->get('cart')){
            $this->cart = session()->get('cart');
        }
    }

    // Add a record to the cart
    public function add($item)
    {
        // add logic comes here
        session()->put('cart', $this->cart);  // save the session
    }

    // Delete a record from the cart
    public function delete($item)
    {
        // delete logic comes here
        session()->put('cart', $this->cart);  // save the session
    }

    // Empty the cart 
    public function empty()
    {
        session()->forget('cart');
    }

    // Get the complete cart
    public function getCart()
    {
        return $this->cart;
    }

    // Get all the records from the cart
    public function getRecords()
    {
        return $this->cart['records'];
    }

    // Get one record from the cart
    public function getOneRecord($key)
    {
        if (array_key_exists($key, $this->cart['records'])) {
            return $this->cart['records'][$key];
        }
    }

    // Get all the record keys
    public function getKeys()
    {
        return array_keys($this->cart['records']);
    }
    
    // Get the number of items 
    public function getTotalQty()
    {
        return $this->cart['totalQty'];
    }

    // Get the total price
    public function getTotalPrice()
    {
        return $this->cart['totalPrice'];
    }

    // Calculate the number of items and total price
    private function updateTotal()
    {
        // calculate logic comes here
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

Add an alias to the class

  • At the beginning of this course, we added the helper class Json to our project
  • Remember that all files inside the folder app/Helpers are automatically loaded by Laravel
  • To use the class Cart inside Blade we have to add an alias to this class
    • Open the file config/app.php and add an alias to the aliases array (don't forget the Facades prefix)




 


'aliases' => ['Json' => Facades\App\Helpers\Json::class,
    'Mask' => Facades\App\Helpers\Mask::class,
	'Cart' => Facades\App\Helpers\Cart::class,
]
1
2
3
4
5
6

Update code hinting

  • To get code hinting inside our application, we have to update the ide-helper
    • In the console, execute the following commands:
php artisan clear-compiled
php artisan ide-helper:generate
1
2

BasketController

  • Create a new controller class BasketController.php in the folder app/Http/Controllers
    • Run the command php artisan make:controller BasketController
  • Open the controller app/Http/Controllers/BasketController.php and add four methods that interact with our Cart class
class BasketController extends Controller
{
    public function index()
    {
        return view('basket');
    }

    public function addToCart($id)
    {
        $record = Record::findOrFail($id);
        Cart::add($record);
        return back();
    }

    public function deleteFromCart($id)
    {
        $record = Record::findOrFail($id);
        Cart::delete($record);
        return back();
    }

    public function emptyCart()
    {
        Cart::empty();
        return redirect('basket');
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

Add routes

  • Open routes/web.php and add four new routes
    • A GET route for showing the cart
    • A GET route for adding a record to the cart
    • A GET route for deleting a record from the cart
    • A GET route to make the cart empty
Route::get('basket', 'BasketController@index');
Route::get('basket/add/{id}', 'BasketController@addToCart');
Route::get('basket/delete/{id}', 'BasketController@deleteFromCart');
Route::get('basket/empty', 'BasketController@emptyCart');
1
2
3
4

Basic view

  • The basket only needs one view

REMARKS

  • In order to test the logic inside the Cart class, we start with a simple list of three records that we can add/remove to our cart. At the bottom we show the content of our cart.
  • In the next chapter we update this page to a 'real' basket
  • Update BasketController.php


 
 
 
 
 


public function index()
{
    // Take the first 3 records, ordered by album title
    $records = Record::orderBy('title')->take(3)->get();
    $result = compact('records');
    Json::dump($result);
    return view('basket', $result);
}
1
2
3
4
5
6
7
8
  • Make a basic view basket.blade.php inside the folder resources/views
  • Open the file resources/views/basket.blade.php and update the view
@extends('layouts.template')

@section('title', 'Your Basket')

@section('main')
    <h1>Basket</h1>
    <table class="table">
        <thead>
        <tr>
            <th>#</th>
            <th>Artist - Album</th>
            <th>Action</th>
        </tr>
        </thead>
        <tbody>

        @foreach($records as $record)
            <tr>
                <td>{{ $record->id }}</td>
                <td>{{ $record->artist }} - {{ $record->title }}</td>
                <td>
                    <div class="btn-group btn-group-sm">
                        <a href="/basket/add/{{ $record->id }}" class="btn btn-outline-success">+1</a>
                        <a href="/basket/delete/{{ $record->id }}" class="btn btn-outline-danger">-1</a>
                    </div>
                </td>
            </tr>
        @endforeach
        </tbody>
    </table>
    <a href="/basket/empty" class="btn btn-sm btn-outline-danger">Empty basket</a>

    <h2 class="mt-5">What's inside my basket?</h2>
    <hr>
    <h4>Cart::getCart():</h4>
    <pre>{{ json_encode(Cart::getCart(), JSON_PRETTY_PRINT) }}</pre>
    <hr>
    <h4>Cart::getRecords():</h4>
    <pre>{{ json_encode(Cart::getRecords(), JSON_PRETTY_PRINT) }}</pre>
    <hr>
    <h4>Cart::getOneRecord(6):</h4>
    <pre>{{ json_encode(Cart::getOneRecord(6), JSON_PRETTY_PRINT) }}</pre>
    <hr>
    <p><b>Cart::getKeys()</b>: {{ json_encode(Cart::getKeys()) }}</p>
    <p><b>Cart::getTotalPrice()</b>: {{ json_encode(Cart::getTotalPrice()) }}</p>
    <p><b>Cart::getTotalQty()</b>: {{ json_encode(Cart::getTotalQty()) }}</p>
@endsection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

Dummy cart

Add some logic to the Cart class

Add item to cart

  • Open app/Helpers/Cart.php
  • The add($item) method receives a parameter $item, containing the selected record (as a collection)
  • First, extract the id and the the price ($singlePrice) because we need this information later on
  • Check, with the PHP method array_key_exists(), if there is an element with the key $id inside the records array (of our cart)
    • If there is NOT an element with that key:
      • Add a new element with key $id to the records array
        • Extract the id, title, artist, cover (which can be null at the moment) and price from the collection
        • Set the quantity qty to 1
    • Else, if the element already exists
      • Increment the quantity by 1
      • Calculate the price by multiplying the new quantity with the price of a single item $singlePrice
  • Recalculate the total price and the number of items in our cart via $this->updateTotal()
  • Save the cart as a session variable cart


 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 



public function add($item)
{
    $id = $item->id;
    $singlePrice = $item->price;
    if (!array_key_exists($id, $this->cart['records'])) {
        $this->cart['records'][$id] = [
            'id' => $item->id,
            'title' => $item->title,
            'artist' => $item->artist,
            'cover' => $item->cover,
            'qty' => 1,
            'price' => $item->price
        ];
    } else {
        $this->cart['records'][$id]['qty']++;
        $this->cart['records'][$id]['price'] = $singlePrice * $this->cart['records'][$id]['qty'];
    }
    $this->updateTotal();                 // update totalQty and totalPrice
    session()->put('cart', $this->cart);  // save the session
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Update number of items and total price in cart

  • Start with the variables $totalQty = 0 and $totalPrice = 0
  • Loop over each record inside the cart and update $totalQty and $totalPrice
  • Update the cart with the new values


 
 
 
 
 
 
 
 


private function updateTotal()
{
    $totalQty = 0;
    $totalPrice = 0;
    foreach ($this->cart['records'] as $record) {
        $totalQty += $record['qty'];
        $totalPrice += $record['price'];
    }
    $this->cart['totalQty'] = $totalQty;
    $this->cart['totalPrice'] = round($totalPrice, 2);
}
1
2
3
4
5
6
7
8
9
10
11

Delete item from cart

  • The delete($item) method also receives a parameter $item, containing the selected record (as a collection)
  • First extract the id and the the price ($singlePrice) because we need this information later on
  • Check if there is an element with the key $id inside the records array (of our cart)
    • If there is an element with that key:
      • Decrement the quantity by 1
      • Check the new quantity
        • If the quantity is NOT 0, then calculate the price by multiplying the new quantity with the price of a single item $singlePrice
        • If the quantity is 0, then remove (unset()) it from the array
      • Recalculate the total price and total quantity in our cart via $this->updateTotal()
  • Save the cart as a session variable cart


 
 
 
 
 
 
 
 
 
 
 



public function delete($item)
{
    $id = $item->id;
    $singlePrice = $item->price;
    if (array_key_exists($id, $this->cart['records'])) {
        $this->cart['records'][$id]['qty']--;
        if ($this->cart['records'][$id]['qty'] != 0) {
            $this->cart['records'][$id]['price'] = $singlePrice * $this->cart['records'][$id]['qty'];
        } else {
            unset($this->cart['records'][$id]);
        }
        $this->updateTotal();
    }
    session()->put('cart', $this->cart);  // save the session
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

EXERCISE: Add cover and badge

  1. Refactor the controller so the cover can't be empty

Add cover

  1. Add a green Bootstrap badge to the Basket link in the navigation
    • The badge shows the number of items in your basket
    • The badge is only visible if there is at least one item in your basket

Badge

Last Updated: 5/4/2020, 4:27:43 PM