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 Bundle Laravel Package

sylius/cart-bundle

Sylius CartBundle provides flexible shopping cart functionality for Symfony and Sylius applications, including cart and cart item models, add/update/remove operations, totals calculation, and integration with ecommerce workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

Understand the two realities before you start: this is a Symfony bundle, not a Laravel package, and it is archived and deprecated in favor of Sylius/Sylius (where its code lives on as Sylius\Component\Order). Your realistic "first use case" is therefore one of:

  1. You inherited a legacy Symfony app pinned to sylius/cart-bundle and need to make a safe change or plan a migration.
  2. You are reading it as a reference model while building your own Laravel cart domain.

Do not composer require this into a modern Laravel project β€” the Symfony/PHP constraints will not resolve cleanly and there is no maintenance path.

Where to look first (in the repo):

Path Why it matters
Model/Cart.php, Model/CartItem.php The domain. Note the cart/line split β€” copy this boundary.
Provider/CartProvider.php Session-backed cart identity (getCart, setCart, abandonCart).
Resolver/ItemResolver.php Turns a raw request payload into a normalized cart line.
Resources/config/doctrine/*.orm.xml Doctrine XML mappings β€” there are no annotations or attributes.
CartEvents.php The extension seams (add/remove/update).
Processor/* The totals pipeline β€” how subtotal/tax/total are computed.
Controller/CartItemController.php Add/remove/clear endpoints.

Minimal wiring in a legacy Symfony app:

// app/AppKernel.php
$bundles = [ /* ... */ new Sylius\Bundle\CartBundle\SyliusCartBundle() ];
# config.yml
sylius_cart:
    driver: doctrine/orm
    classes:
        cart:      AppBundle\Entity\Cart
        cart_item: AppBundle\Entity\CartItem

Then implement ItemResolverInterface for your product entity, register it as a service, and expose the bundle's routes.

First concrete use case β€” the "add to cart" round trip. Trace it end to end: CartItemController::addAction β†’ ItemResolver normalizes the payload β†’ CartProvider supplies the current cart β†’ an add operation appends the line β†’ processors recalculate totals β†’ redirect. If you can narrate that flow from the source, you understand the bundle's entire contribution. Everything else is variations on remove/update/clear.

The Laravel equivalent you should actually build β€” start from the schema, not a package:

Schema::create('carts', function (Blueprint $t) {
    $t->id();
    $t->foreignId('user_id')->nullable()->index();
    $t->string('session_id')->nullable()->index();
    $t->string('currency', 3);
    $t->timestamps();
});

Schema::create('cart_items', function (Blueprint $t) {
    $t->id();
    $t->foreignId('cart_id')->constrained()->cascadeOnDelete();
    $t->morphs('buyable');            // product, variant, subscription…
    $t->unsignedInteger('quantity');
    $t->unsignedInteger('unit_price'); // integer minor units β€” never float
    $t->json('options')->nullable();
    $t->timestamps();
    $t->unique(['cart_id', 'buyable_type', 'buyable_id']);
});

First deliverable: a CartManager bound as a scoped singleton that resolves the cart from session or auth and exposes add(), remove(), clear(). That is roughly 90% of what the bundle did.

Implementation Patterns

1. The Provider pattern β€” never let controllers touch the session key. The bundle's CartProvider existed so that "which cart am I operating on?" was answered in exactly one place. Copy that discipline:

final class CartManager
{
    public function __construct(private CartRepository $carts) {}

    public function current(): Cart
    {
        $key = auth()->check()
            ? ['user_id' => auth()->id()]
            : ['session_id' => session()->getId()];

        return $this->carts->firstOrCreate($key, ['currency' => config('shop.currency')]);
    }
}

2. The ItemResolver pattern β€” one funnel from user input to cart line. The resolver took a raw payload and produced a normalized [product, quantity, price, options]. Keep that funnel and make it return a DTO or throw:

final class CartItemResolver
{
    public function resolve(AddToCartData $data): ResolvedItem
    {
        $variant = ProductVariant::findOrFail($data->variantId);
        abort_unless($variant->isPurchasable(), 422, 'Not purchasable');

        return new ResolvedItem(
            buyable:   $variant,
            quantity:  $data->quantity,
            unitPrice: $variant->price, // already minor units
        );
    }
}

