Appearance
Cart Helper Class
In this chapter, we'll construct a dedicated helper class to manage the shopping cart (or basket) functionality for our vinyl shop application. This class will encapsulate all the logic needed for adding records to the cart, removing them, updating quantities, calculating totals, and ensuring the cart's state persists between user requests using Laravel's session management system. Creating a helper class like this promotes better code organization and reusability.
Understanding Sessions in Laravel
Before building the Cart
class, it's essential to understand how Laravel handles sessions. A session provides a way to store user-specific information across multiple requests. Imagine a user browsing your shop – you need a mechanism to remember which items they've added to their cart as they navigate from page to page.
Unlike cookies, which store data directly in the user's browser, session data is typically stored on the server. When a user first interacts with your application in a way that requires a session (like adding an item to the cart), Laravel generates a unique Session ID. This ID is then sent to the user's browser, usually stored in a secure cookie. On subsequent requests from that user, the browser sends the Session ID cookie back to the server. Laravel uses this ID to retrieve the correct session data stored on the server, effectively "remembering" the user's state, like their shopping cart contents. This server-side storage makes sessions more secure for potentially sensitive information.
Session Configuration
Laravel's session configuration resides in the config/session.php
file. While we often set the primary driver via the .env
file, this configuration file offers finer control. Key options include:
driver
: Determines where session data is stored. Common options arefile
(default, stores instorage/framework/sessions
),cookie
(stores encrypted data in cookies, suitable for small amounts),database
( stores in a DB table, good for scaling),memcached
/redis
(fast in-memory stores), orarray
(only for the current request, useful for testing). We'll stick with thefile
driver for simplicity.lifetime
: The duration (in minutes) a session remains active if the user is idle(defaults to120
).expire_on_close
: Iftrue
, the session ends when the user closes their browser (defaults totrue
).encrypt
: Iftrue
(recommended), session data is encrypted before being stored on the server (defaults totrue
).cookie
: The name of the session cookie stored in the user's browser (defaults tolaravel_session
).
We've already configured the necessary session settings in our .env
file for this course.
Interacting with Session Data
Laravel offers a convenient global helper function, session()
, to interact with session data. Here's a summary of common operations:
Action | Method | Description |
---|---|---|
set | session()->put('key', 'value') | Stores an item in the session with a specific key. |
get | session()->get('key', 'default') | Retrieves an item by key. Returns the optional default value if the key doesn't exist. |
get | session()->get('key') | Retrieves an item by key. Returns null if the key doesn't exist. |
get all | session()->all() | Returns all data stored in the session as an associative array. |
remove key | session()->forget('key') | Removes a specific item (key and value) from the session. |
remove keys | session()->forget(['key1', 'key2']) | Removes specific items (key and value) from the session. |
remove all keys | session()->flush() | Removes all data from the current session. |
has | session()->has('key') | Checks if an item exists in the session for the given key. Returns true or false . |
The session()
helper function provides an easy and accessible way to manage session data throughout your application, which we will leverage in our Cart
class.
Creating the Cart Helper Class
Our Cart
class will act as a centralized manager for all shopping cart operations. It will use static methods, meaning we can call its methods directly without needing to create an instance of the class (e.g., Cart::add($record)
). This makes it convenient to access cart functionality from anywhere in our code, such as Livewire components or controllers.
The core responsibilities of this class will be:
- Initialization: Loading the cart from the session when the application starts or a user interacts with the cart.
- Adding Items: Adding new records to the cart or incrementing the quantity if the item already exists.
- Removing/Updating Items: Decreasing the quantity of an item or removing it completely if the quantity reaches zero.
- Emptying the Cart: Clearing all items from the cart.
- Calculating Totals: Keeping track of the total number of items and the total price.
- Persistence: Saving the updated cart state back into the session after modifications.
- Retrieving Cart Data: Providing methods to get the full cart details, individual items, or specific totals.
REMARKS
In the next chapters, we'll use the Cart
class to handle the shopping cart. To give you a better understanding what's going on, we'll explain the code by an example of what we want to achieve.
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
(in the next chapter, we'll actually get this result).
The user has added the following items to the cart:
- 2 times the record with
$id = 11
( 2 * 16.49 € = 32.98 €) - 1 time the record with
$id = 15
( 1 * 9.99 € = 9.99 €) - Total items inside your basket = 3
- Total price for your basket = 32.98 € + 9.99 € = 42.97 €
Let's create the file and define the complete class structure.
- Create a new directory named
Helpers
inside yourapp
directory if it doesn't already exist. - Inside
app/Helpers
, create a new file namedCart.php
. - Paste the following complete code into
app/Helpers/Cart.php
:
php
<?php
namespace App\Helpers;
use App\Models\Record; // Import the Record model to type-hint and access record properties
use Illuminate\Support\Facades\Session; // Import the Session facade for session management
use Illuminate\Support\Facades\Storage; // Import the Storage facade for checking file existence
class Cart
{
// Static property to hold the cart data structure.
// 'static' means this property belongs to the class itself, not an instance.
private static array $cart = [
'records' => [], // Associative array to hold individual record items (keyed by record ID)
'totalQty' => 0, // Total quantity of all items in the cart
'totalPrice' => 0 // Total price of all items in the cart
];
/**
* Initializes the cart.
* Retrieves the cart from the session if it exists, otherwise uses the default empty structure.
*/
public static function init(): void
{
// Use the null coalescing operator (??) to get 'cart' from session or use the default self::$cart.
self::$cart = Session::get('cart', self::$cart);
}
/**
* Adds a Record model instance to the cart.
* If the record already exists, increments quantity and updates price.
* If new, adds the record details to the cart.
* Always updates totals and saves to session.
*/
public static function add(Record $record): void
{
$singlePrice = (float) $record->price; // Ensure price is float
$recordId = $record->id;
// Check if the record ID already exists as a key in the 'records' array
if (array_key_exists($recordId, self::$cart['records'])) {
// Record exists: Increment quantity and add price
self::$cart['records'][$recordId]['qty']++;
self::$cart['records'][$recordId]['price'] += $singlePrice;
} else {
// Record is new: Add it to the 'records' array
self::$cart['records'][$recordId] = [
'id' => $recordId,
'artist' => $record->artist,
'title' => $record->title,
'mb_id' => $record->mb_id, // MusicBrainz ID, used for cover image
// Check if a specific cover exists, otherwise use a default image
'cover' => Storage::disk('public')->exists('covers/' . $record->mb_id . '.jpg')
? '/storage/covers/' . $record->mb_id . '.jpg' // Path if exists (uses storage link)
: '/storage/covers/no-cover.png', // Path to default image
'price' => $singlePrice, // Price for this specific item entry (updates if qty > 1)
'qty' => 1 // Initial quantity
];
}
// Recalculate totals and save the updated cart to the session
self::updateTotal();
}
/**
* Decreases the quantity of a record in the cart.
* If quantity becomes zero, removes the record entirely.
* Updates totals and saves to session.
*/
public static function delete(Record $record): void
{
$singlePrice = (float) $record->price;
$recordId = $record->id;
// Check if the record exists in the cart
if (array_key_exists($recordId, self::$cart['records'])) {
// Record exists: Decrement quantity and subtract price
self::$cart['records'][$recordId]['qty']--;
self::$cart['records'][$recordId]['price'] -= $singlePrice;
// If quantity drops to zero, remove the record item completely
if (self::$cart['records'][$recordId]['qty'] <= 0) {
unset(self::$cart['records'][$recordId]); // Remove the element from the array
}
// Recalculate totals and save the updated cart to the session
self::updateTotal();
}
// If the record wasn't in the cart, do nothing.
}
/**
* Empties the entire cart by removing it from the session
* and resetting the static property.
*/
public static function empty(): void
{
// Remove the 'cart' key from the session storage
Session::forget('cart');
// Also reset the static property to its initial empty state
self::$cart = [
'records' => [],
'totalQty' => 0,
'totalPrice' => 0
];
}
/**
* (Private) Recalculates the total quantity and total price
* based on the current items in the cart.
* Crucially, it saves the entire updated cart back into the session.
*/
private static function updateTotal(): void
{
$totalQty = 0;
$totalPrice = 0.0;
// Loop through each record currently in the cart
foreach (self::$cart['records'] as $record) {
$totalQty += $record['qty'];
$totalPrice += $record['price']; // Note: $record['price'] is the total price for that item's quantity
}
// Update the static cart property with the new totals
self::$cart['totalQty'] = $totalQty;
self::$cart['totalPrice'] = $totalPrice;
// *** IMPORTANT: Persist the entire updated cart array to the session ***
Session::put('cart', self::$cart);
}
/**
* Returns the entire cart array (records, totalQty, totalPrice).
*/
public static function getCart(): array
{
// Ensure the cart is loaded from the session if it hasn't been already
// Although init() is called below, this is a safeguard.
if (empty(self::$cart['records']) && Session::has('cart')) {
self::init();
}
return self::$cart;
}
/**
* Returns only the array of records currently in the cart.
*/
public static function getRecords(): array
{
return self::getCart()['records']; // Gets the latest cart state first
}
/**
* Returns a specific record from the cart by its ID (key).
* Returns an empty array if the record is not found.
*/
public static function getOneRecord(int $key): array
{
$records = self::getRecords(); // Get current records
// Check if the key exists in the records array
if (array_key_exists($key, $records)) {
return $records[$key];
}
return []; // Return empty array if not found
}
/**
* Returns an array containing all the record IDs (keys) currently in the cart.
*/
public static function getKeys(): array
{
return array_keys(self::getRecords()); // Get keys from the current records array
}
/**
* Returns the total quantity of all items in the cart.
*/
public static function getTotalQty(): int
{
return self::getCart()['totalQty']; // Get totalQty from the current cart state
}
/**
* Returns the total price of all items in the cart.
*/
public static function getTotalPrice(): float
{
return (float) self::getCart()['totalPrice']; // Get totalPrice, ensuring float type
}
}
// Initialize the cart when this class file is loaded by PHP's autoloader.
// This ensures that when the Cart class is first used in a request,
// it attempts to load any existing cart data from the session.
Cart::init();
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
Let's break down the key methods and concepts in this class.
use
Statements
At the top, we have use
statements: use App\Models\Record;
imports the Record
model, allowing us to type-hint method arguments (like add(Record $record)
) and access record properties directly. use Illuminate\Support\Facades\Session;
imports the Session
facade, providing access to Laravel's session management features like Session::get()
, Session::put()
, and Session::forget()
. use Illuminate\Support\Facades\Storage;
imports the Storage
facade, used here specifically to check if a cover image file exists using Storage::disk('public')->exists(...)
.
Static Properties and Methods
The class uses static
properties (private static array $cart
) and static
methods (all public static function
...). This means the $cart
data and the methods belong to the class itself, not to any specific instance of the class. We don't need to write new Cart()
anywhere. We can directly call Cart::add()
, Cart::getTotalQty()
, etc. The private static $cart
property holds the actual cart data, shared across all uses of the class within a single request lifecycle. The init()
method ensures this static property is populated from the session at the start.
init()
Method
php
public static function init(): void
{
self::$cart = Session::get('cart', self::$cart);
}
1
2
3
4
2
3
4
This method is crucial for loading the cart data. Session::get('cart', self::$cart)
attempts to retrieve the item associated with the key 'cart'
from the session. If 'cart'
doesn't exist in the session (e.g., first visit, or session expired), it returns the default value provided, which is the current state of self::$cart
(initially the empty structure). The result is then assigned back to self::$cart
. The Cart::init();
call at the end of the file ensures this logic runs when the class definition is loaded, making the cart available early in the request.
add(Record $record)
Method
php
public static function add(Record $record): void
{
// ... (get price, check if exists)
if (array_key_exists($recordId, self::$cart['records'])) {
// Increment existing item
self::$cart['records'][$recordId]['qty']++;
self::$cart['records'][$recordId]['price'] += $singlePrice;
} else {
// Add new item
self::$cart['records'][$recordId] = [ /* ... record details ... */ ];
}
self::updateTotal(); // IMPORTANT: Recalculate and save
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
This method handles adding a record. It first checks if the record's ID ($recordId
) is already a key in the self::$cart['records']
array using array_key_exists
. If yes, it increments the quantity (qty
) and adds the singlePrice
to the item's total price. If no, it creates a new entry in the records
array, storing all necessary details, including the cover image path. The cover path uses a ternary operator ( condition ? value_if_true : value_if_false
) to check for the existence of a specific cover image using Storage::disk('public')->exists()
. Finally, and vitally, it calls self::updateTotal()
to recalculate the overall cart totals and save the changes to the session.
delete(Record $record)
Method
php
public static function delete(Record $record): void
{
// ... (get price, check if exists)
if (array_key_exists($recordId, self::$cart['records'])) {
// Decrement quantity and price
self::$cart['records'][$recordId]['qty']--;
self::$cart['records'][$recordId]['price'] -= $singlePrice;
// Remove if quantity is zero or less
if (self::$cart['records'][$recordId]['qty'] <= 0) {
unset(self::$cart['records'][$recordId]);
}
self::updateTotal(); // IMPORTANT: Recalculate and save
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
This method decreases a record's quantity. It first checks if the record exists. If so, it decrements the quantity and subtracts the singlePrice
. It then checks if the quantity has dropped to zero or below. If it has, unset()
is used to completely remove that record's entry from the records
array. Again, self::updateTotal()
is called to update totals and persist the changes.
empty()
Method
php
public static function empty(): void
{
Session::forget('cart');
self::$cart = [ /* ... empty structure ... */ ];
}
1
2
3
4
5
2
3
4
5
This simply removes the 'cart'
item from the session storage using Session::forget('cart')
and also resets the static $cart
property within the class to its initial empty state for the remainder of the current request.
updateTotal()
Method
php
private static function updateTotal(): void
{
// ... (calculate totals by looping through self::$cart['records'])
self::$cart['totalQty'] = $totalQty;
self::$cart['totalPrice'] = $totalPrice;
// *** Persist the updated cart to the session ***
Session::put('cart', self::$cart);
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
This private helper method recalculates totalQty
and totalPrice
by iterating through all records currently in self::$cart['records']
. After calculating, it updates the corresponding keys in the self::$cart
array. The most critical step here is Session::put('cart', self::$cart);
, which takes the entire, modified $cart
array and saves it back into the session under the key 'cart'
. Without this line, any changes made by add
or delete
would be lost as soon as the request finishes.
Getter Methods (getCart
, getRecords
, etc.)
Methods like getCart()
, getRecords()
, getOneRecord($key)
, getKeys()
, getTotalQty()
, and getTotalPrice()
are straightforward accessor methods. They provide controlled ways to retrieve information about the cart's current state ( total price, quantity, specific items, etc.) from the self::$cart
property. Note that they often call self::getCart()
or self::getRecords()
internally to ensure they are working with the most up-to-date cart data loaded from the session.
This completes the implementation and explanation of our Cart
helper class. By encapsulating the cart logic and session interaction within this single, static class, we have a clean, reusable, and easily accessible way to manage the shopping cart throughout our Laravel application, particularly within our Livewire components where user interactions will trigger cart updates.