juliobitencourt/laravel-cart

View on GitHub
src/Cart.php

Summary

Maintainability
B
5 hrs
Test Coverage
<?php

namespace JulioBitencourt\Cart;

use JulioBitencourt\Cart\Storage\StorageInterface as Storage;

class Cart implements CartInterface
{
    protected $storage;

    protected static $cart = [];

    /**
     * Create a new Cart instance.
     */
    public function __construct(Storage $storage)
    {
        $this->storage = $storage;

        $cart = $this->storage->get();
        if ($cart) {
            static::$cart = $cart;
        }
    }

    /**
     * Check if the item exists in the cart
     * If it exists, increments the item
     * Otherwise. Creates a new one.
     *
     * @param array $data
     *
     * @return array
     */
    public function insert($data = [])
    {
        // First we check if the data is an array of items
        // If not, we create an array to iterate in.
        if (isset($data['sku'])) {
            $data = array($data);
        }

        $items = $this->processItems($data);

        if (count($items) == 0) {
            return false;
        }

        return count($items) == 1 ? $items[0] : $items;
    }

    /**
     * Add a Child to an item.
     *
     * @return array
     */
    public function insertChild($parentId, $data = [])
    {
        $itemKey = $this->findBy('id', $parentId);

        if ($itemKey === false) {
            return false;
        }

        $parentDescription = static::$cart[$itemKey]['description'];

        $data = array_merge(
            ['parent_id' => $parentId, 'parent_description' => $parentDescription],
            $data
        );

        return $this->insert($data);
    }

    /**
     * Update an item.
     *
     * @param int $id
     * @param int $quantity
     *
     * @return bool
     */
    public function update($id, $quantity)
    {
        $this->validate(['quantity' => $quantity]);

        $itemKey = $this->findBy('id', $id);

        if ($itemKey === false) {
            return false;
        }

        $this->updateOrDelete($itemKey, $quantity);

        return true;
    }

    /**
     * Remove an item.
     *
     * @param int $id
     */
    public function delete($id)
    {
        $itemKey = $this->findBy('id', $id);

        if ($itemKey === false) {
            return false;
        }

        unset(static::$cart[$itemKey]);

        $this->deleteChild($id);

        $this->storage->delete($itemKey);
    }

    /**
     * Destroy the cart.
     */
    public function destroy()
    {
        static::$cart = [];
        $this->storage->destroy();
    }

    /**
     * Check if the cart is empty.
     *
     * @return bool
     */
    public function isEmpty()
    {
        return count(static::$cart) == 0;
    }

    /**
     * Check a list with the cart items.
     */
    public function all()
    {
        return static::$cart;
    }

    /**
     * Sum the price of the items in the cart.
     *
     * @return float
     */
    public function total()
    {
        $amount = 0;

        foreach (static::$cart as $item) {
            $amount += $item['price'] * $item['quantity'];
        }

        return $amount;
    }

    /**
     * Sum the total items in the cart.
     *
     * @return int
     */
    public function totalItems()
    {
        $count = 0;

        foreach (static::$cart as $item) {
            $count += $item['quantity'];
        }

        return $count;
    }

    /**
     * Set the email for the cart
     * @param string $email
     *
     * @return void
     */
    public function setEmail($email)
    {
        $this->validate(['email' => $email]);

        static::$cart['email'] = $email;
        $this->storage->setEmail($email);
    }

    /**
     * Check if an item exists in the collection.
     * If so, returns the key.
     *
     * @return bool
     */
    protected function findBy($field, $value)
    {
        foreach (static::$cart as $key => $item) {
            if (array_key_exists($field, $item) && $item[$field] == $value) {
                return $key;
            }
        }

        return false;
    }

    /**
     * Process the list of items to insert in the cart.
     *
     * @return array
     */
    protected function processItems($data)
    {
        $items = [];

        foreach ($data as $item) {
            $this->validate($item);

            $itemKey = $this->findBy('sku', $item['sku']);

            if ($itemKey === false) {
                $items[] = $this->createItem($item);
            } else {
                $items[] = $this->incrementItem($itemKey, $item['quantity']);
            }
        }

        return $items;
    }

    /**
     * If the item already exists in the cart, updates the quantity.
     */
    protected function incrementItem($itemKey, $quantity)
    {
        $quantity = static::$cart[$itemKey]['quantity'] + $quantity;
        $this->updateOrDelete($itemKey, $quantity);
    }

    /**
     * Creates a new item.
     *
     * @return array
     */
    protected function createItem($data = [])
    {
        $data = array_merge(
            ['id' => $this->hash($data['sku'])],
            $data
        );
        static::$cart[] = $data;
        $this->storage->insert($data);

        return $data;
    }

    /**
     * Update the item quantity or remove the item
     * if the quantity is zero.
     */
    protected function updateOrDelete($itemKey, $quantity)
    {
        if ($quantity == 0) {
            unset(static::$cart[$itemKey]);
            $this->storage->delete($itemKey);
        } else {
            static::$cart[$itemKey]['quantity'] = $quantity;
            $this->storage->update($itemKey, $quantity);
        }
    }

    protected function deleteChild($parentId)
    {
        $itemKey = $this->findBy('parent_id', $parentId);

        if ($itemKey === false) {
            return false;
        }

        $childId = static::$cart[$itemKey]['id'];

        $this->delete($childId);
    }

    /**
     * Creates a hash.
     *
     * @return string
     */
    protected function hash($value)
    {
        return md5($value);
    }

    /**
     * Validate the item.
     *
     * @param array $fields
     *
     * @throws \InvalidArgumentException
     */
    protected function validate($fields)
    {
        foreach ($fields as $field => $value) {
            if ($field == 'parent_id' || $field == 'parent_description') {
                continue;
            }

            switch ($field) {
                case 'sku':
                    if (empty($value)) {
                        throw new \InvalidArgumentException('Invalid SKU for the item');
                    }
                    break;
                case 'description':
                    if (empty($value)) {
                        throw new \InvalidArgumentException('Invalid Description for the item');
                    }
                    break;
                case 'quantity':
                    if (!preg_match('/^[+]?\d+([.]\d+)?$/', $value)) {
                        throw new \InvalidArgumentException('Invalid Quantity for the item');
                    }
                    break;
                case 'price':
                    if (!preg_match('/^-?(?:\d+|\d*\.\d+)$/', $value)) {
                        throw new \InvalidArgumentException('Invalid Price for the item');
                    }
                    break;
                case 'options':
                    if (!is_array($value)) {
                        throw new \InvalidArgumentException('Invalid Options for the item');
                    }
                    break;
                case 'email':
                    if ( ! filter_var($value, FILTER_VALIDATE_EMAIL)) {
                        throw new \InvalidArgumentException('Invalid Email Address');
                    }
                    break;
                default:
                    throw new \InvalidArgumentException();
                    break;
            }
        }
    }
}