Laravel Package Development Part 5 – Shopping Cart CRUD
Laravel Package Development ( 5 Lessons )
Maybe you’ve created a few apps with Laravel framework and now you want to create some re-usable code or a Laravel package. Where do you go to learn how to create a package? Yes, Right on this site!
see full series- Laravel Package Development Part 1 – Introduction
- Laravel Package Development Part 2 – Adding Service Provider
- Laravel Package Development Part 3 – Adding Configuration File
- Laravel Package Development Part 4 – Session Class Constructor
- Laravel Package Development Part 5 – Shopping Cart CRUD
This is the fifth part of our Laravel Package Development series. In this post, we will start adding the CRUD (Create, Read, Update, Delete) functionality to our shopping cart.
In the last post, we added the all()
function which will return all items from the shopping cart (at the moment it returns nothing).
Now, we need to add some helper functions and some core functionality functions to our Session class which is located at this location (packages/larashout/shopping-cart/src/Services/Session.php).
1. Creating Item Class
First thing, we need to alter the behaviour of the Laravel’s Collection class. We can do this by extending Illuminate\Support\Collection in our new class. So let’s create a new folder called Collection inside src
folder and then add a new PHP file in it, named Item.php
. Add the below code in this file to create a new class.
use Illuminate\Support\Collection; class Item extends Collection { // }
By adding the above code, we are creating a new class called Item and extending the Laravel’s Collection class so we have all the functions of core Collection class.
Now we will add a new property in this class called $model
like below:
protected $model;
Now there is a function in Laravel’s Collection class called __get()
, which return the value from collection based on the key or property. We will need to override this function to change its behavior.
Add the below function in your Item class.
public function __get($property) { if ($this->has($property)) { return $this->get($property); } // Below code will be used for the shopping cart when, // we change the storage type to the database. if (!$this->get('__model')) { return; } $model = $this->get('__model'); $class = explode('\\', $model); if (strtolower(end($class)) === $property) { $model = new $model(); return $model->find($this->id); } return; }
Let me explain line by line. Inside this function, we are first checking that, if the given $property
exist in the collection simply return the value for that property.
Important Note
Below explaination might not make any sense to you, but we are using this when we store the cart data in database.
Next, we are checking for a key __model
in the collection and simply returning nothing in case it doesn’t exist.
Next, assigning the value of __model
key to a variable called $model
and exploding the model (which can be App\\Product or anything else) to get the class name.
Next, we are checking if the $property
passed to __get()
method is equal to the model class we got in the above step. If yes, we are creating a new instance of the model and returning the row from the model.
That’s all for the above code. Now let’s add a new function in the Item class called rawId()
, which will return the rawId
for shopping cart.
public function rawId() { return $this->__raw_id; }
Above function is simply returning the __raw_id
from the collection. Once you have adding __get()
and rawId()
methods to Item class, whole class will look like below. Make sure you have the exactly same class as below.
namespace LaraShout\ShoppingCart\Collection; use Illuminate\Support\Collection; class Item extends Collection { /** * @var $model */ protected $model; /** * Get the property from Collection * * @param string $property * @return mixed|void */ public function __get($property) { if ($this->has($property)) { return $this->get($property); } if (!$this->get('__model')) { return; } $model = $this->get('__model'); $class = explode('\\', $model); if (strtolower(end($class)) === $property) { $model = new $model(); return $model->find($this->id); } return; } /** * Get RawId from Collection of Shopping Cart * * @return mixed|void */ public function rawId() { return $this->__raw_id; } }
Now add this Item class to our Session class like below:
use LaraShout\ShoppingCart\Collection\Item;
2. Adding Model Functions
As we are preparing our shopping cart class to store data in the database as well, we need to define some getter functions for the eloquent model.
Open your Session class (packages/larashout/shopping-cart/src/Services/Session.php) and add the below two functions right after the getName()
function which we added in the last post.
/** * Associate as Eloquent Model to Shopping Cart * * @param $model * @return $this * @throws Exception */ public function associate($model) { if (!class_exists($model)) { throw new Exception("Invalid model name '$model'."); } $this->model = $model; return $this; } /** * Get the associated Eloquent Model attached with Shopping Cart * * @return mixed */ public function getModel() { return $this->model; }
To explain the above functions, I have added a short description for each method in the code.
3. Adding New Item To Cart
Now we will start adding the CRUD functionality to the shopping cart.
get() Function
Firstly, we will add a get()
function, which will get the shopping cart row from the collection based on the rawID
.
/** * Get the row from Shopping Cart * * @param $rawId * @return Item|null */ public function get($rawId) { $row = $this->getCart()->get($rawId); return is_null($row) ? null : new Item($row); }
Above function, firstly get the shopping cart by using getCart()
function and then get the row based on rawId
. Behind the scene, this function is getting row using __get()
method from our collection class.
generateRawId() Function
Now, add another function called generateRawId()
which will simply generate the md5 hash id based on the $id
supplied. Add below code into Session class.
/** * Generate a Raw Id for Item rows (__raw_id) * * @param $id * @param $attributes * @return string */ protected function generateRawId($id, $attributes) { ksort($attributes); return md5($id.serialize($attributes)); }
add() Function
Now, we will add a new function called add()
which will be accessable using ShoppingCart::add()
method. Add below code to add this function.
/** * Add an Item to Shopping Cart * * @param $id * @param $name * @param $qty * @param $price * @param $attributes * @return bool|mixed * @throws Exception */ public function add($id, $name = null, $qty = null, $price = null, array $attributes = []) { $cart = $this->getCart(); $this->event->push('cart.adding', [$attributes, $cart]); $row = $this->addRow($id, $name, $qty, $price, $attributes); $this->event->push('cart.added', [$attributes, $cart]); return $row; }
Above function firstly get the shopping cart and then fire a new event called cart.adding
which we can catch in the controller or somewhere where you need to intercept the shopping cart functionality.
Then, we are calling another method addRow()
which will add the row to shopping cart. After that, we are again firing a new event called cart.added
.
addRow() Function
Now we will add the addRow()
function using below code.
/** * Add a row in Shopping Cart if exist then update it * * @param $id * @param $name * @param $qty * @param $price * @param array $attributes * @return bool|Item|mixed * @throws Exception */ protected function addRow($id, $name, $qty, $price, array $attributes = []) { if (!is_numeric($qty) || $qty < 1) { throw new Exception('Invalid quantity.'); } if (!is_numeric($price) || $price < 0) { throw new Exception('Invalid price.'); } $cart = $this->getCart(); $rawId = $this->generateRawId($id, $attributes); if ($row = $cart->get($rawId)) { $row = $this->updateQty($rawId, $row->qty + $qty); } else { $row = $this->insertRow($rawId, $id, $name, $qty, $price, $attributes); } return $row; }
This function will make some checks regarding $qty
and $price
and thow exception if it fails. Then getting the current shopping cart and generating a rawId
. Then we are checking if this rawId
exist in the shopping cart. If yes, we are calling updateQty()
function, if not then we are calling insertRow()
function.
Now, let’s add the above two functions we just used.
updateQty() Function
Here is the code for updateQty() function.
/** * Update quantity of a Row * * @param $rawId * @param $qty * @return bool|mixed */ protected function updateQty($rawId, $qty) { if ($qty <= 0) { return $this->remove($rawId); } return $this->updateRow($rawId, ['qty' => $qty]); }
This function calling two new methods remove()
and updateRow()
let’s add them.
remove() and updateRow() Functions
Here is the code for remove()
method.
/** * Remove an Item from Shopping Cart * * @param $rawId * @return bool */ public function remove($rawId) { if (!$row = $this->get($rawId)) { return true; } $cart = $this->getCart(); $this->event->push('cart.removing', [$row, $cart]); $cart->forget($rawId); $this->event->push('cart.removed', [$row, $cart]); $this->save($cart); return true; }
And here is the code for updateRow()
.
/** * Update whole row of Shopping Cart * * @param $rawId * @param array $attributes * @return mixed */ protected function updateRow($rawId, array $attributes) { $cart = $this->getCart(); $row = $cart->get($rawId); foreach ($attributes as $key => $value) { $row->put($key, $value); } if (count(array_intersect(array_keys($attributes), ['qty', 'price']))) { $row->put('total', $row->qty * $row->price); } $cart->put($rawId, $row); return $row; }
insertRow() Function
Now we will add insertRow()
function which we called in addRow()
function. Here is the code for insertRow()
function.
/** * Insert a new row in Shopping Cart * * @param $rawId * @param $id * @param $name * @param $qty * @param $price * @param array $attributes * @return Item */ protected function insertRow($rawId, $id, $name, $qty, $price, $attributes = []) { $newRow = $this->makeRow($rawId, $id, $name, $qty, $price, $attributes); $cart = $this->getCart(); $cart->put($rawId, $newRow); $this->save($cart); return $newRow; }
This function is calling a new function called makeRow()
, let’s add that as well.
/** * Make a new row in Shopping Cart * * @param $rawId * @param $id * @param $name * @param $qty * @param $price * @param array $attributes * @return Item */ protected function makeRow($rawId, $id, $name, $qty, $price, array $attributes = []) { return new Item(array_merge( [ '__raw_id' => $rawId, 'id' => $id, 'name' => $name, 'qty' => $qty, 'price' => $price, 'total' => $qty * $price, '__model' => $this->model, ], $attributes)); }
This function is creating a new instance of Item class, in other words, it is actually creating a collection using the values provided.
4. Updating Existing Item In Cart
To update an existing item, we will add a new function called update()
. Add the below function to Session class, we are working on.
/** * Update an Item in Shopping Cart * * @param $rawId * @param $attribute * @return bool|mixed * @throws Exception */ public function update($rawId, $attribute) { if (!$row = $this->get($rawId)) { throw new Exception('Item not found.'); } $cart = $this->getCart(); $this->event->push('cart.updating', [$row, $cart]); if (is_array($attribute)) { $raw = $this->updateAttribute($rawId, $attribute); } else { $raw = $this->updateQty($rawId, $attribute); } $this->event->push('cart.updated', [$row, $cart]); return $raw; }
updateAttribute() Function
In the above update()
function, we are checking if the attributes are an array then we are calling updateAttribute()
method, if not we are calling updateQty
function.
Below is the updateAttribute()
function which is simply calling updateRow()
method.
/** * Update selected attributes of Shopping Cart * * @param $rawId * @param $attributes * @return mixed */ protected function updateAttribute($rawId, $attributes) { return $this->updateRow($rawId, $attributes); }
save() Function
Finally, we have a save()
function, which is just storing the shopping cart into session.
/** * Save record to Session * * @param $cart * @return mixed */ protected function save($cart) { $this->session->put($this->name, $cart); return $cart; }
5. Removing Items From Cart
Below is the destroy()
method, which gets the current shopping cart and pushes the null value to flush the whole shopping cart.
/** * Destroy the current Shopping Cart * * @return bool */ public function destroy() { $cart = $this->getCart(); $this->event->push('cart.destroying', $cart); $this->save(null); $this->event->push('cart.destroyed', $cart); return true; }
clean() Function
This function is just an alias to destroy()
function.
/** * An alias to ShoppingCart::destroy(); */ public function clean() { $this->destroy(); }
6. Utility Functions For Cart
Below are the utility functions for our shopping cart, providing various functionality like getting total of the shopping cart, getting the total price, getting cart count, getting the number of rows and searching in the shopping cart.
All functions are self-explanatory, just go through them.
total() Function
/** * Return Total of Shopping Cart * * @return int */ public function total() { return $this->totalPrice(); }
totalPrice() Function
/** * Calculate totals for all Items in Shopping Cart * * @return int */ public function totalPrice() { $total = 0; $cart = $this->getCart(); if ($cart->isEmpty()) { return $total; } foreach ($cart as $row) { $total += $row->qty * $row->price; } return $total; }
isEmpty() Function
/** * Check if Shopping Cart is empty * * @return bool */ public function isEmpty() { return $this->count() <= 0; }
count() Function
/** * Returns the quantity of all items * * @param bool $totalItems * @return int */ public function count($totalItems = true) { $items = $this->getCart(); if (!$totalItems) { return $items->count(); } $count = 0; foreach ($items as $row) { $count += $row->qty; } return $count; }
countRows() Function
/** * Return the number of rows * * @return int */ public function countRows() { return $this->count(false); }
search() Function
/** * Search items by property * * @param array $search * @return Collection */ public function search(array $search) { $rows = new Collection(); if (empty($search)) { return $rows; } foreach ($this->getCart() as $item) { if (array_intersect_assoc($item->intersect($search)->toArray(), $search)) { $rows->put($item->__raw_id, $item); } } return $rows; }
7. Using Shopping Cart
See below, how we can use our shopping cart functions to add, delete and update shopping cart.
// Adding Item To Shopping Cart $row = ShoppingCart::add(PLZ975778, 'Product Name', 5, 100.00, ['color' => 'red', 'size' => 'M']); // Update Item ShoppingCart::update(string $rawId, int $quantity); ShoppingCart::update(string $rawId, array $arrtibutes); // Example ShoppingCart::update('18712b696dbee7d1022050a35f07abb8', ['name' => 'New product name'); ShoppingCart::update('18712b696dbee7d1022050a35f07abb8', 5); // For Updating Options ShoppingCart::update('8a26bdc6d238ca3bb3bc771915b900e1', ['options' => ['color' => 'Green', 'size' => 'L']]); // Getting all items in Shopping Cart ShoppingCart::all(); // Remove the specified item by raw ID. ShoppingCart::remove($rawId); // example ShoppingCart::remove('8a48aa7c8e5202841ddaf767bb4d10da'); // Destroy Shopping Cart ShoppingCart::destroy(); ShoppingCart::clean(); // alias of destroy(); // Total price // Returns the total of all items. ShoppingCart::total(); // alias of totalPrice(); ShoppingCart::totalPrice(); // Check empty ShoppingCart::isEmpty(); // Count rows // Return the number of rows based on rows. ShoppingCart::countRows(); // Count quantity // Returns the quantity of all items ShoppingCart::count($totalItems = true); // $totalItems : When false,will return the number of rows. // Search items // Search items by property. ShoppingCart::search(array $conditions); // example $items = ShoppingCart::search(['name' => 'Item name']);
8. Shopping Cart Events
There are quite a few events which will be fired when you are working with ShoppingCart. If you want to do any customization in between the operations of ShoppingCart, you can fire these events to add your extra bit of logic.
/* | Event Name | Parameters | | ------------------- |:-------------: | | cart.adding | $attributes, $cart | | cart.added | $attributes, $cart | | cart.updating | $row, $cart | | cart.updated | $row, $cart | | cart.updated | $row, $cart | | cart.removing | $row, $cart | | cart.removed | $row, $cart | | cart.destroying | $cart | | cart.cart.destroyed | $cart | */
Above events can be handled easily like below.
Event::on('cart.adding', function($attributes, $cart){ // Your Code });
9. Code Repository
You can find the complete code of this post on Shopping Cart Repository.
If you have any question about this post, please leave them in the comments box below and I will answer them promptly.