Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Cart Laravel Package

leaphly/cart

Leaphly Cart is a PHP shopping cart component for e-commerce applications, offering cart and item models, quantity and pricing logic, extensible storage, and customizable cart workflows. It integrates with Symfony and helps developers build flexible cart features.

Deep Wiki
Context7

Getting Started

Leaphly is a small, framework-agnostic cart domain library: a Cart that holds items, an item interface, and a few helpers around totals. It is deprecated (last release 2013), so treat the first hour as a compatibility audit, not a normal install.

  1. Install and immediately check compatibility.

    composer require leaphly/cart
    

    If Composer refuses on PHP 8.x, add --ignore-platform-reqs only to inspect it locally. Open vendor/leaphly/cart/composer.json first: it tells you the real PHP constraint, whether it pulls symfony/event-dispatcher, and what PSR-0/PSR-4 autoloading it expects. If autoloading fails under Composer 2, you will spend more time fixing that than the cart itself.

  2. Read these classes before anything else, in this order:

    • Cart — the aggregate root (items, totals, add/remove methods)
    • CartItem and the item interface it implements
    • CartInterface / ItemInterface — these are the contracts you will bind in Laravel
    • Any Provider, Storage, or Factory classes — this is where persistence was intended to plug in
  3. First use case — prove the domain works bare, outside Laravel.

    use Leaphly\Cart\Model\Cart;
    use Leaphly\Cart\Model\CartItem;
    
    $cart = new Cart();
    $cart->addItem(new CartItem('sku-123', 'Widget', 2, 1999)); // cents
    echo $cart->getTotal(); // 3998
    

    Do this in php artisan tinker or a throwaway route. If the API names differ (they often do in 2013-era libs — addItem vs add, getTotal vs total), the class files are short enough to read in ten minutes. Do not start with Eloquent integration; establish that the plain-PHP domain object behaves.

  4. Where to look first in the codebase: the tests directory. For an abandoned library this is the only reliable specification of intended behavior, especially around price rounding, quantity updates, and empty-cart totals.

Implementation Patterns

The realistic way to use this in Laravel is as a pure domain object wrapped by your own application services — not as a framework package. There is no service provider, no facade, no config file. You supply all of that.

Bind the cart as a singleton, but be deliberate about scope.

// app/Providers/CartServiceProvider.php
$this->app->scoped(Cart::class, function ($app) {
    return $app->make(CartFactory::class)->forUser($app['auth']->id());
});

Use scoped (per request/job), not singleton. An in-memory cart singleton across queued jobs is a classic source of "items appeared in the wrong order" bugs.

Persist the cart yourself — the library will not. The common pattern is a lightweight Eloquent model pair:

// carts: id, user_id, currency, created_at, updated_at
// cart_items: id, cart_id, sku, name, unit_price, quantity, meta (json)

class CartRepository
{
    public function loadFor(User $user): Cart
    {
        $cart = new Cart();
        foreach ($user->cart()->with('items')->first()?->items ?? [] as $row) {
            $cart->addItem(new CartItem($row->sku, $row->name, $row->quantity, $row->unit_price));
        }
        return $cart;
    }
}

Rehydrate on read, translate back to rows on write. Keep the mapping in one class (CartMapper) so a future replacement library is a one-file swap.

Adapt Eloquent products into cart items at the boundary. Do not let Product models leak into the cart. Build a small adapter:

class ProductItemFactory
{
    public function fromProduct(Product $p, int $qty): CartItem
    {
        return new CartItem($p->sku, $p->name, $qty, $p->price_cents);
    }
}

This is also where you snapshot the price. Because the library stores the price on the item, an existing cart keeps its original price when the catalog changes — usually desired, but be explicit about it.

Use domain events from your own wrapper, not the library's. If leaphly dispatches Symfony events (check for an EventDispatcherInterface constructor argument), don't wire Symfony's dispatcher into Laravel. Instead, emit Laravel events from your service after calling the cart:

class AddToCart
{
    public function __construct(private Cart $cart, private CartRepository $repo) {}

    public function handle(Product $product, int $qty): void
    {
        $this->cart->addItem($this->factory->fromProduct($product, $qty));
        $this->repo->save($this->cart);
        event(new ItemAddedToCart($product->sku, $qty));
    }
}

Keep the action layer thin and put all mutations behind it — carts are one of the few places where a service/action layer pays for itself immediately.

Totals and tax as a decorator. The library computes a raw line total. Wrap it:

class PricedCart
{
    public function __construct(private Cart $cart, private TaxCalculator $tax) {}
    public function subtotal(): int { return $this->cart->getTotal(); }
    public function grandTotal(): int { return $this->subtotal() + $this->tax->on($this->subtotal()); }
}

Never patch the vendor class; decorate.

Testing pattern. Because it's pure PHP with no IO, the cart is trivially unit-testable. Build a CartTestCase with helpers (assertTotal, assertItemCount) and test pricing rules there, not through HTTP tests.

Gotchas and Tips

  • Deprecation is the headline. No PHP 8 compatibility guarantees, no security fixes, no maintenance since 2013. Pin the exact version in composer.json, and isolate it behind your own CartRepository/service interfaces so removal is cheap. Do not let Leaphly\... type hints spread across controllers.
  • Money is almost certainly integer cents. Verify by reading CartItem's constructor. Mixing floats and cents in your adapter is the single most common source of off-by-one totals.
  • Serialization pitfalls. If you ever store the Cart object itself in the session or cache (a tempting shortcut), every item must be serializable, and any event dispatcher or closure property will break serialize(). Prefer persisting rows and rehydrating.
  • Quantity semantics are ambiguous. Check whether addItem on an existing SKU increments quantity or appends a duplicate line. Libraries from this era differ. If it appends, dedupe in your service and decide which behavior checkout expects.
  • No built-in currency handling. Rounding, formatting, and currency codes are your problem. Store the currency on the cart row, not on items.
  • Autoloading age. PSR-0 namespaces and composer.json schema from 2013 can cause silent class-not-found errors under Composer 2. If composer dump-autoload doesn't fix it, add a classmap entry or a small autoload shim rather than fighting the vendor package.
  • Bare new in tests hides DI issues. If the class requires constructor arguments (dispatcher, storage), instantiate through a factory in both tests and app code so you don't discover a missing dependency in production.
  • Extension points to prefer: a custom item class implementing the item interface (for extra fields like meta or discount), a factory to centralize construction, and a repository for persistence. Avoid subclassing Cart itself — its methods are coupled to its internal item collection and subclassing tends to break removeItem/recalculation assumptions.
  • Debugging tips: dump $cart->getItems() and per-item totals rather than the grand total; mismatches are nearly always a stale price snapshot or a duplicated line. Add a dd($this->cart->getItems()) at the top of your action before suspecting the library.
  • Have an exit plan. Given 29 stars and deprecation, write the interface first (CartInterface with add, remove, items, subtotal) so a later swap to a maintained library is an adapter change. Budget this from day one — it is the main reason to use this package at all, as a small, dependency-free domain model you own the boundaries of.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo