Shopware 6.7 introduces significant enhancements to its rule builder system, providing developers with more flexibility and control over custom business logic implementation. This blog post will walk you through the technical process of creating a custom rule builder field that integrates seamlessly with Shopware's existing rule engine.
Understanding Shopware 6.7 Rule Builder Architecture
Before diving into implementation details, it's crucial to understand how Shopware 6.7's rule builder system works. The rule builder is a core component that allows merchants to define conditional logic for various business scenarios such as product pricing, shipping calculations, and promotional campaigns.
In Shopware 6.7, the rule builder has been refactored to use a more modular approach with improved performance and extensibility. The system now relies on a registry pattern where each rule condition is registered as a separate service, making it easier to extend without modifying core files.
Prerequisites and Setup
To create a custom rule builder field in Shopware 6.7, you'll need:
- A working Shopware 6.7 installation
- Basic understanding of Symfony services and dependency injection
- Familiarity with Shopware's administration interface development
- Knowledge of JavaScript/TypeScript and Vue.js (for admin interface)
Step 1: Creating the Rule Condition Service
The first step in implementing a custom rule builder field is to create the backend service that will handle the condition logic. This service must implement the RuleConditionInterface provided by Shopware.
<?php declare(strict_types=1);
namespace YourPlugin\Rule;
use Shopware\Core\Framework\Rule\Rule;
use Shopware\Core\Framework\Rule\RuleComparison;
use Shopware\Core\Framework\Rule\RuleContext;
use Symfony\Component\Validator\Constraints\NotBlank;
class CustomProductRule extends Rule
{
protected ?string $productNumber = null;
public function getOperators(): array
{
return [
self::OPERATOR_EQ => 'Equals',
self::OPERATOR_NEQ => 'Not equals',
self::OPERATOR_CONTAINS => 'Contains',
];
}
public function match(RuleContext $context): bool
{
if (!$this->productNumber) {
return false;
}
$product = $context->getProduct();
if (!$product) {
return false;
}
switch ($this->operator) {
case self::OPERATOR_EQ:
return $product->getProductNumber() === $this->productNumber;
case self::OPERATOR_NEQ:
return $product->getProductNumber() !== $this->productNumber;
case self::OPERATOR_CONTAINS:
return str_contains($product->getProductNumber(), $this->productNumber);
}
return false;
}
public function getConstraints(): array
{
return [
'productNumber' => [new NotBlank()],
];
}
public function getName(): string
{
return 'custom_product_rule';
}
}
Step 2: Registering the Rule Service
After creating the rule condition service, you need to register it as a Symfony service and tag it appropriately for the rule builder system.
<!-- Resources/config/services.xml -->
<service id="YourPlugin\Rule\CustomProductRule">
<tag name="shopware.rule"/>
<tag name="shopware.rule_condition" type="custom_product_rule" />
</service>
Alternatively, using the modern YAML configuration:
# Resources/config/services.yaml
services:
YourPlugin\Rule\CustomProductRule:
tags:
- { name: 'shopware.rule' }
- { name: 'shopware.rule_condition', type: 'custom_product_rule' }
Step 3: Creating the Administration Interface
The administration interface is where merchants will configure your custom rule. This involves creating a Vue component that integrates with Shopware's existing rule builder UI.
<!-- Resources/administration/src/module/sw-rule/component/sw-condition-custom-product.vue -->
<template>
<div class="sw-condition-custom-product">
<sw-select-field
v-model="currentValue"
:label="$tc('your-plugin.rule.customProduct')"
:placeholder="$tc('your-plugin.rule.selectProduct')"
:options="productOptions"
@update:value="onUpdate"
/>
</div>
</template>
<script>
export default {
name: 'sw-condition-custom-product',
props: {
condition: {
type: Object,
required: true
}
},
data() {
return {
currentValue: this.condition.value || '',
productOptions: []
}
},
mounted() {
this.loadProducts();
},
methods: {
onUpdate(value) {
this.currentValue = value;
this.$emit('update:condition', {
...this.condition,
value: value
});
},
async loadProducts() {
try {
const response = await this.repository.search(
new Criteria(100),
this.context
);
this.productOptions = response.items.map(product => ({
label: product.name,
value: product.productNumber
}));
} catch (error) {
console.error('Failed to load products:', error);
}
}
}
}
</script>
Step 4: Registering the Administration Component
You need to register your custom rule component with Shopware's administration system so it appears in the rule builder interface.
// Resources/administration/src/module/sw-rule/index.js
import './component/sw-condition-custom-product';
const { Module } = Shopware;
Module.register('your-plugin-rule', {
// ... other module configuration
ruleConditions: [
{
name: 'custom_product_rule',
component: 'sw-condition-custom-product',
label: 'Your Plugin - Custom Product Rule'
}
]
});
Step 5: Implementing the Rule Condition Logic
The core logic of your rule condition needs to be carefully implemented to ensure it integrates properly with Shopware's context and performance requirements. Here's an enhanced version that handles edge cases:
<?php declare(strict_types=1);
namespace YourPlugin\Rule;
use Shopware\Core\Framework\Rule\Rule;
use Shopware\Core\Framework\Rule\RuleContext;
use Symfony\Component\Validator\Constraints\NotBlank;
class AdvancedProductRule extends Rule
{
protected ?string $productNumber = null;
protected ?array $productNumbers = null;
public function match(RuleContext $context): bool
{
if (!$this->productNumber && !$this->productNumbers) {
return false;
}
$product = $context->getProduct();
if (!$product) {
return false;
}
// Handle single product number comparison
if ($this->productNumber) {
switch ($this->operator) {
case self::OPERATOR_EQ:
return $product->getProductNumber() === $this->productNumber;
case self::OPERATOR_NEQ:
return $product->getProductNumber() !== $this->productNumber;
case self::OPERATOR_CONTAINS:
return str_contains($product->getProductNumber(), $this->productNumber);
}
}
// Handle multiple product numbers comparison
if ($this->productNumbers) {
switch ($this->operator) {
case self::OPERATOR_IN:
return in_array($product->getProductNumber(), $this->productNumbers, true);
case self::OPERATOR_NOT_IN:
return !in_array($product->getProductNumber(), $this->productNumbers, true);
}
}
return false;
}
public function getConstraints(): array
{
return [
'productNumber' => [new NotBlank()],
];
}
public function getName(): string
{
return 'advanced_product_rule';
}
}
Performance Considerations
When implementing custom rule builder fields, performance is crucial. Shopware 6.7 includes several optimizations that you should leverage:
- Caching: Implement proper caching strategies for expensive operations
- Lazy Loading: Load data only when needed
- Database Optimization: Use efficient queries with proper indexing
// Optimized rule condition with caching
public function match(RuleContext $context): bool
{
if (!$this->productNumber) {
return false;
}
$product = $context->getProduct();
if (!$product) {
return false;
}
// Use cached product data when available
$cacheKey = 'product_' . $product->getId() . '_number';
$cachedNumber = $this->cache->get($cacheKey);
if ($cachedNumber === null) {
$cachedNumber = $product->getProductNumber();
$this->cache->set($cacheKey, $cachedNumber, 3600); // Cache for 1 hour
}
return $cachedNumber === $this->productNumber;
}
Testing Your Custom Rule
Comprehensive testing is essential for rule builder extensions. Shopware provides testing utilities that you can leverage:
<?php declare(strict_types=1);
namespace YourPlugin\Test\Rule;
use PHPUnit\Framework\TestCase;
use Shopware\Core\Framework\Test\TestCaseBase\IntegrationTestBehaviour;
use YourPlugin\Rule\CustomProductRule;
class CustomProductRuleTest extends TestCase
{
use IntegrationTestBehaviour;
public function testMatchReturnsTrueForMatchingProducts(): void
{
$rule = new CustomProductRule();
$rule->setOperator(CustomProductRule::OPERATOR_EQ);
$rule->setProductNumber('TEST-123');
// Mock product context
$product = $this->createMock(Product::class);
$product->method('getProductNumber')->willReturn('TEST-123');
$context = $this->createMock(RuleContext::class);
$context->method('getProduct')->willReturn($product);
$result = $rule->match($context);
static::assertTrue($result);
}
}
Integration with Existing Systems
Your custom rule builder field should integrate seamlessly with existing Shopware systems. This includes:
- Product Context: Properly handling product data access
- Order Context: Supporting order-level conditions when applicable
- User Context: Integrating with customer and user data
- Cache Integration: Working with Shopware's caching mechanisms
Conclusion
Creating custom rule builder fields in Shopware 6.7 opens up tremendous possibilities for extending business logic without requiring core modifications. By following the structured approach outlined in this guide, you can create robust, performant rule conditions that integrate smoothly with Shopware's existing architecture.
The key to successful implementation lies in understanding Shopware's service container, properly registering your services, and creating intuitive user interfaces that match the platform's design language. Remember to test thoroughly and consider performance implications when building complex rule conditions.
As you continue developing custom rules, keep an eye on Shopware's documentation updates and community feedback, as the rule builder system continues to evolve with each release, offering more capabilities for developers to build sophisticated business logic extensions.