freshbitsweb/cart-manager
Freshbitsweb Cart Manager is a lightweight Laravel package for e-commerce carts. It manages items, quantities, prices, totals, taxes, discounts, and multiple cart instances through a clean, fluent API, making it easy to add robust cart functionality to any Laravel application.
1. Install via Composer
composer require freshbitsweb/cart-manager
On Laravel 5.5+ the service provider and Cart facade alias are auto-discovered. On older versions you'll need to register Freshbitsweb\CartManager\CartManagerServiceProvider and alias Cart in config/app.php. The composer.json constraints target older Laravel/PHP lines — verify them before the spike rather than mid-integration.
2. Publish the config (and migrations if you want database storage)
php artisan vendor:publish --provider="Freshbitsweb\CartManager\CartManagerServiceProvider"
Read the generated config/cart-manager.php first. It is your map of storage driver, session keys, and tax/discount hooks. If you intend to persist carts (guest → user merge, abandoned-cart recovery, order drafts), also run the published migration:
php artisan migrate
3. Where to look first
config/cart-manager.php — storage driver (session vs database), cart instance naming, tax/discount configuration.4. First use case: add-to-cart on a product page
use Freshbitsweb\CartManager\Facades\Cart;
public function addToCart(Request $request, Product $product)
{
Cart::add(
$product->id,
$product->name,
$request->integer('quantity', 1),
$product->price,
['size' => $request->input('size')] // custom attributes
);
return redirect()->route('cart.index');
}
public function index()
{
return view('cart.index', [
'items' => Cart::content(),
'subtotal' => Cart::subtotal(),
'tax' => Cart::tax(),
'total' => Cart::total(),
]);
}
Confirm the exact add() argument order against the README before wiring it into a controller — it's a sparsely-documented package and the signature is the one thing you don't want to guess.
Wrap it in a thin application service. Don't call Cart::* directly from controllers and Blade views. Introduce App\Cart\CartService (or a CartManager adapter) that exposes addItem(), removeItem(), clear(), recalculate(), and toOrder(). This is the single most important pattern for a package on a 2023 release line with a small maintainer footprint — a future swap or a 2.0 bump becomes one file, not a grep across the codebase.
Multiple cart instances for different contexts. The package's instance() concept is what enables a single user to hold a "wishlist", a "gift" cart, and a "default" cart simultaneously. Assign each instance a named key (Cart::instance('wishlist')) and treat the default instance as your checkout cart. This is far cheaper than building three separate models.
Session storage for the happy path, database storage when you need recovery. Session carts are the right default for an anonymous shopper — fast, no writes, no schema. Reach for the database driver the moment one of these is true: you want abandoned-cart emails, you need multi-device continuity, or a guest cart must survive into a logged-in user's account. Implement a mergeGuestCartIntoUser() method on your service that runs on Login and re-keys the cart to the user.
Taxes and discounts belong in config, not in controllers. Let the package's tax/discount configuration do the arithmetic so subtotal(), tax(), and total() stay internally consistent. If you override totals manually you will fight the package on every recalculation and lose.
Use events for anything that isn't cart state. ItemAdded, ItemRemoved, CartCleared (or whatever the package exposes) are the right place to fire analytics, emit domain events, invalidate cached cart counts, or update a "recently viewed" table. Subscribe in an EventServiceProvider or a dedicated listener module — not inline in the controller.
Pricing discipline: store and read integers. Even if the package accepts floats for price, keep a price_in_cents column in your products table and pass whole cents in. Format for display at the view layer only. This is the single most common source of "the cart total is off by one cent" bugs and it is entirely avoidable.
Snapshot product data at add-time. Don't resolve product name, price, or image in the cart view — store them as custom attributes when the item is added. Otherwise a price change silently re-prices every existing cart. Your order table should be an immutable record of what was actually in the cart at checkout.
The README is thin and the last release is from early 2023. Budget the spike to read the facade source directly rather than the documentation — the method signatures are the ground truth. Pin to an exact version ("freshbitsweb/cart-manager": "1.6.*") until you've read the changelog for the last two minors, then relax to caret.
Config caching silently breaks published-config assumptions. php artisan config:cache in production will not pick up runtime overrides of config('cart-manager.*'). If you need per-tenant or per-store tax rates, resolve them in your service and pass them per-call rather than mutating config at runtime.
Session carts and CSRF/session regeneration. On php artisan session:regenerate (login, privilege escalation) the session driver can lose the cart if the underlying storage key changes. Test the login-merge flow explicitly — this is the #1 silent data-loss bug in cart integrations.
Database driver needs a cart owner key. The package will not guess "whose cart is this." You must pass a user identifier (or a guest UUID stored in a cookie) when using database storage. Plan for the guest-to-user migration path on day one; retrofitting it later requires a data backfill.
Floating-point totals are approximate. total() computed from float prices will occasionally be off by a fraction of a cent. Round at presentation and store order totals as integers. Never compare cart totals with === in tests.
Multiple instances mean multiple session keys. Clearing "the cart" only clears the current instance. If you're building a checkout flow, be explicit: Cart::instance('default')->destroy() — not just Cart::destroy() — and write a test that proves one instance's clear doesn't touch another.
Extension points to check before you need them. Two specific questions to answer during the spike: (a) can you register a custom tax calculator or discount strategy, and (b) is the storage resolver swappable via container binding? If both answers are "no", you are committing to the package's opinionated defaults and should scope accordingly. If "yes", you have the escape hatch that makes adoption safe.
Events are your observability layer. On an older package, log from event listeners rather than patching vendor code. Subscribing to ItemAdded/ItemRemoved gives you a clean audit trail, a hook for stock reservation, and a debugging trail when "the item disappeared from the cart" bug reports come in — without a single line of modification to vendor/.
Testing tip. Bind a fresh cart instance per test in setUp(), and prefer the array/in-memory storage driver (if available) over the session driver in unit tests. Session-backed cart tests are slow and order-dependent; they belong in one or two end-to-end feature tests, not the whole suite.
How can I help you explore Laravel packages today?