mirror of
https://github.com/kevin-DL/LaravelShoppingcart.git
synced 2026-01-22 23:25:23 +00:00
Large update for version 2.0
This commit is contained in:
503
src/Cart.php
Normal file
503
src/Cart.php
Normal file
@@ -0,0 +1,503 @@
|
||||
<?php
|
||||
|
||||
namespace Gloudemans\Shoppingcart;
|
||||
|
||||
use Closure;
|
||||
use Gloudemans\Shoppingcart\Exceptions\CartAlreadyStoredException;
|
||||
use Illuminate\Database\DatabaseManager;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Session\SessionManager;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Gloudemans\Shoppingcart\Contracts\Buyable;
|
||||
use Gloudemans\Shoppingcart\Exceptions\UnknownModelException;
|
||||
use Gloudemans\Shoppingcart\Exceptions\InvalidRowIDException;
|
||||
|
||||
class Cart
|
||||
{
|
||||
const DEFAULT_INSTANCE = 'default';
|
||||
|
||||
/**
|
||||
* Instance of the session manager.
|
||||
*
|
||||
* @var \Illuminate\Session\SessionManager
|
||||
*/
|
||||
private $session;
|
||||
|
||||
/**
|
||||
* Instance of the event dispatcher.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Events\Dispatcher
|
||||
*/
|
||||
private $events;
|
||||
|
||||
/**
|
||||
* Holds the current cart instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $instance;
|
||||
|
||||
/**
|
||||
* Cart constructor.
|
||||
*
|
||||
* @param \Illuminate\Session\SessionManager $session
|
||||
* @param \Illuminate\Contracts\Events\Dispatcher $events
|
||||
*/
|
||||
public function __construct(SessionManager $session, Dispatcher $events)
|
||||
{
|
||||
$this->session = $session;
|
||||
$this->events = $events;
|
||||
|
||||
$this->instance(self::DEFAULT_INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current cart instance.
|
||||
*
|
||||
* @param string|null $instance
|
||||
* @return \Gloudemans\Shoppingcart\Cart
|
||||
*/
|
||||
public function instance($instance = null)
|
||||
{
|
||||
$instance = $instance ?: self::DEFAULT_INSTANCE;
|
||||
|
||||
$this->instance = sprintf('%s.%s', 'cart', $instance);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current cart instance.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function currentInstance()
|
||||
{
|
||||
return str_replace('cart.', '', $this->instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item to the cart.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param mixed $name
|
||||
* @param int|float $qty
|
||||
* @param float $price
|
||||
* @param array $options
|
||||
* @return \Gloudemans\Shoppingcart\CartItem
|
||||
*/
|
||||
public function add($id, $name = null, $qty = null, $price = null, array $options = [])
|
||||
{
|
||||
if ($this->isMulti($id)) {
|
||||
return array_map(function ($item) {
|
||||
return $this->add($item);
|
||||
}, $id);
|
||||
}
|
||||
|
||||
$cartItem = $this->createCartItem($id, $name, $qty, $price, $options);
|
||||
|
||||
$content = $this->getContent();
|
||||
|
||||
if ($content->has($cartItem->rowId)) {
|
||||
$cartItem->qty++;
|
||||
}
|
||||
|
||||
$content->put($cartItem->rowId, $cartItem);
|
||||
|
||||
$this->events->fire('cart.added', $cartItem);
|
||||
|
||||
$this->session->put($this->instance, $content);
|
||||
|
||||
return $cartItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the cart item with the given rowId.
|
||||
*
|
||||
* @param string $rowId
|
||||
* @param mixed $qty
|
||||
* @return void
|
||||
*/
|
||||
public function update($rowId, $qty)
|
||||
{
|
||||
$cartItem = $this->get($rowId);
|
||||
|
||||
if ($qty instanceof Buyable) {
|
||||
$cartItem->updateFromBuyable($qty);
|
||||
} elseif (is_array($qty)) {
|
||||
$cartItem->updateFromArray($qty);
|
||||
} else {
|
||||
$cartItem->qty = $qty;
|
||||
}
|
||||
|
||||
$content = $this->getContent();
|
||||
|
||||
if ($rowId !== $cartItem->rowId) {
|
||||
$content->pull($rowId);
|
||||
|
||||
if ($content->has($cartItem->rowId)) {
|
||||
$existingCartItem = $this->get($cartItem->rowId);
|
||||
$cartItem->setQuantity($existingCartItem->qty + $cartItem->qty);
|
||||
}
|
||||
}
|
||||
|
||||
if ($cartItem->qty <= 0) {
|
||||
$this->remove($cartItem->rowId);
|
||||
return;
|
||||
} else {
|
||||
$content->put($cartItem->rowId, $cartItem);
|
||||
}
|
||||
|
||||
$this->events->fire('cart.updated', $cartItem);
|
||||
|
||||
$this->session->put($this->instance, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the cart item with the given rowId from the cart.
|
||||
*
|
||||
* @param string $rowId
|
||||
* @return void
|
||||
*/
|
||||
public function remove($rowId)
|
||||
{
|
||||
$cartItem = $this->get($rowId);
|
||||
|
||||
$content = $this->getContent();
|
||||
|
||||
$content->pull($cartItem->rowId);
|
||||
|
||||
$this->events->fire('cart.removed', $cartItem);
|
||||
|
||||
$this->session->put($this->instance, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a cart item from the cart by its rowId.
|
||||
*
|
||||
* @param string $rowId
|
||||
* @return \Gloudemans\Shoppingcart\CartItem
|
||||
*/
|
||||
public function get($rowId)
|
||||
{
|
||||
$content = $this->getContent();
|
||||
|
||||
if ( ! $content->has($rowId))
|
||||
throw new InvalidRowIDException("The cart does not contain rowId {$rowId}.");
|
||||
|
||||
return $content->get($rowId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the current cart instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function destroy()
|
||||
{
|
||||
$this->session->remove($this->instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content of the cart.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function content()
|
||||
{
|
||||
return $this->session->get($this->instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of items in the cart.
|
||||
*
|
||||
* @return int|float
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
$content = $this->getContent();
|
||||
|
||||
return $content->sum('qty');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total price of the items in the cart.
|
||||
*
|
||||
* @param int $decimals
|
||||
* @param string $decimalPoint
|
||||
* @param string $thousandSeperator
|
||||
* @return float
|
||||
*/
|
||||
public function total($decimals = 2, $decimalPoint = '.', $thousandSeperator = ',')
|
||||
{
|
||||
$content = $this->getContent();
|
||||
|
||||
$total = $content->reduce(function ($subTotal, CartItem $cartItem) {
|
||||
return $subTotal + ($cartItem->qty * $cartItem->price);
|
||||
}, 0);
|
||||
|
||||
return number_format($total, $decimals, $decimalPoint, $thousandSeperator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total tax of the items in the cart.
|
||||
*
|
||||
* @param int $decimals
|
||||
* @param string $decimalPoint
|
||||
* @param string $thousandSeperator
|
||||
* @return float
|
||||
*/
|
||||
public function tax($decimals = 2, $decimalPoint = '.', $thousandSeperator = ',')
|
||||
{
|
||||
$content = $this->getContent();
|
||||
|
||||
$tax = $content->reduce(function ($tax, CartItem $cartItem) {
|
||||
return $tax + ($cartItem->qty * $cartItem->tax);
|
||||
}, 0);
|
||||
|
||||
return number_format($tax, $decimals, $decimalPoint, $thousandSeperator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the subtotal (total - tax) of the items in the cart.
|
||||
*
|
||||
* @param int $decimals
|
||||
* @param string $decimalPoint
|
||||
* @param string $thousandSeperator
|
||||
* @return float
|
||||
*/
|
||||
public function subtotal($decimals = 2, $decimalPoint = '.', $thousandSeperator = ',')
|
||||
{
|
||||
$subtotal = $this->total - $this->tax;
|
||||
|
||||
return number_format($subtotal, $decimals, $decimalPoint, $thousandSeperator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the cart content for a cart item matching the given search closure.
|
||||
*
|
||||
* @param \Closure $search
|
||||
* @return \Gloudemans\Shoppingcart\CartItem|\Illuminate\Support\Collection
|
||||
*/
|
||||
public function search(Closure $search)
|
||||
{
|
||||
$content = $this->getContent();
|
||||
|
||||
$found = $content->filter($search);
|
||||
|
||||
if($found->count() === 1) return $found->first();
|
||||
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the cart item with the given rowId with the given model.
|
||||
*
|
||||
* @param string $rowId
|
||||
* @param mixed $model
|
||||
* @return void
|
||||
*/
|
||||
public function associate($rowId, $model)
|
||||
{
|
||||
if(is_string($model) && ! class_exists($model))
|
||||
throw new UnknownModelException("The supplied model {$model} does not exist.");
|
||||
|
||||
$cartItem = $this->get($rowId);
|
||||
|
||||
$cartItem->associate($model);
|
||||
|
||||
$content = $this->getContent();
|
||||
|
||||
$content->put($cartItem->rowId, $cartItem);
|
||||
|
||||
$this->session->put($this->instance, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tax rate for the cart item with the given rowId.
|
||||
*
|
||||
* @param string $rowId
|
||||
* @param int|float $taxRate
|
||||
* @return void
|
||||
*/
|
||||
public function setTax($rowId, $taxRate)
|
||||
{
|
||||
$cartItem = $this->get($rowId);
|
||||
|
||||
$cartItem->setTaxRate($taxRate);
|
||||
|
||||
$content = $this->getContent();
|
||||
|
||||
$content->put($cartItem->rowId, $cartItem);
|
||||
|
||||
$this->session->put($this->instance, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store an the current instance of the cart.
|
||||
*
|
||||
* @param mixed $identifier
|
||||
* @return void
|
||||
*/
|
||||
public function store($identifier)
|
||||
{
|
||||
$content = $this->getContent();
|
||||
|
||||
if ($this->storedCartWithIdentifierExists($identifier)) {
|
||||
throw new CartAlreadyStoredException("A cart with identifier {$identifier} was already stored.");
|
||||
}
|
||||
|
||||
$this->getConnection()->table($this->getTableName())->insert([
|
||||
'identifier' => $identifier,
|
||||
'instance' => $this->currentInstance(),
|
||||
'content' => serialize($content)
|
||||
]);
|
||||
|
||||
$this->events->fire('cart.stored');
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the cart with the given identifier.
|
||||
*
|
||||
* @param mixed $identifier
|
||||
* @return void
|
||||
*/
|
||||
public function restore($identifier)
|
||||
{
|
||||
if( ! $this->storedCartWithIdentifierExists($identifier)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stored = $this->getConnection()->table($this->getTableName())
|
||||
->where('identifier', $identifier)->first();
|
||||
|
||||
$storedContent = unserialize($stored->content);
|
||||
|
||||
$currentInstance = $this->currentInstance();
|
||||
|
||||
$this->instance($stored->instance);
|
||||
|
||||
$content = $this->getContent();
|
||||
|
||||
foreach ($storedContent as $cartItem) {
|
||||
$content->put($cartItem->rowId, $cartItem);
|
||||
}
|
||||
|
||||
$this->events->fire('cart.restored');
|
||||
|
||||
$this->session->put($this->instance, $content);
|
||||
|
||||
$this->instance($currentInstance);
|
||||
|
||||
$this->getConnection()->table($this->getTableName())
|
||||
->where('identifier', $identifier)->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to make accessing the total, tax and subtotal properties possible.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @return float|null
|
||||
*/
|
||||
public function __get($attribute)
|
||||
{
|
||||
if($attribute === 'total') {
|
||||
return $this->total(2, '.', '');
|
||||
}
|
||||
|
||||
if($attribute === 'tax') {
|
||||
return $this->tax(2, '.', '');
|
||||
}
|
||||
|
||||
if($attribute === 'subtotal') {
|
||||
return $this->subtotal(2, '.', '');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the carts content, if there is no cart content set yet, return a new empty Collection
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function getContent()
|
||||
{
|
||||
$content = $this->session->has($this->instance)
|
||||
? $this->session->get($this->instance)
|
||||
: new Collection;
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new CartItem from the supplied attributes.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param mixed $name
|
||||
* @param int|float $qty
|
||||
* @param float $price
|
||||
* @param array $options
|
||||
* @return \Gloudemans\Shoppingcart\CartItem
|
||||
*/
|
||||
private function createCartItem($id, $name, $qty, $price, array $options)
|
||||
{
|
||||
if ($id instanceof Buyable) {
|
||||
$cartItem = CartItem::fromBuyable($id, $qty ?: []);
|
||||
$cartItem->setQuantity($name ?: 1);
|
||||
$cartItem->associate($id);
|
||||
} elseif (is_array($id)) {
|
||||
$cartItem = CartItem::fromArray($id);
|
||||
$cartItem->setQuantity($id['qty']);
|
||||
} else {
|
||||
$cartItem = CartItem::fromAttributes($id, $name, $price, $options);
|
||||
$cartItem->setQuantity($qty);
|
||||
}
|
||||
|
||||
$cartItem->setTaxRate(config('cart.tax'));
|
||||
|
||||
return $cartItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the item is a multidimensional array or an array of Buyables.
|
||||
*
|
||||
* @param mixed $item
|
||||
* @return bool
|
||||
*/
|
||||
private function isMulti($item)
|
||||
{
|
||||
if ( ! is_array($item)) return false;
|
||||
|
||||
return is_array(head($item)) || head($item) instanceof Buyable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $identifier
|
||||
* @return bool
|
||||
*/
|
||||
private function storedCartWithIdentifierExists($identifier)
|
||||
{
|
||||
return $this->getConnection()->table($this->getTableName())->where('identifier', $identifier)->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database connection.
|
||||
*
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
private function getConnection()
|
||||
{
|
||||
return app(DatabaseManager::class)->connection(config('cart.database.connection', config('database.default')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database table name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getTableName()
|
||||
{
|
||||
return config('cart.database.table', 'shoppingcart');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user