3. The processor pipeline β€” totals as a deterministic function. The bundle recalculated subtotal/tax/total via configurable processors rather than inline math in a controller. Mirror this with Laravel's Pipeline or a set of small invokable classes, so add, update, and merge all recompute identically:

$total = app(Pipeline::class)
    ->send($cart)
    ->through([
        ApplyLineSubtotals::class,
        ApplyAdjustments::class,
        ApplyTaxes::class,
        ApplyGrandTotal::class,
    ])->thenReturn();

Trigger recalculation on every mutation β€” never trust a stored total after a quantity change.

4. Operation objects for mutations. Rather than fat controller methods, model each mutation as a small class (AddToCart, UpdateQuantity, RemoveItem, ClearCart) that resolves the cart, mutates lines, recalculates, and dispatches an event. This mirrors the bundle's operation-plus-event split and makes each op independently testable.

5. Guest-to-user merge on login. The bundle's session-backed provider implies cart identity changes at authentication. Handle the merge explicitly: on Login, find the session cart, find or create the user cart, add session lines into it (summing quantities for identical buyable), then abandonCart() the session one. Do this inside a transaction and guard against races (two tabs, double-submit) with a DB lock.

6. Keep the cart/order boundary. The single most valuable idea in the bundle: a cart is not an order. Cart is mutable and session-scoped; order is an immutable snapshot created by converting the cart. If you collapse them into one table you will regret it at your first "customer changed their address after checkout" bug. Model conversion as an explicit transaction that copies lines and freezes prices.

7. Events as extension seams. The bundle exposed CartEvents so other code could react without subclassing. In Laravel, dispatch CartItemAdded, CartUpdated, CartCleared after the mutation commits, and let listeners handle cross-cutting concerns (analytics, promo re-evaluation, inventory holds).

Gotchas and Tips

  • Archived means you own it. No security backports, no PHP/Laravel compatibility work, no escalation path. If this is in a production tree, it is a remediation item with a scheduled exit, not a dependency to extend. Inventory every usage before planning a feature on top of it.

  • Doctrine XML mappings, not attributes. If you go looking for a PHP entity with @ORM annotations you'll waste an hour β€” the models are mapped in Resources/config/doctrine/*.orm.xml. When porting to Eloquent, translate column-by-column; don't assume the model class defines the schema.

  • Quantity is an integer, units are the mature design. Pre-1.0 the line held a plain quantity. The successor design splits a line into per-unit rows, which is what makes serial numbers, per-unit fulfillment, and partial refunds tractable. If your domain needs any of those, adopt the units model from day one β€” retrofitting it later is a painful migration.

  • Money as integers, always. Every price path should be integer minor units. The most common porting bug is a float creeping in at the resolver boundary ($variant->price in cents, a shipping or tax processor computing in dollars). Assert this with tests.

  • Session carts don't scale horizontally. The provider pattern is session-bound, which breaks behind a load balancer without sticky sessions or shared session storage. Prefer database or Redis persistence keyed by session ID, and be ready for cart-merge races on login β€” the silent-data-loss failure mode to watch for.

  • Recalculation is not free. Running the processor pipeline on every mutation is correct but O(lines Γ— adjustments). Cache the pipeline result per request and only recompute the lines that changed if carts grow large.

  • Debugging a legacy install: if totals are wrong, isolate the processor β€” dump cart lines before the pipeline and after each stage. If "add to cart" silently fails, check the ItemResolver first; a resolver returning null or throwing a caught exception is the usual culprit.

  • Extension points if you must modify it in place: implement ItemResolverInterface for your product type, register a custom processor in the totals pipeline via config, and subscribe to CartEvents rather than overriding controllers. Subclassing Cart/CartItem is supported through the classes: config β€” but each override is a future migration liability.

  • Version constraints are the real integration cost. Symfony 2.x/3.x-era components will collide with Laravel 10/11's shared Symfony packages. A timeboxed spike to confirm the conflict is worthwhile before any "just try it" plan; expect composer resolution to fail rather than degrade gracefully.

  • Migration path out: move to Sylius/Sylius if you're staying on the Sylius platform (and consider exposing the shop API rather than embedding it), otherwise port the concepts β€” provider, resolver, operation objects, processor pipeline, cart/order split β€” into native Laravel code. The domain is small enough that a clean reimplementation beats a bridge in almost every case.

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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core