Shopware 6.7 introduces significant improvements to its rule system, making it more flexible and extensible than ever before. One of the most powerful features is the ability to create custom rule conditions that can be used throughout the platform's commerce engine. This blog post will guide you through the complete process of registering a custom rule condition in Shopware 6.7.

Understanding Rule Conditions in Shopware 6.7

Before diving into implementation, it's crucial to understand what rule conditions are and how they work in Shopware 6.7. Rule conditions are used to evaluate specific criteria within the platform's business logic. They're commonly used for:

  • Product pricing rules
  • Shipping method calculations
  • Promotion eligibility
  • Customer group assignments
  • Cart rule evaluations

In Shopware 6.7, the rule system has been enhanced with better performance optimizations and more intuitive APIs for developers to extend functionality.

Prerequisites and Setup

To create a custom rule condition, you'll need:

  1. A working Shopware 6.7 installation
  2. Basic knowledge of Symfony and PHP
  3. Understanding of Shopware's service container and dependency injection
  4. Familiarity with the platform's event system

Creating the Custom Rule Condition Class

The first step in creating a custom rule condition is to create the condition class itself. This class will extend Shopware's base condition class and implement the necessary interfaces.

<?php declare(strict_types=1);

namespace MyPlugin\Rule;

use Shopware\Core\Framework\Rule\Rule;
use Shopware\Core\Framework\Rule\RuleScope;
use Symfony\Component\Validator\Constraints as Assert;

class CustomProductCondition extends Rule
{
    protected ?string $productId = null;
    
    protected bool $isNot = false;
    
    public function __construct(?string $productId = null, bool $isNot = false)
    {
        $this->productId = $productId;
        $this->isNot = $isNot;
    }
    
    public function getName(): string
    {
        return 'custom_product_condition';
    }
    
    public function match(RuleScope $scope): bool
    {
        if (!$scope instanceof ProductScope) {
            return false;
        }
        
        $product = $scope->getProduct();
        
        if (!$product || !$this->productId) {
            return false;
        }
        
        $result = $product->getId() === $this->productId;
        
        return $this->isNot ? !$result : $result;
    }
    
    public function getConstraints(): array
    {
        return [
            'productId' => [new Assert\NotBlank()],
            'isNot' => [new Assert\Type('bool')],
        ];
    }
}

Creating the Condition Scope

In Shopware 6.7, rule conditions require specific scope implementations. You'll need to create a custom scope class that defines how your condition should be evaluated.

<?php declare(strict_types=1);

namespace MyPlugin\Rule;

use Shopware\Core\Framework\Rule\RuleScope;
use Shopware\Core\Framework\Rule\RuleScopeInterface;
use Shopware\Core\System\SalesChannel\SalesChannelContext;

class ProductScope implements RuleScopeInterface
{
    private ?string $productId = null;
    
    private SalesChannelContext $context;
    
    public function __construct(
        string $productId,
        SalesChannelContext $context
    ) {
        $this->productId = $productId;
        $this->context = $context;
    }
    
    public function getProductId(): ?string
    {
        return $this->productId;
    }
    
    public function getContext(): SalesChannelContext
    {
        return $this->context;
    }
}

Registering the Condition with Dependency Injection

The next step involves registering your custom rule condition with Shopware's service container. This is done through the services.xml file in your plugin's configuration directory.

<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="MyPlugin\Rule\CustomProductCondition">
            <tag name="shopware.rule_condition"/>
            <argument type="service" id="Shopware\Core\Framework\Validation\DataValidator"/>
        </service>
        
        <service id="MyPlugin\Rule\ProductScope">
            <argument type="service" id="Shopware\Core\System\SalesChannel\SalesChannelContext"/>
        </service>
    </services>
</container>

Creating the Rule Configuration

To make your custom rule condition available in the admin interface, you need to create a configuration file that defines how it should be displayed and configured.

<?php declare(strict_types=1);

namespace MyPlugin\Rule;

use Shopware\Core\Framework\Rule\RuleConfig;
use Symfony\Component\Validator\Constraints as Assert;

class CustomProductConditionConfig extends RuleConfig
{
    public function __construct()
    {
        parent::__construct();
        
        $this->add('productId', [
            'type' => 'text',
            'label' => 'Product ID',
            'required' => true,
        ]);
        
        $this->add('isNot', [
            'type' => 'checkbox',
            'label' => 'Negate Condition',
            'default' => false,
        ]);
    }
    
    public function getRule(): string
    {
        return CustomProductCondition::class;
    }
}

Implementing the Rule Configuration Service

You'll need to create a service that handles the configuration and registration of your rule conditions.

<?php declare(strict_types=1);

namespace MyPlugin\Rule;

use Shopware\Core\Framework\Rule\RuleConfigServiceInterface;
use Shopware\Core\Framework\Rule\RuleConfig;

class CustomRuleConfigService implements RuleConfigServiceInterface
{
    public function getConfig(string $ruleClass): ?RuleConfig
    {
        if ($ruleClass === CustomProductCondition::class) {
            return new CustomProductConditionConfig();
        }
        
        return null;
    }
    
    public function getAvailableRules(): array
    {
        return [
            CustomProductCondition::class => 'Custom Product Condition',
        ];
    }
}

