https://github.com/revotale/php-shopping-cart
PHP library providing basic shopping cart object and interfaces to implement any type of cart items + promotions combination.
https://github.com/revotale/php-shopping-cart
Last synced: 4 months ago
JSON representation
PHP library providing basic shopping cart object and interfaces to implement any type of cart items + promotions combination.
- Host: GitHub
- URL: https://github.com/revotale/php-shopping-cart
- Owner: RevoTale
- License: mit
- Created: 2024-11-26T13:34:24.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-11-24T09:35:42.000Z (7 months ago)
- Last Synced: 2025-11-27T09:28:43.009Z (7 months ago)
- Language: PHP
- Homepage:
- Size: 177 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# RevoTale Shopping Cart
A powerful and flexible PHP library for implementing shopping cart functionality with advanced promotion system and precise decimal calculations.
**Notice!** This README was "vibe coded". If you encouter any errors please feel free to open the issue.
[](https://php.net)
[](https://opensource.org/licenses/MIT)
## Features
- 🛒 **Flexible Cart Management**: Add, remove, and manage cart items with quantity control
- 🏷️ **Advanced Promotion System**: Support for multiple promotion types (percentage discounts, fixed amount discounts, free products, etc.)
- 💰 **Precise Decimal Calculations**: Built-in arbitrary precision decimal arithmetic using BCMath
- 🔧 **Extensible Architecture**: Easy to extend with custom items and promotions
- 📊 **Comprehensive Totals**: Detailed cart totals with promotion impacts and item subtotals
- 🎯 **Promotion Stacking**: Support for multiple promotions that can interact with each other
- 🧮 **Mathematical Functions**: Full-featured decimal class with trigonometric, logarithmic, and power functions
## Installation
```bash
composer require revotale/shopping-cart
```
### Requirements
- PHP ^8.2
- BCMath extension
## Quick Start
### Basic Cart Operations
```php
id;
}
public function getCartType(): string
{
return 'product';
}
public function getUnitPrice(): int
{
return $this->priceInCents; // Price in smallest currency unit (cents)
}
}
// Create cart and add items
$cart = new Cart();
$product = new Product('SKU123', 'T-Shirt', 2999); // $29.99
$cart->addItem($product, 2); // Add 2 t-shirts
// Get cart totals
$totals = $cart->performTotals();
echo $totals->getTotal()->asFloat(); // 59.98
```
### Adding Promotions
```php
use RevoTale\ShoppingCart\PromotionTemplates\CartPercentageDiscount;
use RevoTale\ShoppingCart\CartInterface;
// Create a 10% discount promotion
class TenPercentDiscount extends CartPercentageDiscount
{
public function getCartId(): string
{
return 'ten_percent_off';
}
public function getCartType(): string
{
return 'discount';
}
public function isEligible(CartInterface $cart): bool
{
// Apply if cart total is over $50
return $cart->getItems() !== [] &&
$cart->performTotals()->getTotal()->isGreaterThan(
\RevoTale\ShoppingCart\Decimal::fromInteger(5000)
);
}
public function getDiscountMultiplier(): float
{
return 0.9; // 90% of original price = 10% discount
}
}
// Add promotion to cart
$cart->addPromotion(new TenPercentDiscount());
$totals = $cart->performTotals();
echo $totals->getTotal()->asFloat(); // 53.98 (10% off $59.98)
```
## Core Components
### Cart
The main cart class that manages items and promotions.
```php
$cart = new Cart();
// Item management
$cart->addItem($item, $quantity);
$cart->removeItem($item, $quantity);
$cart->hasItem($item);
$cart->getItemQuantity($item);
// Promotion management
$cart->addPromotion($promotion);
$cart->removePromotion($promotion);
$cart->hasPromo($promotion);
// Clear operations
$cart->clearItems();
$cart->clearPromotions();
$cart->clear(); // Clear both items and promotions
// Calculate totals
$totals = $cart->performTotals();
```
### Cart Items
Items must implement `CartItemInterface`:
```php
interface CartItemInterface
{
public function getCartId(): string; // Unique identifier
public function getCartType(): string; // Type category
public function getUnitPrice(): int; // Price in smallest currency unit
}
```
### Promotions
Promotions implement `PromotionInterface` and can:
- **Reduce item prices**: Modify individual item subtotals
- **Add/remove items**: Add free products or remove items
- **Control other promotions**: Enable/disable other promotions
- **Apply cart-wide effects**: Fixed amount discounts, shipping rules
#### Built-in Promotion Templates
1. **CartPercentageDiscount**: Apply percentage discounts
```php
class MyPercentageDiscount extends CartPercentageDiscount
{
public function getDiscountMultiplier(): float
{
return 0.85; // 15% discount
}
public function isEligible(CartInterface $cart): bool
{
return true; // Always eligible
}
// ... implement required methods
}
```
2. **CartFixedSumDiscount**: Apply fixed amount discounts
```php
class MyFixedDiscount extends CartFixedSumDiscount
{
public function getDiscountAmount(): float
{
return 10.00; // $10 off
}
public function isEligible(CartInterface $cart): bool
{
return true;
}
// ... implement required methods
}
```
#### Custom Promotions
For complex promotion logic, implement `PromotionInterface` directly:
```php
class BuyOneGetOneFree implements PromotionInterface
{
public function isEligible(CartInterface $cart): bool
{
return $cart->getItemQuantity($this->targetItem) >= 2;
}
public function reduceItems(ModifiedCartData $cart, array $itemCounters): array
{
// Add free items based on cart contents
foreach ($itemCounters as $counter) {
if ($counter->item === $this->targetItem) {
$freeQuantity = intval($counter->quantity / 2);
if ($freeQuantity > 0) {
$itemCounters[] = new CartItemCounter($this->freeItem, $freeQuantity);
}
}
}
return $itemCounters;
}
// ... implement other required methods
}
```
### Decimal Arithmetic
The library includes a comprehensive `Decimal` class for precise calculations:
```php
use RevoTale\ShoppingCart\Decimal;
// Create decimals
$price = Decimal::fromFloat(29.99);
$quantity = Decimal::fromInteger(3);
$discount = Decimal::fromString("0.1");
// Arithmetic operations
$subtotal = $price->mul($quantity); // 89.97
$discountAmount = $subtotal->mul($discount); // 8.997
$total = $subtotal->sub($discountAmount); // 80.973
// Rounding and formatting
$finalTotal = $total->round(2); // 80.97
echo $finalTotal->asFloat(); // 80.97
// Comparisons
if ($total->isGreaterThan(Decimal::fromInteger(80))) {
echo "Total exceeds $80";
}
// Mathematical functions
$sqrt = Decimal::fromInteger(16)->sqrt(); // 4.0
$log = Decimal::fromInteger(100)->log10(); // 2.0
$power = Decimal::fromInteger(2)->pow(Decimal::fromInteger(8)); // 256.0
```
## Advanced Usage
### Promotion Context
Use `PromoCalculationsContext` to share data between promotions during calculation:
```php
public function reduceItemSubtotal(
ModifiedCartData $cart,
CartItemInterface $item,
Decimal $subTotal,
PromoCalculationsContext $context
): Decimal {
// Store data for later use
$context->setValue($this, 'discount_applied', true);
// Retrieve data from other promotions
$previousDiscount = $context->getValue($otherPromotion, 'discount_amount');
return $subTotal->mul(Decimal::fromFloat(0.9));
}
```
### Cart Totals Analysis
The `CartTotals` object provides detailed information:
```php
$totals = $cart->performTotals();
// Basic totals
$grandTotal = $totals->getTotal();
$items = $totals->getItems();
$promotions = $totals->getPromotions();
// Detailed item information
foreach ($totals->getItemSubTotals() as $itemSubTotal) {
echo sprintf(
"Item: %s, Qty: %d, Before: %s, After: %s\n",
$itemSubTotal->item->getCartId(),
$itemSubTotal->quantity,
$itemSubTotal->subTotalBeforePromo->asFloat(),
$itemSubTotal->subTotalAfterPromo->asFloat()
);
}
// Promotion impacts
foreach ($totals->getPromotionItemsImpact() as $impact) {
echo sprintf(
"Promotion %s affected %s by %s\n",
$impact->promotion->getCartId(),
$impact->item->getCartId(),
$impact->priceImpact->asFloat()
);
}
// Check for changes
if ($totals->isPromotionDiff()) {
echo "Promotions were added or removed during calculation\n";
}
if ($totals->isItemsDiff()) {
echo "Items were added or removed during calculation\n";
}
```
### Promotion Execution Order
Promotions are executed in the order they were added to the cart. Later promotions can modify the effects of earlier ones. Use `reducePromotions()` to control which other promotions are active:
```php
public function reducePromotions(ModifiedCartData $cart, array $promotions): array
{
// Remove conflicting promotions
return array_filter($promotions, function($promo) {
return !($promo instanceof ConflictingPromotionType);
});
}
```
## Error Handling
The library throws specific exceptions for various error conditions:
```php
try {
// Division by zero
$result = $decimal->div(Decimal::fromInteger(0));
} catch (DomainException $e) {
echo "Mathematical error: " . $e->getMessage();
}
try {
// Invalid number format
$decimal = Decimal::fromString("not-a-number");
} catch (UnexpectedValueException $e) {
echo "Invalid input: " . $e->getMessage();
}
```
## Performance Considerations
- The library uses BCMath for precise decimal calculations, which is slower than float arithmetic but provides exact results
- Promotion calculations are performed each time `performTotals()` is called
- For high-performance scenarios, consider caching totals when cart contents haven't changed
- The `Decimal` class is immutable, so operations create new instances
## Testing
```bash
# Run tests
composer run phpunit
# Run static analysis
composer run phpstan
# Run code style fixes
composer run rector:fix
```
## Contributing
1. Fork the repository
2. Create a feature branch
3. Add tests for new functionality
4. Ensure all tests pass
5. Submit a pull request
## License
This library is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
## Support
For questions, issues, or feature requests, please use the GitHub issue tracker.