vespolina/commerce-bundle
Vespolina Commerce Bundle brings e-commerce building blocks to Symfony applications, including product catalogs, carts, orders, pricing, and checkout workflows. It provides reusable commerce services and Doctrine-backed models for assembling flexible online stores.
First, the reality check. vespolina/commerce-bundle is a Symfony 2 bundle, not a Laravel package. There is no service provider, no config/commerce.php, no facade. Its composer.json targets symfony/symfony ~2.x and PHP 5.x-era code, last tagged 2014-07-27, with 9 stars and 0 dependents. Running composer require vespolina/commerce-bundle inside a modern Laravel app will fail dependency resolution against illuminate/* and current symfony/* components. So "getting started" means one of two things:
| Path | Why |
|---|---|
composer.json |
Confirms the Symfony 2 constraint and the vespolina/* library set (core, product, cart, order, pricing, taxation) |
DependencyInjection/VespolinaCommerceExtension.php + Resources/config/services.xml |
The service graph and class parameters — the real "documentation" for this bundle |
Model/ |
Cart, CartItem, Order, OrderItem, Product, Price — mostly plain PHP objects |
Controller/CheckoutController.php + Checkout/ |
The checkout process and its ordered steps |
Form/Type/, EventListener/ |
How forms and domain events were wired |
The Model/ classes are largely POPOs with no Symfony coupling, so they PSR-4 fine inside Laravel:
// app/Domain/Commerce/CartItem.php (ported from Vespolina\CommerceBundle\Model\CartItem)
class CartItem
{
protected Product $product;
protected int $quantity = 1;
protected Price $unitPrice; // snapshot, not a live product lookup
protected ?Order $order = null;
}
Then give them persistence (Eloquent models wrapping the domain objects, or just Eloquent directly) and a CommerceServiceProvider that binds CartService, OrderService, PricingService interfaces. That's the fastest path to value from this repo.
Vespolina delegated all mutation to managers (ProductManager, CartManager, OrderManager) configured as services with overridable class parameters. In Laravel, translate that shape to constructor-injected services bound in a provider:
$this->app->singleton(CartService::class, fn ($app) => new CartService(
$app->make(CartRepository::class),
$app->make(PricingService::class),
));
Keep the manager methods (addToCart, updateQuantity, findOpenCartFor, completeOrder) as the public API so the ported model code stays familiar.
The Symfony bundle used a session-backed cart plus a cart identifier/provider. The Laravel equivalent that respects the same seam:
CartProvider interface with SessionCartProvider and DatabaseCartProvider.The bundle's CheckoutProcess runs an ordered list of CheckoutSteps, each of which can validate and halt the flow. That maps cleanly onto Laravel:
// app/Checkout/CheckoutProcess.php
public function advance(Order $order, array $input): Order
{
return app(Pipeline::class)
->send(new CheckoutContext($order, $input))
->through(config('commerce.checkout.steps')) // [AddressStep, ShippingStep, PaymentStep, ReviewStep]
->then(fn (CheckoutContext $ctx) => $ctx->order);
}
Persist checkout_step on the order — never derive it from "what's filled in."
The bundle dispatched cart/order events to listeners for pricing, stock, email, and payment handoff. Mirror them:
CartUpdated → recalculate totalsOrderPlaced → invoice, email, stock decrementOrderCompleted (post-payment) → fulfillmentUse queued listeners for anything touching payment or email so the checkout request stays fast.
Resolve price per product/option and tax per line item at the moment of adding to cart, and store the resolved unit price, tax rate, and tax amount on the line item. Display totals from stored values only. This is the single most important behavioural lesson in the package.
Resources/views/ is Twig with path(), render(controller(...)), and Symfony form themes. Rewrite Blade templates from scratch; don't attempt a conversion.
array() notation, and possibly deprecated functions (each(), create_function()) that fatal or flood deprecations on PHP 8. Lint anything you copy before it enters app/.%vespolina_commerce.model.cart.class%), not autowiring. In Laravel this becomes interface bindings — don't try to mimic the parameter pattern, it buys you nothing.Document/ and Entity/ side by side, that's why — and it's a common source of stale-product bugs. In a port, keep one store.Money VO; don't port float math.Resources/config/services.xml as the source of truth and grep for event dispatch sites to reconstruct behaviour.How can I help you explore Laravel packages today?