Registering the Configuration Service

Don't forget to register your configuration service in the services.xml file:

<service id="MyPlugin\Rule\CustomRuleConfigService">
    <tag name="shopware.rule_config_service"/>
</service>

Creating the Admin Interface Integration

Shopware 6.7 provides enhanced tools for creating admin interfaces for rule conditions. You'll need to create a Vue component that handles the configuration UI.

<template>
    <div class="custom-product-condition">
        <sw-field 
            type="text" 
            :label="$t('custom-product-condition.product-id')"
            v-model="config.productId"
            required
        />
        
        <sw-checkbox-field
            :label="$t('custom-product-condition.negate')"
            v-model="config.isNot"
        />
    </div>
</template>

<script>
export default {
    name: 'custom-product-condition',
    props: {
        config: {
            type: Object,
            required: true
        }
    }
}
</script>

Implementing the Rule Condition Service

To ensure your custom condition works seamlessly with Shopware's rule system, create a dedicated service that manages all aspects of the condition.

<?php declare(strict_types=1);

namespace MyPlugin\Rule;

use Shopware\Core\Framework\Rule\RuleConditionServiceInterface;
use Shopware\Core\Framework\Rule\RuleScope;
use Shopware\Core\Framework\Rule\RuleContext;

class CustomProductConditionService implements RuleConditionServiceInterface
{
    public function match(Rule $rule, RuleScope $scope): bool
    {
        if (!$rule instanceof CustomProductCondition) {
            return false;
        }
        
        if (!$scope instanceof ProductScope) {
            return false;
        }
        
        $product = $scope->getProduct();
        if (!$product || !$rule->getProductId()) {
            return false;
        }
        
        $result = $product->getId() === $rule->getProductId();
        return $rule->isNot() ? !$result : $result;
    }
    
    public function getRuleClass(): string
    {
        return CustomProductCondition::class;
    }
}

Performance Considerations

When implementing custom rule conditions in Shopware 6.7, consider performance implications:

  1. Caching: Implement appropriate caching mechanisms for frequently used conditions
  2. Database Queries: Minimize database hits within condition logic
  3. Memory Usage: Be mindful of memory consumption during rule evaluation
  4. Indexing: Ensure proper database indexing for condition parameters
<?php declare(strict_types=1);

class OptimizedCustomProductCondition extends Rule
{
    // ... previous implementation
    
    public function match(RuleScope $scope): bool
    {
        // Add caching layer to prevent repeated evaluations
        if ($this->shouldCache()) {
            return $this->getCachedResult($scope);
        }
        
        // ... original matching logic
    }
    
    private function shouldCache(): bool
    {
        return $this->productId !== null;
    }
}

Testing Your Custom Rule Condition

Proper testing is crucial for rule conditions. Create unit tests to ensure your implementation works correctly:

<?php declare(strict_types=1);

namespace MyPlugin\Tests\Rule;

use PHPUnit\Framework\TestCase;
use MyPlugin\Rule\CustomProductCondition;
use Shopware\Core\System\SalesChannel\SalesChannelContext;

class CustomProductConditionTest extends TestCase
{
    public function testConditionMatches(): void
    {
        $condition = new CustomProductCondition('product-123');
        
        // Mock product and context
        $mockProduct = $this->createMock(Product::class);
        $mockProduct->method('getId')->willReturn('product-123');
        
        $context = $this->createMock(SalesChannelContext::class);
        $scope = new ProductScope('product-123', $context);
        
        $result = $condition->match($scope);
        $this->assertTrue($result);
    }
    
    public function testConditionDoesNotMatch(): void
    {
        $condition = new CustomProductCondition('product-123');
        
        $mockProduct = $this->createMock(Product::class);
        $mockProduct->method('getId')->willReturn('product-456');
        
        $context = $this->createMock(SalesChannelContext::class);
        $scope = new ProductScope('product-123', $context);
        
        $result = $condition->match($scope);
        $this->assertFalse($result);
    }
}

Best Practices and Recommendations

When creating custom rule conditions in Shopware 6.7, follow these best practices:

  1. Keep Conditions Simple: Each condition should focus on a single business logic point
  2. Use Proper Validation: Always validate input parameters to prevent runtime errors
  3. Implement Caching: Cache frequently used conditions to improve performance
  4. Handle Edge Cases: Consider null values, empty strings, and unexpected inputs
  5. Document Your Conditions: Provide clear documentation for other developers
  6. Follow Naming Conventions: Use consistent naming patterns that align with Shopware's standards

Conclusion

Creating custom rule conditions in Shopware 6.7 opens up tremendous possibilities for extending your e-commerce platform's functionality. By following the steps outlined in this guide, you can create robust, performant rule conditions that integrate seamlessly with Shopware's existing commerce engine.

The key to successful implementation lies in understanding how Shopware's rule system works, properly structuring your code, and considering performance implications from the start. With proper testing and documentation, your custom rule conditions will provide valuable functionality while maintaining the stability and performance of your Shopware installation.

Remember to test thoroughly in different scenarios, monitor performance metrics, and keep your implementation updated with Shopware 6.7's ongoing improvements and best practices. The enhanced rule system in Shopware 6.7 makes it easier than ever to create sophisticated business logic that can be reused throughout your platform.