Laravel Package Development Part 5 – Shopping Cart CRUD

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
  1. Laravel Package Development Part 1 – Introduction
  2. Laravel Package Development Part 2 – Adding Service Provider
  3. Laravel Package Development Part 3 – Adding Configuration File
  4. Laravel Package Development Part 4 – Session Class Constructor
  5. 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).

This post is very lengthy, grab a cup of coffee and read it.

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.

Your little help will keep this site alive and help us to produce quality content for you.