Merge pull request #146 from bumbummen99/refactor-moneyphp

Add MoneyPHP
This commit is contained in:
Patrick
2022-02-06 19:31:38 +01:00
committed by GitHub
22 changed files with 541 additions and 1323 deletions

View File

@@ -22,34 +22,27 @@ jobs:
env:
PHP_VERSION: ${{ matrix.php-versions }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: bcmath,gmp,intl
env:
runner: ubuntu-18.04
- name: Use appropiate Laravel version
- name: Checkout
uses: actions/checkout@v2
- name: Use appropiate Laravel version in composer.json (without installing)
run: |
composer require "laravel/framework:${{ matrix.laravel-version }}" --no-interaction --no-update
composer require --no-update --no-interaction "laravel/framework:${{ matrix.laravel-version }}"
composer validate
- name: Validate composer.json and composer.lock
run: composer validate
- name: Get composer cache directory
id: composercache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache Composer packages
id: composer-cache
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composercache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
path: ~/.composer/cache/files
key: dependencies-laravel-${{ matrix.laravel-version }}-php-${{ matrix.php-version }}-composer-${{ hashFiles('composer.json') }}
- name: Install composer packages
run: composer install --prefer-dist --no-interaction

3
.gitignore vendored
View File

@@ -3,4 +3,5 @@ composer.phar
composer.lock
.DS_Store
coverage.xml
.phpunit.result.cache
.phpunit.result.cache
/.vscode

View File

@@ -19,8 +19,6 @@ Run the Composer require command from the Terminal:
Now you're ready to start using the shoppingcart in your application.
**As of version 2 of this package it's possibly to use dependency injection to inject an instance of the Cart class into your controller or other class**
You definitely should publish the `config` file and take a look at it.
php artisan vendor:publish --provider="Gloudemans\Shoppingcart\ShoppingcartServiceProvider" --tag="config"
@@ -611,7 +609,7 @@ class DefaultCalculator implements Calculator
{
public static function getAttribute(string $attribute, CartItem $cartItem)
{
$decimals = config('cart.format.decimals', 2);
$decimals = Config::get('cart.format.decimals', 2);
switch ($attribute) {
case 'discount':

View File

@@ -20,7 +20,9 @@
"illuminate/support": "^6.0|^7.0|^8.0|^9.0",
"illuminate/session": "^6.0|^7.0|^8.0|^9.0",
"illuminate/events": "^6.0|^7.0|^8.0|^9.0",
"nesbot/carbon": "^2.0"
"illuminate/database": "^6.0|^7.0|^8.0|^9.0",
"nesbot/carbon": "^2.0",
"moneyphp/money": "^4.0.0"
},
"require-dev": {
"phpunit/phpunit": "~8.0|~9.0",

View File

@@ -1,37 +0,0 @@
<?php
namespace Gloudemans\Shoppingcart\Calculation;
use Gloudemans\Shoppingcart\CartItem;
use Gloudemans\Shoppingcart\Contracts\Calculator;
class DefaultCalculator implements Calculator
{
public static function getAttribute(string $attribute, CartItem $cartItem)
{
$decimals = config('cart.format.decimals', 2);
switch ($attribute) {
case 'discount':
return $cartItem->price * ($cartItem->getDiscountRate() / 100);
case 'tax':
return round($cartItem->priceTarget * ($cartItem->taxRate / 100), $decimals);
case 'priceTax':
return round($cartItem->priceTarget + $cartItem->tax, $decimals);
case 'discountTotal':
return round($cartItem->discount * $cartItem->qty, $decimals);
case 'priceTotal':
return round($cartItem->price * $cartItem->qty, $decimals);
case 'subtotal':
return max(round($cartItem->priceTotal - $cartItem->discountTotal, $decimals), 0);
case 'priceTarget':
return round(($cartItem->priceTotal - $cartItem->discountTotal) / $cartItem->qty, $decimals);
case 'taxTotal':
return round($cartItem->subtotal * ($cartItem->taxRate / 100), $decimals);
case 'total':
return round($cartItem->subtotal + $cartItem->taxTotal, $decimals);
default:
return;
}
}
}

View File

@@ -1,39 +0,0 @@
<?php
namespace Gloudemans\Shoppingcart\Calculation;
use Gloudemans\Shoppingcart\CartItem;
use Gloudemans\Shoppingcart\Contracts\Calculator;
class GrossPrice implements Calculator
{
public static function getAttribute(string $attribute, CartItem $cartItem)
{
$decimals = config('cart.format.decimals', 2);
switch ($attribute) {
case 'priceNet':
return round($cartItem->price / (1 + ($cartItem->taxRate / 100)), $decimals);
case 'discount':
return $cartItem->priceNet * ($cartItem->getDiscountRate() / 100);
case 'tax':
return round($cartItem->priceTarget * ($cartItem->taxRate / 100), $decimals);
case 'priceTax':
return round($cartItem->priceTarget + $cartItem->tax, $decimals);
case 'discountTotal':
return round($cartItem->discount * $cartItem->qty, $decimals);
case 'priceTotal':
return round($cartItem->priceNet * $cartItem->qty, $decimals);
case 'subtotal':
return max(round($cartItem->priceTotal - $cartItem->discountTotal, $decimals), 0);
case 'priceTarget':
return round(($cartItem->priceTotal - $cartItem->discountTotal) / $cartItem->qty, $decimals);
case 'taxTotal':
return round($cartItem->subtotal * ($cartItem->taxRate / 100), $decimals);
case 'total':
return round($cartItem->subtotal + $cartItem->taxTotal, $decimals);
default:
return;
}
}
}

View File

@@ -2,6 +2,9 @@
namespace Gloudemans\Shoppingcart;
use Money\Currency;
use Money\Money;
trait CanBeBought
{
/**
@@ -9,47 +12,41 @@ trait CanBeBought
*
* @return int|string
*/
public function getBuyableIdentifier()
public function getBuyableIdentifier(CartItemOptions $options)
{
return method_exists($this, 'getKey') ? $this->getKey() : $this->id;
}
/**
* Get the name, title or description of the Buyable item.
*
* @return string
*/
public function getBuyableDescription() : ?string
public function getBuyableDescription(CartItemOptions $options): ?string
{
if (($name = $this->getAttribute('name'))) {
return $name;
} else if (($title = $this->getAttribute('title'))) {
} elseif (($title = $this->getAttribute('title'))) {
return $title;
} else if (($description = $this->getAttribute('description'))) {
} elseif (($description = $this->getAttribute('description'))) {
return $description;
} else {
return null;
}
}
}
/**
* Get the price of the Buyable item.
*
* @return float
*/
public function getBuyablePrice()
public function getBuyablePrice(CartItemOptions $options): Money
{
if (($price = $this->getAttribute('price'))) {
return $price;
if (($price = $this->getAttribute('price')) && ($currency = $this->getAttribute('currency'))) {
return new Money($price, new Currency($currency));
}
}
/**
* Get the weight of the Buyable item.
*
* @return float
*/
public function getBuyableWeight()
public function getBuyableWeight(CartItemOptions $options): int
{
if (($weight = $this->getAttribute('weight'))) {
return $weight;

View File

@@ -11,9 +11,14 @@ use Gloudemans\Shoppingcart\Exceptions\InvalidRowIDException;
use Gloudemans\Shoppingcart\Exceptions\UnknownModelException;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\DatabaseManager;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Session\SessionManager;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Traits\Macroable;
use InvalidArgumentException;
use Money\Currency;
use Money\Money;
class Cart
{
@@ -33,31 +38,23 @@ class Cart
/**
* Holds the current cart instance.
*
* @var string
*/
private string $instance;
/**
* Holds the creation date of the cart.
*
* @var mixed
*/
private $createdAt;
private ?Carbon $createdAt = null;
/**
* Holds the update date of the cart.
*
* @var mixed
*/
private $updatedAt;
private ?Carbon $updatedAt = null;
/**
* Defines the discount percentage.
*
* @var float
*/
private $discount = 0;
private float $discount = 0;
/**
* Defines the tax rate.
@@ -76,7 +73,7 @@ class Cart
{
$this->session = $session;
$this->events = $events;
$this->taxRate = config('cart.tax');
$this->taxRate = Config::get('cart.tax');
$this->instance(self::DEFAULT_INSTANCE);
}
@@ -88,7 +85,7 @@ class Cart
*
* @return \Gloudemans\Shoppingcart\Cart
*/
public function instance($instance = null)
public function instance($instance = null): self
{
$instance = $instance ?: self::DEFAULT_INSTANCE;
@@ -107,34 +104,65 @@ class Cart
*
* @return string
*/
public function currentInstance()
public function currentInstance(): string
{
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 float $weight
* @param array $options
*
* @return \Gloudemans\Shoppingcart\CartItem
*/
public function add($id, ?string $name = null, $qty = null, $price = null, $weight = 0, array $options = [])
public function add(int|string|Buyable|array $id, null|string|int $nameOrQty = null, null|int|CartItemOptions $qtyOrOptions = null, ?Money $price = null, ?int $weight = null, ?CartItemOptions $options = null): CartItem|array
{
if ($this->isMulti($id)) {
return array_map(function ($item) {
return $this->add($item);
}, $id);
/* Allow adding a CartItem by raw parameters */
if (is_int($id) || is_string($id)) {
if (!is_null($nameOrQty) && !is_string($nameOrQty)) {
throw new InvalidArgumentException('$nameOrQty must be of type string (name) or null when adding with raw parameters');
}
if (!is_null($qtyOrOptions) && !is_int($qtyOrOptions)) {
throw new InvalidArgumentException('$nameOrQty must be of type int (quantity) or null when adding with raw parameters');
}
return $this->addCartItem(CartItem::fromAttributes($id, $nameOrQty, $price, $qtyOrOptions ?: 1, $weight ?: 0, $options ?: new CartItemOptions([])));
}
/* Also allow passing a Buyable instance, get data from the instance rather than parameters */
elseif ($id instanceof Buyable) {
if (!is_null($qtyOrOptions) && !is_int($nameOrQty)) {
throw new InvalidArgumentException('$nameOrQty must be of type int (quantity) when adding a Buyable instance');
}
$cartItem = $this->createCartItem($id, $name, $qty, $price, $weight, $options);
if (!is_null($qtyOrOptions) && !$qtyOrOptions instanceof CartItemOptions) {
throw new InvalidArgumentException('$qtyOrOptions must be of type CartItemOptions (options) or null when adding a Buyable instance');
}
return $this->addCartItem($cartItem);
$cartItem = CartItem::fromBuyable($id, $nameOrQty ?: 1, $qtyOrOptions ?: new CartItemOptions([]));
if ($id instanceof Model) {
$cartItem->associate($id);
}
return $this->addCartItem($cartItem);
}
/* Also allow passing multiple definitions at the same time, simply call same method and collec return value */
elseif (is_array($id)) {
/* Check if this iterable contains instances */
if (is_array(head($id)) || head($id) instanceof Buyable) {
return array_map(function (Buyable|iterable $item) {
return $this->add($item);
}, $id);
}
/* Treat the array itself as an instance */
else {
$cartItem = CartItem::fromArray($id);
return $this->addCartItem($cartItem);
}
}
/* Due to PHP8 union types this should never happen */
else {
throw new InvalidArgumentException('$id must be of type int|string|Buyable|Iterable');
}
}
/**
@@ -147,10 +175,12 @@ class Cart
*
* @return \Gloudemans\Shoppingcart\CartItem The CartItem
*/
public function addCartItem(CartItem $item, bool $keepDiscount = false, bool $keepTax = false, bool $dispatchEvent = true)
public function addCartItem(CartItem $item, bool $keepDiscount = false, bool $keepTax = false, bool $dispatchEvent = true): CartItem
{
$item->setInstance($this->currentInstance());
if (!$keepDiscount) {
$item->setDiscountRate($this->discount);
$item->setDiscount($this->discount);
}
if (!$keepTax) {
@@ -186,7 +216,7 @@ class Cart
*
* @return \Gloudemans\Shoppingcart\CartItem
*/
public function update(string $rowId, $qty)
public function update(string $rowId, $qty): ?CartItem
{
$cartItem = $this->get($rowId);
@@ -214,7 +244,7 @@ class Cart
if ($cartItem->qty <= 0) {
$this->remove($cartItem->rowId);
return;
return null;
} else {
if (isset($itemOldIndex)) {
$content = $content->slice(0, $itemOldIndex)
@@ -241,7 +271,7 @@ class Cart
*
* @return void
*/
public function remove(string $rowId)
public function remove(string $rowId): void
{
$cartItem = $this->get($rowId);
@@ -263,7 +293,7 @@ class Cart
*
* @return \Gloudemans\Shoppingcart\CartItem
*/
public function get(string $rowId)
public function get(string $rowId): CartItem
{
$content = $this->getContent();
@@ -271,7 +301,11 @@ class Cart
throw new InvalidRowIDException("The cart does not contain rowId {$rowId}.");
}
return $content->get($rowId);
$cartItem = $content->get($rowId);
if ($cartItem instanceof CartItem) {
return $cartItem;
}
}
/**
@@ -279,7 +313,7 @@ class Cart
*
* @return void
*/
public function destroy()
public function destroy(): void
{
$this->session->remove($this->instance);
}
@@ -289,7 +323,7 @@ class Cart
*
* @return \Illuminate\Support\Collection
*/
public function content()
public function content(): Collection
{
if (is_null($this->session->get($this->instance))) {
return new Collection([]);
@@ -300,10 +334,8 @@ class Cart
/**
* Get the total quantity of all CartItems in the cart.
*
* @return int|float
*/
public function count()
public function count(): int
{
return $this->getContent()->sum('qty');
}
@@ -312,69 +344,27 @@ class Cart
* Get the amount of CartItems in the Cart.
* Keep in mind that this does NOT count quantity.
*/
public function countItems() : int
public function countItems(): int
{
return $this->getContent()->count();
}
/**
* Get the total price of the items in the cart.
*/
public function totalFloat() : float
{
return $this->getContent()->reduce(function ($total, CartItem $cartItem) {
return $total + $cartItem->total;
}, 0);
}
/**
* Get the total price of the items in the cart as formatted string.
*/
public function total(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null) : string
{
return $this->numberFormat($this->totalFloat(), $decimals, $decimalPoint, $thousandSeperator);
}
/**
* Get the total tax of the items in the cart.
*/
public function taxFloat() : float
{
return $this->getContent()->reduce(function ($tax, CartItem $cartItem) {
return $tax + $cartItem->taxTotal;
}, 0);
}
/**
* Get the total tax of the items in the cart as formatted string.
*/
public function tax(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null) : string
{
return $this->numberFormat($this->taxFloat(), $decimals, $decimalPoint, $thousandSeperator);
}
/**
* Get the subtotal (total - tax) of the items in the cart.
*/
public function subtotalFloat() : float
{
return $this->getContent()->reduce(function ($subTotal, CartItem $cartItem) {
return $subTotal + $cartItem->subtotal;
}, 0);
}
/**
* Get the subtotal (total - tax) of the items in the cart as formatted string.
* Get the discount of the items in the cart.
*
* @param int $decimals
* @param string $decimalPoint
* @param string $thousandSeperator
*
* @return string
* @return Money
*/
public function subtotal(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null) : string
public function price(): Money
{
return $this->numberFormat($this->subtotalFloat(), $decimals, $decimalPoint, $thousandSeperator);
$calculated = $this->getContent()->reduce(function (Money $discount, CartItem $cartItem) {
return $discount->add($cartItem->price());
}, new Money(0, new Currency('USD')));
if ($calculated instanceof Money) {
return $calculated;
} else {
throw new \TypeError('Calculated price is not an instance of Money');
}
}
/**
@@ -382,79 +372,79 @@ class Cart
*
* @return float
*/
public function discountFloat() : float
public function discount(): Money
{
return $this->getContent()->reduce(function ($discount, CartItem $cartItem) {
return $discount + $cartItem->discountTotal;
}, 0);
$calculated = $this->getContent()->reduce(function (Money $discount, CartItem $cartItem) {
return $discount->add($cartItem->discount());
}, new Money(0, new Currency('USD')));
if ($calculated instanceof Money) {
return $calculated;
} else {
throw new \TypeError('Calculated discount is not an instance of Money');
}
}
/**
* Get the discount of the items in the cart as formatted string.
* Get the subtotal (total - tax) of the items in the cart.
*/
public function discount(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null) : string
public function subtotal(): Money
{
return $this->numberFormat($this->discountFloat(), $decimals, $decimalPoint, $thousandSeperator);
$calculated = $this->getContent()->reduce(function (Money $subTotal, CartItem $cartItem) {
return $subTotal->add($cartItem->subtotal());
}, new Money(0, new Currency('USD')));
if ($calculated instanceof Money) {
return $calculated;
} else {
throw new \TypeError('Calculated subtotal is not an instance of Money');
}
}
/**
* Get the price of the items in the cart (not rounded).
* Get the total tax of the items in the cart.
*/
public function initialFloat() : float
public function tax(): Money
{
return $this->getContent()->reduce(function ($initial, CartItem $cartItem) {
return $initial + ($cartItem->qty * $cartItem->price);
}, 0);
$calculated = $this->getContent()->reduce(function (Money $tax, CartItem $cartItem) {
return $tax->add($cartItem->tax());
}, new Money(0, new Currency('USD')));
if ($calculated instanceof Money) {
return $calculated;
}
}
/**
* Get the price of the items in the cart as formatted string.
* Get the total price of the items in the cart.
*/
public function initial(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null) : string
public function total(): Money
{
return $this->numberFormat($this->initialFloat(), $decimals, $decimalPoint, $thousandSeperator);
}
$calculated = $this->getContent()->reduce(function (Money $total, CartItem $cartItem) {
return $total->add($cartItem->total());
}, new Money(0, new Currency('USD')));
/**
* Get the price of the items in the cart (previously rounded).
*/
public function priceTotalFloat() : float
{
return $this->getContent()->reduce(function ($initial, CartItem $cartItem) {
return $initial + $cartItem->priceTotal;
}, 0);
}
/**
* Get the price of the items in the cart as formatted string.
*/
public function priceTotal(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null) : string
{
return $this->numberFormat($this->priceTotalFloat(), $decimals, $decimalPoint, $thousandSeperator);
if ($calculated instanceof Money) {
return $calculated;
} else {
throw new \TypeError('Calculated total is not an instance of Money');
}
}
/**
* Get the total weight of the items in the cart.
*/
public function weightFloat() : float
public function weight(): int
{
return $this->getContent()->reduce(function ($total, CartItem $cartItem) {
return $total + ($cartItem->qty * $cartItem->weight);
$calculated = $this->getContent()->reduce(function (int $total, CartItem $cartItem) {
return $total + $cartItem->weight();
}, 0);
}
/**
* Get the total weight of the items in the cart.
*
* @param int $decimals
* @param string $decimalPoint
* @param string $thousandSeperator
*
* @return string
*/
public function weight(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null) : string
{
return $this->numberFormat($this->weightFloat(), $decimals, $decimalPoint, $thousandSeperator);
if (is_int($calculated)) {
return $calculated;
} else {
throw new \TypeError('Calculated weight was not an integer');
}
}
/**
@@ -464,7 +454,7 @@ class Cart
*
* @return \Illuminate\Support\Collection
*/
public function search(Closure $search) : Collection
public function search(Closure $search): Collection
{
return $this->getContent()->filter($search);
}
@@ -477,7 +467,7 @@ class Cart
*
* @return void
*/
public function associate(string $rowId, $model)
public function associate(string $rowId, $model): void
{
if (is_string($model) && !class_exists($model)) {
throw new UnknownModelException("The supplied model {$model} does not exist.");
@@ -502,7 +492,7 @@ class Cart
*
* @return void
*/
public function setTax(string $rowId, $taxRate)
public function setTax(string $rowId, $taxRate): void
{
$cartItem = $this->get($rowId);
@@ -521,7 +511,7 @@ class Cart
*
* @param float $discount
*/
public function setGlobalTax($taxRate)
public function setGlobalTax($taxRate): void
{
$this->taxRate = $taxRate;
@@ -535,17 +525,12 @@ class Cart
/**
* Set the discount rate for the cart item with the given rowId.
*
* @param string $rowId
* @param int|float $taxRate
*
* @return void
*/
public function setDiscount(string $rowId, $discount)
public function setDiscount(string $rowId, float|Money $discount): void
{
$cartItem = $this->get($rowId);
$cartItem->setDiscountRate($discount);
$cartItem->setDiscount($discount);
$content = $this->getContent();
@@ -562,14 +547,14 @@ class Cart
*
* @return void
*/
public function setGlobalDiscount($discount)
public function setGlobalDiscount(float $discount): void
{
$this->discount = $discount;
$content = $this->getContent();
if ($content && $content->count()) {
$content->each(function ($item, $key) {
$item->setDiscountRate($this->discount);
$content->each(function (CartItem $item, $key) {
$item->setDiscount($this->discount);
});
}
}
@@ -581,7 +566,7 @@ class Cart
*
* @return void
*/
public function store($identifier)
public function store($identifier): void
{
$content = $this->getContent();
@@ -595,7 +580,7 @@ class Cart
throw new CartAlreadyStoredException("A cart with identifier {$identifier} was already stored.");
}
$this->getConnection()->table($this->getTableName())->insert([
$this->getConnection()->table(self::getTableName())->insert([
'identifier' => $identifier,
'instance' => $instance,
'content' => serialize($content),
@@ -613,7 +598,7 @@ class Cart
*
* @return void
*/
public function restore($identifier)
public function restore($identifier): void
{
if ($identifier instanceof InstanceIdentifier) {
$identifier = $identifier->getInstanceIdentifier();
@@ -625,7 +610,7 @@ class Cart
return;
}
$stored = $this->getConnection()->table($this->getTableName())
$stored = $this->getConnection()->table(self::getTableName())
->where(['identifier'=> $identifier, 'instance' => $currentInstance])->first();
$storedContent = unserialize(data_get($stored, 'content'));
@@ -647,7 +632,7 @@ class Cart
$this->createdAt = Carbon::parse(data_get($stored, 'created_at'));
$this->updatedAt = Carbon::parse(data_get($stored, 'updated_at'));
$this->getConnection()->table($this->getTableName())->where(['identifier' => $identifier, 'instance' => $currentInstance])->delete();
$this->getConnection()->table(self::getTableName())->where(['identifier' => $identifier, 'instance' => $currentInstance])->delete();
}
/**
@@ -657,7 +642,7 @@ class Cart
*
* @return void
*/
public function erase($identifier)
public function erase($identifier): void
{
if ($identifier instanceof InstanceIdentifier) {
$identifier = $identifier->getInstanceIdentifier();
@@ -669,7 +654,7 @@ class Cart
return;
}
$this->getConnection()->table($this->getTableName())->where(['identifier' => $identifier, 'instance' => $instance])->delete();
$this->getConnection()->table(self::getTableName())->where(['identifier' => $identifier, 'instance' => $instance])->delete();
$this->events->dispatch('cart.erased');
}
@@ -684,13 +669,13 @@ class Cart
*
* @return bool
*/
public function merge($identifier, bool $keepDiscount = false, bool $keepTax = false, bool $dispatchAdd = true, $instance = self::DEFAULT_INSTANCE)
public function merge($identifier, bool $keepDiscount = false, bool $keepTax = false, bool $dispatchAdd = true, $instance = self::DEFAULT_INSTANCE): bool
{
if (!$this->storedCartInstanceWithIdentifierExists($instance, $identifier)) {
return false;
}
$stored = $this->getConnection()->table($this->getTableName())
$stored = $this->getConnection()->table(self::getTableName())
->where(['identifier'=> $identifier, 'instance'=> $instance])->first();
$storedContent = unserialize($stored->content);
@@ -708,10 +693,8 @@ class Cart
* Magic method to make accessing the total, tax and subtotal properties possible.
*
* @param string $attribute
*
* @return float|null
*/
public function __get(string $attribute)
public function __get(string $attribute): ?Money
{
switch ($attribute) {
case 'total':
@@ -721,16 +704,14 @@ class Cart
case 'subtotal':
return $this->subtotal();
default:
return;
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() : Collection
protected function getContent(): Collection
{
if ($this->session->has($this->instance)) {
return $this->session->get($this->instance);
@@ -739,69 +720,18 @@ class Cart
return new Collection();
}
/**
* Create a new CartItem from the supplied attributes.
*
* @param mixed $id
* @param mixed $name
* @param int|float $qty
* @param float $price
* @param float $weight
* @param array $options
*
* @return \Gloudemans\Shoppingcart\CartItem
*/
private function createCartItem($id, ?string $name = null, $qty, $price, $weight, array $options) : CartItem
{
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, $weight, $options);
$cartItem->setQuantity($qty);
}
$cartItem->setInstance($this->currentInstance());
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 storedCartInstanceWithIdentifierExists($instance, $identifier)
private function storedCartInstanceWithIdentifierExists(string $instance, string $identifier): bool
{
return $this->getConnection()->table($this->getTableName())->where(['identifier' => $identifier, 'instance'=> $instance])->exists();
return $this->getConnection()->table(self::getTableName())->where(['identifier' => $identifier, 'instance'=> $instance])->exists();
}
/**
* Get the database connection.
*
* @return \Illuminate\Database\Connection
*/
private function getConnection()
private function getConnection(): \Illuminate\Database\Connection
{
return app(DatabaseManager::class)->connection($this->getConnectionName());
}
@@ -811,48 +741,19 @@ class Cart
*
* @return string
*/
private function getTableName()
private static function getTableName(): string
{
return config('cart.database.table', 'shoppingcart');
return Config::get('cart.database.table', 'shoppingcart');
}
/**
* Get the database connection name.
*
* @return string
*/
private function getConnectionName()
private function getConnectionName(): ?string
{
$connection = config('cart.database.connection');
$connection = Config::get('cart.database.connection');
return is_null($connection) ? config('database.default') : $connection;
}
/**
* Get the Formatted number.
*
* @param $value
* @param $decimals
* @param $decimalPoint
* @param $thousandSeperator
*
* @return string
*/
private function numberFormat($value, ?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
{
if (is_null($decimals)) {
$decimals = config('cart.format.decimals', 2);
}
if (is_null($decimalPoint)) {
$decimalPoint = config('cart.format.decimal_point', '.');
}
if (is_null($thousandSeperator)) {
$thousandSeperator = config('cart.format.thousand_separator', ',');
}
return number_format($value, $decimals, $decimalPoint, $thousandSeperator);
return is_null($connection) ? Config::get('database.default') : $connection;
}
/**
@@ -860,7 +761,7 @@ class Cart
*
* @return \Carbon\Carbon|null
*/
public function createdAt() : ?Carbon
public function createdAt(): ?Carbon
{
return $this->createdAt;
}
@@ -870,7 +771,7 @@ class Cart
*
* @return \Carbon\Carbon|null
*/
public function updatedAt() : ?Carbon
public function updatedAt(): ?Carbon
{
return $this->updatedAt;
}

View File

@@ -2,70 +2,47 @@
namespace Gloudemans\Shoppingcart;
use Gloudemans\Shoppingcart\Calculation\DefaultCalculator;
use Gloudemans\Shoppingcart\Contracts\Buyable;
use Gloudemans\Shoppingcart\Contracts\Calculator;
use Gloudemans\Shoppingcart\Exceptions\InvalidCalculatorException;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use ReflectionClass;
use Illuminate\Support\Facades\Config;
use Money\Currencies\ISOCurrencies;
use Money\Formatter\DecimalMoneyFormatter;
use Money\Money;
/**
* @property-read mixed discount
* @property-read float discountTotal
* @property-read float priceTarget
* @property-read float priceNet
* @property-read float priceTotal
* @property-read float subtotal
* @property-read float taxTotal
* @property-read float tax
* @property-read float total
* @property-read float priceTax
*/
class CartItem implements Arrayable, Jsonable
{
/**
* The rowID of the cart item.
*
* @var string
*/
public $rowId;
public string $rowId;
/**
* The ID of the cart item.
*
* @var int|string
*/
public $id;
public int|string $id;
/**
* The quantity for this cart item.
*
* @var int|float
*/
public $qty;
public int $qty;
/**
* The name of the cart item.
*
* @var string
*/
public string $name;
/**
* The price without TAX of the cart item.
*
* @var float
*/
public $price;
public Money $price;
/**
* The weight of the product.
*
* @var float
*/
public $weight;
public int $weight;
/**
* The options for this cart item.
@@ -74,240 +51,47 @@ class CartItem implements Arrayable, Jsonable
/**
* The tax rate for the cart item.
*
* @var int|float
*/
public $taxRate = 0;
public float $taxRate = 0;
/**
* The FQN of the associated model.
*
* @var string|null
*/
private $associatedModel = null;
public ?string $associatedModel = null;
/**
* The discount rate for the cart item.
*
* @var float
*/
private $discountRate = 0;
public float|Money $discount = 0;
/**
* The cart instance of the cart item.
*/
public ?string $instance = null;
/**
* CartItem constructor.
*
* @param int|string $id
* @param string $name
* @param float $price
* @param float $weight
* @param array $options
*/
public function __construct($id, string $name, $price, $weight = 0, array $options = [])
public function __construct(int|string $id, string $name, Money $price, int $qty = 1, int $weight = 0, ?CartItemOptions $options = null)
{
if (empty($id)) {
throw new \InvalidArgumentException('Please supply a valid identifier.');
}
if (empty($name)) {
throw new \InvalidArgumentException('Please supply a valid name.');
}
if (strlen($price) < 0 || !is_numeric($price)) {
throw new \InvalidArgumentException('Please supply a valid price.');
}
if (strlen($weight) < 0 || !is_numeric($weight)) {
throw new \InvalidArgumentException('Please supply a valid weight.');
}
$this->id = $id;
$this->name = $name;
$this->price = floatval($price);
$this->weight = floatval($weight);
$this->options = new CartItemOptions($options);
$this->rowId = $this->generateRowId($id, $options);
}
/**
* Returns the formatted weight.
*
* @param int $decimals
* @param string $decimalPoint
* @param string $thousandSeperator
*
* @return string
*/
public function weight(int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
{
return $this->numberFormat($this->weight, $decimals, $decimalPoint, $thousandSeperator);
}
/**
* Returns the formatted price without TAX.
*
* @param int $decimals
* @param string $decimalPoint
* @param string $thousandSeperator
*
* @return string
*/
public function price(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
{
return $this->numberFormat($this->price, $decimals, $decimalPoint, $thousandSeperator);
}
/**
* Returns the formatted price with discount applied.
*
* @param int $decimals
* @param string $decimalPoint
* @param string $thousandSeperator
*
* @return string
*/
public function priceTarget(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
{
return $this->numberFormat($this->priceTarget, $decimals, $decimalPoint, $thousandSeperator);
}
/**
* Returns the formatted price with TAX.
*
* @param int $decimals
* @param string $decimalPoint
* @param string $thousandSeperator
*
* @return string
*/
public function priceTax(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
{
return $this->numberFormat($this->priceTax, $decimals, $decimalPoint, $thousandSeperator);
}
/**
* Returns the formatted subtotal.
* Subtotal is price for whole CartItem without TAX.
*
* @param int $decimals
* @param string $decimalPoint
* @param string $thousandSeperator
*
* @return string
*/
public function subtotal(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
{
return $this->numberFormat($this->subtotal, $decimals, $decimalPoint, $thousandSeperator);
}
/**
* Returns the formatted total.
* Total is price for whole CartItem with TAX.
*
* @param int $decimals
* @param string $decimalPoint
* @param string $thousandSeperator
*
* @return string
*/
public function total(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
{
return $this->numberFormat($this->total, $decimals, $decimalPoint, $thousandSeperator);
}
/**
* Returns the formatted tax.
*
* @param int $decimals
* @param string $decimalPoint
* @param string $thousandSeperator
*
* @return string
*/
public function tax(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
{
return $this->numberFormat($this->tax, $decimals, $decimalPoint, $thousandSeperator);
}
/**
* Returns the formatted tax.
*
* @param int $decimals
* @param string $decimalPoint
* @param string $thousandSeperator
*
* @return string
*/
public function taxTotal(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
{
return $this->numberFormat($this->taxTotal, $decimals, $decimalPoint, $thousandSeperator);
}
/**
* Returns the formatted discount.
*
* @param int $decimals
* @param string $decimalPoint
* @param string $thousandSeperator
*
* @return string
*/
public function discount(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
{
return $this->numberFormat($this->discount, $decimals, $decimalPoint, $thousandSeperator);
}
/**
* Returns the formatted total discount for this cart item.
*
* @param int $decimals
* @param string $decimalPoint
* @param string $thousandSeperator
*
* @return string
*/
public function discountTotal(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
{
return $this->numberFormat($this->discountTotal, $decimals, $decimalPoint, $thousandSeperator);
}
/**
* Returns the formatted total price for this cart item.
*
* @param int $decimals
* @param string $decimalPoint
* @param string $thousandSeperator
*
* @return string
*/
public function priceTotal(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
{
return $this->numberFormat($this->priceTotal, $decimals, $decimalPoint, $thousandSeperator);
$this->price = $price;
$this->qty = $qty;
$this->weight = $weight;
$this->options = $options ?: new CartItemOptions([]);
$this->rowId = $this->generateRowId($id, $options->toArray());
}
/**
* Set the quantity for this cart item.
*
* @param int|float $qty
*/
public function setQuantity($qty)
public function setQuantity(int $qty)
{
if (empty($qty) || !is_numeric($qty)) {
throw new \InvalidArgumentException('Please supply a valid quantity.');
}
$this->qty = $qty;
}
/**
* Update the cart item from a Buyable.
*
* @param \Gloudemans\Shoppingcart\Contracts\Buyable $item
*
* @return void
*/
public function updateFromBuyable(Buyable $item)
public function updateFromBuyable(Buyable $item): void
{
$this->id = $item->getBuyableIdentifier($this->options);
$this->name = $item->getBuyableDescription($this->options);
@@ -316,12 +100,8 @@ class CartItem implements Arrayable, Jsonable
/**
* Update the cart item from an array.
*
* @param array $attributes
*
* @return void
*/
public function updateFromArray(array $attributes)
public function updateFromArray(array $attributes): void
{
$this->id = Arr::get($attributes, 'id', $this->id);
$this->qty = Arr::get($attributes, 'qty', $this->qty);
@@ -337,10 +117,8 @@ class CartItem implements Arrayable, Jsonable
* Associate the cart item with the given model.
*
* @param mixed $model
*
* @return \Gloudemans\Shoppingcart\CartItem
*/
public function associate($model)
public function associate(string|Model $model): self
{
$this->associatedModel = is_string($model) ? $model : get_class($model);
@@ -349,12 +127,8 @@ class CartItem implements Arrayable, Jsonable
/**
* Set the tax rate.
*
* @param int|float $taxRate
*
* @return \Gloudemans\Shoppingcart\CartItem
*/
public function setTaxRate($taxRate)
public function setTaxRate(float $taxRate): self
{
$this->taxRate = $taxRate;
@@ -363,124 +137,119 @@ class CartItem implements Arrayable, Jsonable
/**
* Set the discount rate.
*
* @param int|float $discountRate
*
* @return \Gloudemans\Shoppingcart\CartItem
*/
public function setDiscountRate($discountRate)
public function setDiscount(float|Money $discount): self
{
$this->discountRate = $discountRate;
$this->discount = $discount;
return $this;
}
/**
* Set cart instance.
*
* @param null|string $instance
*
* @return \Gloudemans\Shoppingcart\CartItem
*/
public function setInstance(?string $instance)
public function setInstance(?string $instance): self
{
$this->instance = $instance;
return $this;
}
/**
* Get an attribute from the cart item or get the associated model.
*
* @param string $attribute
*
* @return mixed
*/
public function __get($attribute)
public function model(): ?Model
{
if (property_exists($this, $attribute)) {
return $this->{$attribute};
}
$decimals = config('cart.format.decimals', 2);
switch ($attribute) {
case 'model':
if (isset($this->associatedModel)) {
return with(new $this->associatedModel())->find($this->id);
}
// no break
case 'modelFQCN':
if (isset($this->associatedModel)) {
return $this->associatedModel;
}
// no break
case 'weightTotal':
return round($this->weight * $this->qty, $decimals);
if (isset($this->associatedModel)) {
return (new $this->associatedModel())->find($this->id);
}
$class = new ReflectionClass(config('cart.calculator', DefaultCalculator::class));
if (!$class->implementsInterface(Calculator::class)) {
throw new InvalidCalculatorException('The configured Calculator seems to be invalid. Calculators have to implement the Calculator Contract.');
}
return null;
}
return call_user_func($class->getName().'::getAttribute', $attribute, $this);
/**
* This will is the price of the CartItem considering the set quantity. If you need the single
* price just set the parameter to true.
*/
public function price(): Money
{
return $this->price->multiply($this->qty);
}
/**
* This is the discount granted for this CartItem. It is based on the given price and, in case
* discount is a float, multiplied or, in case it is an absolute Money, subtracted. It will return
* a minimum value of 0.
*/
public function discount(): Money
{
if ($this->discount instanceof Money) {
return $this->price()->subtract($this->discount);
} else {
return $this->price()->multiply(sprintf('%.14F', $this->discount), Config::get('cart.rounding', Money::ROUND_UP));
}
}
/**
* This is the final price of the CartItem but without any tax applied. This does on the
* other hand include any discounts.
*/
public function subtotal(): Money
{
return Money::max(new Money(0, $this->price->getCurrency()), $this->price()->subtract($this->discount()));
}
/**
* This is the tax, based on the subtotal (all previous calculations) and set tax rate.
*/
public function tax(): Money
{
return $this->subtotal()->multiply(sprintf('%.14F', $this->taxRate), Config::get('cart.rounding', Money::ROUND_UP));
}
/**
* This is the total price, consisting of the subtotal and tax applied.
*/
public function total(): Money
{
return $this->subtotal()->add($this->tax());
}
/**
* This is the total price, consisting of the subtotal and tax applied.
*/
public function weight(): int
{
return $this->qty * $this->weight;
}
/**
* Create a new instance from a Buyable.
*
* @param \Gloudemans\Shoppingcart\Contracts\Buyable $item
* @param array $options
*
* @return \Gloudemans\Shoppingcart\CartItem
*/
public static function fromBuyable(Buyable $item, array $options = [])
public static function fromBuyable(Buyable $item, int $qty = 1, ?CartItemOptions $options = null): self
{
return new self($item->getBuyableIdentifier($options), $item->getBuyableDescription($options), $item->getBuyablePrice($options), $item->getBuyableWeight($options), $options);
$options = $options ?: new CartItemOptions([]);
return new self($item->getBuyableIdentifier($options), $item->getBuyableDescription($options), $item->getBuyablePrice($options), $qty, $item->getBuyableWeight($options), $options);
}
/**
* Create a new instance from the given array.
*
* @param array $attributes
*
* @return \Gloudemans\Shoppingcart\CartItem
*/
public static function fromArray(array $attributes)
public static function fromArray(array $attributes): self
{
$options = Arr::get($attributes, 'options', []);
$options = new CartItemOptions(Arr::get($attributes, 'options', []));
return new self($attributes['id'], $attributes['name'], $attributes['price'], $attributes['weight'], $options);
return new self($attributes['id'], $attributes['name'], $attributes['price'], $attributes['qty'], $attributes['weight'], $options);
}
/**
* Create a new instance from the given attributes.
*
* @param int|string $id
* @param string $name
* @param float $price
* @param array $options
*
* @return \Gloudemans\Shoppingcart\CartItem
*/
public static function fromAttributes($id, string $name, $price, $weight, array $options = [])
public static function fromAttributes(int|string $id, string $name, Money $price, int $qty = 1, int $weight = 0, ?CartItemOptions $options = null): self
{
return new self($id, $name, $price, $weight, $options);
}
$options = $options ?: new CartItemOptions([]);
/**
* Generate a unique id for the cart item.
*
* @param string $id
* @param array $options
*
* @return string
*/
protected function generateRowId($id, array $options)
{
ksort($options);
return md5($id.serialize($options));
return new self($id, $name, $price, $qty, $weight, $options);
}
/**
@@ -494,15 +263,16 @@ class CartItem implements Arrayable, Jsonable
'rowId' => $this->rowId,
'id' => $this->id,
'name' => $this->name,
'price' => self::formatMoney($this->price),
'qty' => $this->qty,
'price' => $this->price,
'weight' => $this->weight,
'options' => is_object($this->options)
? $this->options->toArray()
: $this->options,
'discount' => $this->discount,
'tax' => $this->tax,
'subtotal' => $this->subtotal,
'options' => $this->options->toArray(),
/* Calculated attributes */
'discount' => self::formatMoney($this->discount()),
'subtotal' => self::formatMoney($this->subtotal()),
'tax' => self::formatMoney($this->tax()),
'total' => self::formatMoney($this->total()),
];
}
@@ -519,40 +289,20 @@ class CartItem implements Arrayable, Jsonable
}
/**
* Get the formatted number.
*
* @param float $value
* @param int $decimals
* @param string $decimalPoint
* @param string $thousandSeperator
*
* @return string
* Generate a unique id for the cart item.
*/
private function numberFormat($value, ?int $decimals, ?string $decimalPoint, ?string $thousandSeperator)
private static function formatMoney(Money $money): string
{
if (is_null($decimals)) {
$decimals = config('cart.format.decimals', 2);
}
if (is_null($decimalPoint)) {
$decimalPoint = config('cart.format.decimal_point', '.');
}
if (is_null($thousandSeperator)) {
$thousandSeperator = config('cart.format.thousand_separator', ',');
}
return number_format($value, $decimals, $decimalPoint, $thousandSeperator);
return (new DecimalMoneyFormatter(new ISOCurrencies()))->format($money);
}
/**
* Getter for the raw internal discount rate.
* Should be used in calculators.
*
* @return float
* Generate a unique id for the cart item.
*/
public function getDiscountRate()
protected function generateRowId(string $id, array $options): string
{
return $this->discountRate;
ksort($options);
return md5($id.serialize($options));
}
}

View File

@@ -1,10 +1,12 @@
<?php
use Money\Money;
return [
/*
|--------------------------------------------------------------------------
| Gross price as base price
| Rounding strategy
|--------------------------------------------------------------------------
|
| This default value is used to select the method to calculate prices and taxes
@@ -12,7 +14,7 @@ return [
|
*/
'calculator' => \Gloudemans\Shoppingcart\Calculation\DefaultCalculator::class,
'rounding' => Money::ROUND_UP,
/*
|--------------------------------------------------------------------------
@@ -24,7 +26,7 @@ return [
|
*/
'tax' => 21,
'tax' => 0.21,
/*
|--------------------------------------------------------------------------
@@ -44,18 +46,6 @@ return [
],
/*
|--------------------------------------------------------------------------
| Destroy the cart on user logout
|--------------------------------------------------------------------------
|
| When this option is set to 'true' the cart will automatically
| destroy all cart instances when the user logs out.
|
*/
'destroy_on_logout' => false,
/*
|--------------------------------------------------------------------------
| Default number format

View File

@@ -2,6 +2,9 @@
namespace Gloudemans\Shoppingcart\Contracts;
use Gloudemans\Shoppingcart\CartItemOptions;
use Money\Money;
interface Buyable
{
/**
@@ -9,24 +12,20 @@ interface Buyable
*
* @return int|string
*/
public function getBuyableIdentifier();
public function getBuyableIdentifier(CartItemOptions $options);
/**
* Get the description or title of the Buyable item.
*/
public function getBuyableDescription() : ?string;
public function getBuyableDescription(CartItemOptions $options): ?string;
/**
* Get the price of the Buyable item.
*
* @return float
*/
public function getBuyablePrice();
public function getBuyablePrice(CartItemOptions $options): Money;
/**
* Get the weight of the Buyable item.
*
* @return float
*/
public function getBuyableWeight();
public function getBuyableWeight(CartItemOptions $options): int;
}

View File

@@ -1,10 +0,0 @@
<?php
namespace Gloudemans\Shoppingcart\Contracts;
use Gloudemans\Shoppingcart\CartItem;
interface Calculator
{
public static function getAttribute(string $attribute, CartItem $cartItem);
}

View File

@@ -13,8 +13,6 @@ interface InstanceIdentifier
/**
* Get the unique identifier to load the Cart from.
*
* @return int|string
*/
public function getInstanceGlobalDiscount();
public function getInstanceGlobalDiscount(): float;
}

View File

@@ -11,7 +11,7 @@ class CreateShoppingcartTable extends Migration
*/
public function up()
{
Schema::create(config('cart.database.table'), function (Blueprint $table) {
Schema::create(Config::get('cart.database.table'), function (Blueprint $table) {
$table->string('identifier');
$table->string('instance');
$table->longText('content');
@@ -26,6 +26,6 @@ class CreateShoppingcartTable extends Migration
*/
public function down()
{
Schema::drop(config('cart.database.table'));
Schema::drop(Config::get('cart.database.table'));
}
}

View File

@@ -1,9 +0,0 @@
<?php
namespace Gloudemans\Shoppingcart\Exceptions;
use RuntimeException;
class InvalidCalculatorException extends RuntimeException
{
}

View File

@@ -2,8 +2,6 @@
namespace Gloudemans\Shoppingcart;
use Illuminate\Auth\Events\Logout;
use Illuminate\Session\SessionManager;
use Illuminate\Support\ServiceProvider;
class ShoppingcartServiceProvider extends ServiceProvider
@@ -15,19 +13,19 @@ class ShoppingcartServiceProvider extends ServiceProvider
*/
public function register()
{
/* Bind Cart class to cart for Facade usage */
$this->app->bind('cart', 'Gloudemans\Shoppingcart\Cart');
/* Determine where the config file is located */
$config = __DIR__.'/Config/cart.php';
/* Use local config */
$this->mergeConfigFrom($config, 'cart');
$this->publishes([__DIR__.'/Config/cart.php' => config_path('cart.php')], 'config');
$this->app['events']->listen(Logout::class, function () {
if ($this->app['config']->get('cart.destroy_on_logout')) {
$this->app->make(SessionManager::class)->forget('cart');
}
});
/* Also allow publishing to overwrite local config */
$this->publishes([$config => config_path('cart.php')], 'config');
/* Publish included migrations */
$this->publishes([
realpath(__DIR__.'/Database/migrations') => $this->app->databasePath().'/migrations',
], 'migrations');

View File

@@ -3,7 +3,10 @@
namespace Gloudemans\Tests\Shoppingcart;
use Gloudemans\Shoppingcart\CartItem;
use Gloudemans\Shoppingcart\CartItemOptions;
use Gloudemans\Shoppingcart\ShoppingcartServiceProvider;
use Money\Currency;
use Money\Money;
use Orchestra\Testbench\TestCase;
class CartItemTest extends TestCase
@@ -23,45 +26,35 @@ class CartItemTest extends TestCase
/** @test */
public function it_can_be_cast_to_an_array()
{
$cartItem = new CartItem(1, 'Some item', 10.00, 550, ['size' => 'XL', 'color' => 'red']);
$cartItem->setQuantity(2);
$cartItem = new CartItem(1, 'Some item', new Money(1000, new Currency('USD')), 2, 550, new CartItemOptions(['size' => 'XL', 'color' => 'red']));
$this->assertEquals([
'id' => 1,
'name' => 'Some item',
'price' => 10.00,
'price' => '10.00',
'rowId' => '07d5da5550494c62daf9993cf954303f',
'qty' => 2,
'options' => [
'size' => 'XL',
'color' => 'red',
],
'tax' => 0,
'subtotal' => 20.00,
'discount' => 0.0,
'weight' => 550.0,
'tax' => '0.00',
'subtotal' => '20.00',
'total' => '20.00',
'discount' => '0.00',
'weight' => 550,
], $cartItem->toArray());
}
/** @test */
public function it_can_be_cast_to_json()
{
$cartItem = new CartItem(1, 'Some item', 10.00, 550, ['size' => 'XL', 'color' => 'red']);
$cartItem->setQuantity(2);
$cartItem = new CartItem(1, 'Some item', new Money(1000, new Currency('USD')), 2, 550, new CartItemOptions(['size' => 'XL', 'color' => 'red']));
$this->assertJson($cartItem->toJson());
$json = '{"rowId":"07d5da5550494c62daf9993cf954303f","id":1,"name":"Some item","qty":2,"price":10,"weight":550,"options":{"size":"XL","color":"red"},"discount":0,"tax":0,"subtotal":20}';
$json = '{"rowId":"07d5da5550494c62daf9993cf954303f","id":1,"name":"Some item","price":"10.00","qty":2,"weight":550,"options":{"size":"XL","color":"red"},"discount":"0.00","subtotal":"20.00","tax":"0.00","total":"20.00"}';
$this->assertEquals($json, $cartItem->toJson());
}
/** @test */
public function it_formats_price_total_correctly()
{
$cartItem = new CartItem(1, 'Some item', 10.00, 550, ['size' => 'XL', 'color' => 'red']);
$cartItem->setQuantity(2);
$this->assertSame('20.00', $cartItem->priceTotal());
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,8 +2,11 @@
namespace Gloudemans\Tests\Shoppingcart\Fixtures;
use Gloudemans\Shoppingcart\CartItemOptions;
use Gloudemans\Shoppingcart\Contracts\Buyable;
use Illuminate\Database\Eloquent\Model;
use Money\Currency;
use Money\Money;
class BuyableProduct extends Model implements Buyable
{
@@ -18,14 +21,16 @@ class BuyableProduct extends Model implements Buyable
'title',
'description',
'price',
'currency',
'weight',
];
protected $attributes = [
'id' => 1,
'name' => 'Item name',
'price' => 10.00,
'weight' => 0,
'id' => 1,
'name' => 'Item name',
'price' => 1000,
'currency' => 'USD',
'weight' => 0,
];
/**
@@ -33,7 +38,7 @@ class BuyableProduct extends Model implements Buyable
*
* @return int|string
*/
public function getBuyableIdentifier()
public function getBuyableIdentifier(CartItemOptions $options)
{
return $this->id;
}
@@ -43,27 +48,23 @@ class BuyableProduct extends Model implements Buyable
*
* @return string
*/
public function getBuyableDescription() : ?string
public function getBuyableDescription(CartItemOptions $options): ?string
{
return $this->name;
}
/**
* Get the price of the Buyable item.
*
* @return float
*/
public function getBuyablePrice()
public function getBuyablePrice(CartItemOptions $options): Money
{
return $this->price;
return new Money($this->price, new Currency($this->currency));
}
/**
* Get the price of the Buyable item.
*
* @return float
*/
public function getBuyableWeight()
public function getBuyableWeight(CartItemOptions $options): int
{
return $this->weight;
}

View File

@@ -20,13 +20,15 @@ class BuyableProductTrait extends Model implements Buyable
'title',
'description',
'price',
'currency',
'weight',
];
protected $attributes = [
'id' => 1,
'name' => 'Item name',
'price' => 10.00,
'weight' => 0,
'id' => 1,
'name' => 'Item name',
'price' => 1000,
'currency' => 'USD',
'weight' => 0,
];
}

View File

@@ -44,7 +44,7 @@ class Identifiable implements InstanceIdentifier
*
* @return int|string
*/
public function getInstanceGlobalDiscount()
public function getInstanceGlobalDiscount(): float
{
return $this->discountRate;
}

View File

@@ -2,11 +2,13 @@
namespace Gloudemans\Tests\Shoppingcart\Fixtures;
class ProductModel
use Illuminate\Database\Eloquent\Model;
class ProductModel extends Model
{
public $someValue = 'Some value';
public function find($id) : self
public function find($id): self
{
return $this;
}