Shopware 6.7 introduces powerful enhancements to cart functionality, allowing developers to extend the core cart system with custom line item types. This feature enables merchants to implement specialized product offerings such as loyalty points, service fees, or promotional discounts that behave differently from standard products.
Understanding Cart Line Items
In Shopware's cart system, line items represent individual components of a customer's shopping cart. Each line item contains metadata about the product or service, including price calculations, quantities, and tax information. The core system supports several predefined line item types including product, credit, shipping costs, and promotion.
Custom line item types extend this functionality by allowing developers to create specialized behaviors that integrate seamlessly with the existing cart infrastructure while maintaining full compatibility with Shopware's core cart calculations.
Creating a Custom Line Item Type
To implement a custom line item type, we need to register it within the service container and define its behavior through various event listeners and processors.
Step 1: Registering the Custom Line Item Type
First, create a new service definition in your plugin's services.xml file:
<service id="YourPlugin\Cart\CustomLineItemProcessor">
<argument type="service" id="Shopware\Core\Checkout\Cart\CartPersisterInterface"/>
<argument type="service" id="Shopware\Core\Checkout\Cart\Price\QuantityPriceCalculator"/>
<tag name="kernel.event_subscriber"/>
</service>
<service id="YourPlugin\Cart\CustomLineItemValidator">
<argument type="service" id="Shopware\Core\System\SalesChannel\SalesChannelContext"/>
<tag name="kernel.event_subscriber"/>
</service>
Step 2: Implementing the Line Item Processor
The core functionality of custom line items is implemented through a processor that handles price calculations and validation:
<?php declare(strict_types=1);
namespace YourPlugin\Cart;
use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Cart\CartBehavior;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
use Shopware\Core\Checkout\Cart\Price\Struct\CalculatedPrice;
use Shopware\Core\Checkout\Cart\Price\Struct\PriceCollection;
use Shopware\Core\Checkout\Cart\Price\QuantityPriceCalculator;
use Shopware\Core\Checkout\Cart\Tax\Struct\CalculatedTaxCollection;
use Shopware\Core\Checkout\Cart\Tax\Struct\TaxRuleCollection;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
class CustomLineItemProcessor
{
private QuantityPriceCalculator $priceCalculator;
public function __construct(QuantityPriceCalculator $priceCalculator)
{
$this->priceCalculator = $priceCalculator;
}
public function process(LineItem $lineItem, Cart $cart, CartBehavior $behavior): void
{
// Check if this is our custom line item type
if ($lineItem->getType() !== 'custom_promotion') {
return;
}
// Calculate price based on custom logic
$price = $this->calculateCustomPrice($lineItem);
$lineItem->setPrice($price);
$lineItem->setLabel('Custom Promotion');
}
private function calculateCustomPrice(LineItem $lineItem): CalculatedPrice
{
$quantity = $lineItem->getQuantity();
$customValue = $lineItem->getPayload()['custom_value'] ?? 0;
// Custom pricing logic - e.g., percentage discount based on cart total
$unitPrice = ($customValue / 100) * $this->getCartTotal($lineItem);
$totalPrice = $unitPrice * $quantity;
return new CalculatedPrice(
$totalPrice,
$unitPrice,
new PriceCollection(),
new CalculatedTaxCollection(),
$quantity
);
}
private function getCartTotal(LineItem $lineItem): float
{
// Implementation to retrieve cart total
// This would typically involve accessing the cart context
return 100.0; // Placeholder value
}
}
Step 3: Creating the Line Item Type Service
Register your custom line item type through a service that extends Shopware's core functionality:
<?php declare(strict_types=1);
namespace YourPlugin\Cart;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Shopware\Core\Checkout\Cart\Event\CartBeforeCalculateEvent;
use Shopware\Core\Checkout\Cart\Event\CartLineItemAddedEvent;
use Shopware\Core\Checkout\Cart\Event\CartLineItemRemovedEvent;
class CustomLineItemSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
CartBeforeCalculateEvent::class => 'onCartBeforeCalculate',
CartLineItemAddedEvent::class => 'onLineItemAdded',
CartLineItemRemovedEvent::class => 'onLineItemRemoved',
];
}
public function onCartBeforeCalculate(CartBeforeCalculateEvent $event): void
{
$cart = $event->getCart();
foreach ($cart->getLineItems() as $lineItem) {
if ($lineItem->getType() === 'custom_promotion') {
// Apply custom processing logic
$this->processCustomLineItem($lineItem, $cart);
}
}
}
private function processCustomLineItem(LineItem $lineItem, Cart $cart): void
{
// Custom business logic for handling the line item
$payload = $lineItem->getPayload();
if (!isset($payload['processed'])) {
// Add custom processing logic here
$payload['processed'] = true;
$lineItem->setPayload($payload);
}
}
public function onLineItemAdded(CartLineItemAddedEvent $event): void
{
$lineItem = $event->getLineItem();
if ($lineItem->getType() === 'custom_promotion') {
// Handle custom logic when item is added to cart
$this->handleCustomAddition($lineItem);
}
}
private function handleCustomAddition(LineItem $lineItem): void
{
// Custom handling logic for new line items
// This could include validation, additional data population, etc.
}
public function onLineItemRemoved(CartLineItemRemovedEvent $event): void
{
$lineItem = $event->getLineItem();
if ($lineItem->getType() === 'custom_promotion') {
// Handle custom logic when item is removed from cart
}
}
}
Payload Structure and Data Management
Custom line items require a well-defined payload structure to store additional information needed for processing. The payload should be designed with performance in mind, as it's serialized and stored with the cart:
// Example payload structure for a custom promotion line item
$payload = [
'promotion_id' => 'uuid',
'discount_type' => 'percentage', // or 'fixed'
'discount_value' => 15.0,
'valid_from' => '2023-01-01',
'valid_until' => '2023-12-31',
'conditions' => [
'min_cart_amount' => 100.0,
'customer_groups' => ['group1', 'group2'],
],
'processed' => true,
];
$lineItem = new LineItem('custom_promotion', $payload);
$lineItem->setQuantity(1);
Validation and Error Handling
Implement proper validation to ensure custom line items are correctly configured before processing:
<?php declare(strict_types=1);
namespace YourPlugin\Cart;
use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
use Shopware\Core\Framework\Validation\DataValidationDefinition;
use Shopware\Core\Framework\Validation\DataValidator;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Type;
class CustomLineItemValidator
{
public function validate(LineItem $lineItem, Cart $cart): array
{
$errors = [];
// Validate required fields in payload
if (!isset($lineItem->getPayload()['discount_value'])) {
$errors[] = 'Discount value is required for custom promotions';
}
if (!isset($lineItem->getPayload()['promotion_id'])) {
$errors[] = 'Promotion ID is required';
}
// Validate numeric values
if (isset($lineItem->getPayload()['discount_value']) &&
!is_numeric($lineItem->getPayload()['discount_value'])) {
$errors[] = 'Discount value must be numeric';
}
return $errors;
}
}
Performance Considerations
When implementing custom line item types, consider the following performance implications:
- Serialization Overhead: Custom payloads are serialized with each cart operation, so keep data structures minimal
- Database Queries: Avoid expensive database lookups within processing logic
- Caching Strategy: Implement appropriate caching for frequently accessed data
- Event Processing: Be mindful of event subscribers that fire during every cart operation
Integration with Existing Systems
Custom line items integrate seamlessly with Shopware's existing systems:
// Accessing custom line items in templates
$customItems = array_filter($cart->getLineItems()->toArray(), function($item) {
return $item->getType() === 'custom_promotion';
});
// Using custom line items in order processing
foreach ($order->getLineItems() as $lineItem) {
if ($lineItem->getType() === 'custom_promotion') {
// Process custom promotion logic during order creation
}
}
Testing Custom Line Items
Thorough testing ensures custom line item functionality works correctly:
public function testCustomLineItemProcessing(): void
{
$cart = new Cart('test-cart');
$lineItem = new LineItem('custom_promotion', [
'discount_value' => 15.0,
'promotion_id' => 'test-promo-123'
]);
$lineItem->setQuantity(1);
$cart->add($lineItem);
// Assert that line item was processed correctly
$this->assertEquals('custom_promotion', $lineItem->getType());
$this->assertNotNull($lineItem->getPrice());
}
Conclusion
Shopware 6.7's enhanced cart system provides robust capabilities for extending functionality with custom line item types. By following the patterns outlined in this guide, developers can create sophisticated cart behaviors that maintain compatibility with existing Shopware features while delivering unique business logic.
The key to successful implementation lies in proper event handling, efficient data management, and thorough testing. Custom line items offer tremendous flexibility for implementing complex pricing rules, loyalty programs, and specialized product offerings that can significantly enhance the shopping experience while maintaining system performance and stability.
This approach ensures that custom functionality integrates seamlessly with Shopware's core cart processing, providing merchants with powerful tools to create differentiated shopping experiences without compromising the platform's reliability or performance.