bertugfahriozer / ci4shoppingcart
Codeigniter 4 Shopping Cart
Package info
github.com/bertugfahriozer/ci4shoppingcart
pkg:composer/bertugfahriozer/ci4shoppingcart
Requires
- php: ^8.2
Requires (Dev)
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^9.6
README
π CodeIgniter 4 Shopping Cart
CodeIgniter 3's Cart class ported to CodeIgniter 4 β session-backed, dependency-free, drop-in.
English Β· TΓΌrkΓ§e
The Cart class lets you add items to a session that stays active while a user browses your site. Those items can then be retrieved and displayed in a standard "shopping cart" format, allowing the user to update quantities or remove items.
Important
The Cart class provides only the core cart functionality. It does not provide shipping, credit card authorization, or other order-processing components.
π Table of contents
- Requirements
- Installation
- Quick start
- Usage
- How it works
- Behaviour worth knowing
- API reference
- Example application
- License
π§ Requirements
| Requirement | Version | Note |
|---|---|---|
| PHP | ^8.2 |
|
| CodeIgniter | 4.x | The session() helper must be available |
| Session driver | database recommended | See below |
Warning
The Cart class stores its contents using CodeIgniter's Session class. Before using the library, create the session table described in the Session documentation and set your session preferences in .env to use the database.
π¦ Installation
composer require bertugfahriozer/ci4shoppingcart
The package ships only the library itself β no tests, tooling or sample code end up in your vendor/ directory. A complete working example lives in the repository under example/; copy it into your own project and adapt it however you like.
π Quick start
<?php namespace App\Controllers; use ci4shoppingcart\Libraries\Cart; class Product extends BaseController { protected Cart $cart; public function __construct() { $this->cart = new Cart(); } public function add() { $this->cart->insert([ 'id' => 'sku_123ABC', 'qty' => 1, 'price' => 39.95, 'name' => 'T-Shirt', ]); return redirect()->to('/cart'); } }
The Cart class loads and initialises the Session class for you. Unless you use sessions elsewhere in your application, you do not need to load Session separately.
π Usage
Adding an item to the cart
Pass an array containing the product information to insert():
$data = [ 'id' => 'sku_123ABC', 'qty' => 1, 'price' => 39.95, 'name' => 'T-Shirt', 'options' => ['Size' => 'L', 'Color' => 'Red'], ]; $rowid = $this->cart->insert($data);
The first four keys are required. If any of them is missing the data is not saved to the cart and the method returns FALSE.
| Key | Required | Type | Description |
|---|---|---|---|
id |
β | string | A unique identifier for each product in your store β typically an SKU. |
qty |
β | int|float | The quantity being purchased. A value of 0 inserts nothing. |
price |
β | int|float | The unit price of the item. |
name |
β | string | The name of the item. |
options |
β | array | Additional attributes that identify the product (size, colourβ¦). Included in the Row ID calculation. |
Caution
rowid and subtotal are reserved keys used internally by the Cart class. Do not use those names for your own data when inserting products.
Extra fields
Your array may contain any additional data you like; all of it is stored in the session. Standardising field names across products makes displaying the cart in a table much easier.
$data = [ 'id' => 'sku_123ABC', 'qty' => 1, 'price' => 39.95, 'name' => 'T-Shirt', 'coupon' => 'XMAS-50OFF', ]; $this->cart->insert($data);
Adding multiple items to the cart
Passing a multi-dimensional array adds several products in one call. This is useful when you let people pick from among several items on the same page.
$data = [ [ 'id' => 'sku_123ABC', 'qty' => 1, 'price' => 39.95, 'name' => 'T-Shirt', 'options' => ['Size' => 'L', 'Color' => 'Red'], ], [ 'id' => 'sku_567ZYX', 'qty' => 1, 'price' => 9.95, 'name' => 'Coffee Mug', ], [ 'id' => 'sku_965QRS', 'qty' => 1, 'price' => 29.95, 'name' => 'Shot Glass', ], ]; $this->cart->insert($data);
Note
The return value comes in two shapes: inserting a single item gives you that item's rowid (a string), while a multi-dimensional array returns TRUE if at least one item was inserted. If nothing could be inserted, both cases return FALSE. See Behaviour worth knowing for details.
Displaying the cart
Pass the cart object to your view:
public function yourMethod() { return view('path/view', ['cart' => $this->cart]); }
Then loop over it inside the view:
<form action="<?= route_to('yourRoute') ?>" method="post"> <table> <tr> <th>QTY</th> <th>Item description</th> <th style="text-align:right">Item price</th> <th style="text-align:right">Sub-total</th> </tr> <?php foreach ($cart->contents() as $items): ?> <tr> <td> <input type="hidden" name="rowid[]" value="<?= $items['rowid'] ?>"> <input type="number" name="qty[]" value="<?= $items['qty'] ?>"> </td> <td> <?= esc($items['name']) ?> <?php if ($cart->has_options($items['rowid'])): ?> <p> <?php foreach ($cart->product_options($items['rowid']) as $name => $value): ?> <strong><?= esc($name) ?>:</strong> <?= esc($value) ?><br> <?php endforeach; ?> </p> <?php endif; ?> </td> <td style="text-align:right">$<?= $cart->format_number($items['price']) ?></td> <td style="text-align:right">$<?= $cart->format_number($items['subtotal']) ?></td> </tr> <?php endforeach; ?> <tr> <td colspan="2"></td> <td style="text-align:right"><strong>Total</strong></td> <td style="text-align:right">$<?= $cart->format_number($cart->total()) ?></td> </tr> </table> <button type="submit">Save cart</button> </form>
Carrying each row's rowid through the form in a hidden field matters β updating depends on that value.
Updating the cart
Pass update() an array containing the rowid and whichever properties you want to change.
$this->cart->update([ 'rowid' => 'b99ccdf16028f015540f341130b6d8ec', 'qty' => 3, ]);
A multi-dimensional array updates several rows in one call:
$this->cart->update([ ['rowid' => 'b99ccdf16028f015540f341130b6d8ec', 'qty' => 3], ['rowid' => 'xw82g9q3r495893iajdh473990rikw23', 'qty' => 4], ['rowid' => 'fh4kdkkkaoe30njgoe92rkdkkobec333', 'qty' => 2], ]);
You may also update options, price, or any custom field you defined when inserting the item:
$this->cart->update([ 'rowid' => 'b99ccdf16028f015540f341130b6d8ec', 'qty' => 1, 'price' => 49.95, 'coupon' => null, ]);
Tip
Setting qty to 0 removes the item from the cart β you do not need to call remove() as well.
Removing items and emptying the cart
// Remove a single row $this->cart->remove($rowid); // Destroy the whole cart (the session key is cleared too) $this->cart->destroy();
βοΈ How it works
What is a Row ID
The Row ID is a unique identifier generated when an item is added to the cart. It exists so that identical products with different options can be managed as separate rows.
For example, say someone buys two identical t-shirts (same product ID) but in different sizes. The product ID and name are identical for both; the only difference is the size. The cart therefore needs a way to tell them apart, and it does so by deriving a "row ID" from the product ID and any options:
without options β rowid = md5(id)
with options β rowid = md5(id . serialize(options))
In practice you rarely touch the Row ID directly. It is enough that your "view cart" page carries the value in a hidden form field and passes it to update() when the form is submitted.
Where the cart is stored
The entire cart lives under a single session key: cart_contents. That array holds both the product rows β keyed by rowid β and the cart_total and total_items counters at the same level. contents() strips those two counters before handing the array back to you.
When the last item is removed, cart_contents is deleted from the session entirely.
Validation rules
Every insert() call validates the product ID and name. If validation fails the item is not inserted, an error is written to the log, and FALSE is returned.
| Field | Rule | Default pattern |
|---|---|---|
id |
Letters, digits, dots, dashes and underscores only | \.a-z0-9_- |
name |
Letters, digits, punctuation, symbols and spaces; 1β255 characters | \p{L}\p{N}\p{P}\p{S} + space |
name |
Control characters plus < and > are always rejected |
β |
qty |
Cast to float; an item with 0 is not inserted |
β |
price |
Cast to float |
β |
The patterns can be changed through the $product_id_rules and $product_name_rules properties. Setting $product_name_safe = FALSE disables name validation entirely β but that also disables the < and > rejection, so escaping output with esc() becomes your responsibility.
π§ Behaviour worth knowing
These behaviours are not obvious from the method names, but they do come up in practice.
insert() does not return a single type
Inserting a single item returns the rowid (a string); a multi-dimensional array returns TRUE. Both return FALSE on failure. Comparing the result with === TRUE therefore misbehaves for the single-item case β if you only want to know whether it succeeded, use a loose check:
if ($this->cart->insert($data)) { // success }
Re-inserting the same product adds to the quantity β but the existing quantity is truncated to an integer
Inserting a product that resolves to an existing rowid adds the new quantity on top of the current one. The current quantity is cast to int before that addition, so if you work in fractional quantities (selling by weight, for instance) the decimal part is lost.
$this->cart->insert(['id' => 'x', 'qty' => 1.5, 'price' => 10, 'name' => 'Cheese']); $this->cart->insert(['id' => 'x', 'qty' => 1, 'price' => 10, 'name' => 'Cheese']); // quantity becomes 2.0, not 2.5
If you deal in fractional quantities, write an absolute quantity with update() instead of inserting again.
update() only touches keys that already exist on the row
update() intersects the keys you pass with the keys already present on the row. That means you cannot add a new field to an item after the fact β it is silently ignored.
$this->cart->insert(['id' => 'x', 'qty' => 1, 'price' => 10, 'name' => 'Box']); $this->cart->update(['rowid' => $rowid, 'gift_note' => 'Birthday']); // gift_note is not written; it had to be defined during insert
update() cannot change id or name
Both are deliberately protected. If you need to change a product's name or ID, remove the row and insert it again β a different id means a different rowid anyway.
remove() returns TRUE even for a rowid that does not exist
remove() always returns TRUE; it does not tell you whether the row was actually there. If you need to confirm the removal happened, check with get_item() first.
Sub-totals are recalculated before you read them
Each row's subtotal, along with the cart's cart_total and total_items counters, is recalculated whenever the cart is written to the session. The subtotal you get from contents() is therefore always current β you do not have to compute it yourself.
π API reference
class Cart
Properties
| Property | Default | Description |
|---|---|---|
$product_id_rules |
\.a-z0-9_- |
Character class used to validate product IDs β letters, digits, dashes, underscores and dots. |
$product_name_rules |
\p{L}\p{N}\p{P}\p{S} |
Character class used to validate product names β Unicode letters, digits, punctuation, symbols and spaces. |
$product_name_safe |
TRUE |
Whether to allow only safe product names. |
Methods
insert([$items = []])
| Parameters | $items (array) β The item or items to insert into the cart |
| Returns | The rowid for a single item, TRUE for a multi-dimensional array, FALSE on failure |
| Return type | string|bool |
Inserts items into the cart and saves it to the session. The id, qty, price and name keys are required.
update([$items = []])
| Parameters | $items (array) β The row or rows to update |
| Returns | TRUE on success, FALSE on failure |
| Return type | bool |
Changes the properties of the row matching the given rowid. Typically called from the "view cart" page when a user changes quantities before checkout. A qty of 0 removes the row from the cart.
remove($rowid)
| Parameters | $rowid (string) β Row ID of the item to remove from the cart |
| Returns | TRUE in every case |
| Return type | bool |
Removes the row identified by $rowid from the cart.
total()
| Returns | The total amount in the cart |
| Return type | float |
Returns the total amount in the cart.
total_items()
| Returns | The total item count in the cart |
| Return type | float |
Returns the sum of the quantities across every row in the cart.
contents([$newest_first = FALSE])
| Parameters | $newest_first (bool) β Order the array with the newest items first |
| Returns | The cart contents |
| Return type | array |
Returns an array containing everything in the cart. Pass TRUE to sort from newest to oldest, otherwise it is sorted from oldest to newest. The cart_total and total_items counters are stripped from the result.
get_item($row_id)
| Parameters | $row_id (string) β Row ID to retrieve |
| Returns | The row data, or FALSE if no such row exists |
| Return type | array|bool |
Returns the data for the row matching the given Row ID. Also returns FALSE for cart_total and total_items.
has_options([$row_id = ''])
| Parameters | $row_id (string) β Row ID to inspect |
| Returns | TRUE if options exist, FALSE otherwise |
| Return type | bool |
Reports whether a particular row in the cart carries options. Designed to be used in a loop alongside contents().
product_options([$row_id = ''])
| Parameters | $row_id (string) β Row ID |
| Returns | The product options, or an empty array |
| Return type | array |
Returns the options for a particular product. Designed to be used in a loop alongside contents().
format_number([$n = ''])
| Parameters | $n (float|string) β The number to format |
| Returns | The number formatted with a thousands separator and two decimal places; '' in, '' out |
| Return type | string |
Formats a number as 1,234.57 β comma as the thousands separator, dot as the decimal point. Used when printing prices and sub-totals.
destroy()
| Return type | void |
Empties the cart and deletes the cart_contents key from the session. Usually called once the customer's order has been processed.
π§ͺ Example application
The example/ directory in the repository contains a working CodeIgniter 4 application that uses the library end to end: a product list, AJAX add-to-cart, quantity updates and emptying the cart. It is deliberately excluded from the Composer package, so browse or clone it from GitHub rather than looking for it in vendor/.
| File | Contents |
|---|---|
example/app/Controllers/Product.php |
Cart endpoints β insert, update, remove, destroy |
example/app/Controllers/BaseController.php |
Constructing the Cart object and passing it to views |
example/app/Views/products/basket.php |
The cart view |
example/public/basket.js |
The AJAX side |
π Project activity
π License
MIT β see LICENSE.
Release history is in CHANGELOG.md.
Developed by BertuΔ Fahri ΓZER Β· GitHub Β· Packagist