Introduction

Shopware 6.7 represents a significant leap forward in e-commerce platform capabilities, introducing enhanced performance optimizations, improved developer tools, and refined architecture patterns. One of the key aspects of Shopware development is extending functionality through custom components, including Twig filters that enhance template flexibility and data manipulation capabilities.

In this comprehensive guide, we'll explore how to create custom Twig filters in Shopware 6.7, covering the technical implementation details, best practices, and practical applications that will help developers extend their e-commerce platform's templating capabilities.

Understanding Twig Filters in Shopware 6.7

Twig filters are essential components that allow developers to modify variables within templates. In Shopware 6.7, the integration with Symfony's Twig component provides a robust foundation for creating custom filters that can transform data, format output, or perform complex operations directly within your templates.

The modern Shopware 6.7 architecture emphasizes dependency injection and service-oriented design, making it crucial to understand how filters integrate with the platform's service container and lifecycle management.

Prerequisites and Setup

Before diving into filter creation, ensure you have:

  • Shopware 6.7 installed and running
  • A basic understanding of PHP and Symfony concepts
  • Familiarity with Shopware plugin structure
  • Access to the command line for console operations

Creating a Custom Twig Filter Service

The first step in creating a custom Twig filter involves defining a service that implements the Twig\Extension\ExtensionInterface. In Shopware 6.7, this is typically accomplished through a plugin's service definition.

<?php declare(strict_types=1);

namespace YourPlugin\Service;

use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;

class CustomTwigExtension extends AbstractExtension
{
    public function getFilters(): array
    {
        return [
            new TwigFilter('custom_format', [$this, 'formatCustom']),
            new TwigFilter('price_with_tax', [$this, 'formatPriceWithTax']),
        ];
    }

    public function formatCustom($value, $options = [])
    {
        // Custom formatting logic here
        return strtoupper((string)$value);
    }

    public function formatPriceWithTax($price, $taxRate = 0)
    {
        if ($taxRate > 0) {
            return $price * (1 + $taxRate / 100);
        }
        return $price;
    }
}

Plugin Registration and Configuration

To register your custom Twig extension within Shopware 6.7, you need to create a service definition in your plugin's Resources/config/services.xml file:

<?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 https://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="YourPlugin\Service\CustomTwigExtension">
            <tag name="twig.extension"/>
        </service>
    </services>
</container>

Advanced Filter Implementation

Let's explore a more sophisticated example that demonstrates practical use cases in an e-commerce environment:

<?php declare(strict_types=1);

namespace YourPlugin\Service;

use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;

class EcommerceTwigExtension extends AbstractExtension
{
    private EntityRepositoryInterface $productRepository;

    public function __construct(EntityRepositoryInterface $productRepository)
    {
        $this->productRepository = $productRepository;
    }

    public function getFilters(): array
    {
        return [
            new TwigFilter('product_image', [$this, 'getProductImage'], ['is_safe' => ['html']]),
            new TwigFilter('currency_format', [$this, 'formatCurrency'], ['is_safe' => ['html']]),
            new TwigFilter('stock_status', [$this, 'getStockStatus']),
        ];
    }

    public function getProductImage($productId, $size = 'small')
    {
        $criteria = new Criteria([$productId]);
        $criteria->addAssociation('media');
        
        $product = $this->productRepository->search($criteria)->first();
        
        if (!$product || !$product->getMedia()) {
            return '';
        }

        $media = $product->getMedia()->first();
        return sprintf(
            '<img src="%s" alt="%s" class="product-image-%s">',
            $media->getUrl(),
            $product->getName() ?? 'Product Image',
            $size
        );
    }

    public function formatCurrency($amount, $currency = 'EUR', $decimals = 2)
    {
        return number_format((float)$amount, $decimals, ',', '.');
    }

    public function getStockStatus($stock)
    {
        if ($stock > 10) {
            return 'In Stock';
        } elseif ($stock > 0) {
            return 'Low Stock';
        }
        return 'Out of Stock';
    }
}

Performance Considerations

When implementing custom Twig filters in Shopware 6.7, performance optimization becomes crucial. Here are key considerations:

Caching Strategies

<?php declare(strict_types=1);

use Symfony\Contracts\Cache\TagAwareCacheInterface;

class OptimizedTwigExtension extends AbstractExtension
{
    private TagAwareCacheInterface $cache;
    
    public function __construct(TagAwareCacheInterface $cache)
    {
        $this->cache = $cache;
    }

    public function getFilters(): array
    {
        return [
            new TwigFilter('cached_format', [$this, 'formatWithCache'], ['is_safe' => ['html']]),
        ];
    }

    public function formatWithCache($value, $options = [])
    {
        $cacheKey = md5(serialize([$value, $options]));
        
        return $this->cache->get($cacheKey, function () use ($value, $options) {
            // Expensive computation here
            return $this->performComplexOperation($value, $options);
        });
    }

    private function performComplexOperation($value, $options)
    {
        // Simulate expensive operation
        sleep(1);
        return strtoupper((string)$value);
    }
}

Lazy Loading and Service Dependencies

<?php declare(strict_types=1);

class LazyLoadedTwigExtension extends AbstractExtension
{
    private ?EntityRepositoryInterface $productRepository = null;
    
    public function getFilters(): array
    {
        return [
            new TwigFilter('lazy_product_info', [$this, 'getProductInfoLazy']),
        ];
    }

    public function getProductInfoLazy($productId)
    {
        if ($this->productRepository === null) {
            // This would typically be injected via dependency injection
            $this->productRepository = $this->container->get('product.repository');
        }
        
        // Implementation here
        return $this->fetchProductData($productId);
    }
}

Integration with Shopware 6.7 Architecture

Shopware 6.7's modern architecture emphasizes microservices and decoupled components. Custom Twig filters should align with these principles by:

  1. Using proper dependency injection through service containers
  2. Implementing interfaces where appropriate
  3. Following naming conventions consistent with Shopware standards
  4. Ensuring thread safety for multi-request environments

Error Handling and Debugging

Robust error handling is essential for custom Twig filters:

<?php declare(strict_types=1);

class RobustTwigExtension extends AbstractExtension
{
    public function getFilters(): array
    {
        return [
            new TwigFilter('safe_format', [$this, 'safeFormat'], ['is_safe' => ['html']]),
        ];
    }

    public function safeFormat($value, $options = [])
    {
        try {
            if ($value === null) {
                return '';
            }
            
            // Validate input
            if (!is_scalar($value) && !is_array($value)) {
                throw new \InvalidArgumentException('Invalid input type for format filter');
            }
            
            return $this->performFormatting($value, $options);
        } catch (\Exception $e) {
            // Log error but don't break template rendering
            error_log('Twig filter error: ' . $e->getMessage());
            return (string)$value;
        }
    }

    private function performFormatting($value, $options)
    {
        // Your formatting logic here
        return strtoupper((string)$value);
    }
}

Testing Custom Filters

Comprehensive testing ensures reliability in production environments:

<?php declare(strict_types=1);

use PHPUnit\Framework\TestCase;
use Twig\Environment;
use Twig\Loader\ArrayLoader;

class CustomFilterTest extends TestCase
{
    public function testCustomFormatFilter()
    {
        $twig = new Environment(new ArrayLoader([
            'test' => '{{ "hello"|custom_format }}'
        ]));
        
        $twig->addExtension(new CustomTwigExtension());
        
        $result = $twig->render('test');
        $this->assertEquals('HELLO', $result);
    }

    public function testPriceWithTaxFilter()
    {
        $twig = new Environment(new ArrayLoader([
            'test' => '{{ 100|price_with_tax(19) }}'
        ]));
        
        $twig->addExtension(new CustomTwigExtension());
        
        $result = $twig->render('test');
        $this->assertEquals('119', $result);
    }
}

Best Practices and Recommendations

1. Performance Optimization

Always consider caching strategies for computationally expensive operations. Shopware 6.7's built-in cache mechanisms should be leveraged effectively.

2. Security Considerations

When using is_safe flags, ensure output is properly sanitized to prevent XSS vulnerabilities.

3. Maintainability

Keep filter logic simple and focused on single responsibilities. Complex operations should be delegated to service classes.

4. Documentation

Provide clear documentation for your custom filters, including parameter expectations and return values.

Conclusion

Creating custom Twig filters in Shopware 6.7 opens up tremendous possibilities for extending template functionality while maintaining clean, reusable code. The platform's modern architecture provides robust foundations for implementing these extensions with proper dependency management, caching strategies, and error handling.

By following the patterns and best practices outlined in this guide, developers can create powerful, performant custom filters that enhance the Shopware 6.7 e-commerce experience while maintaining compatibility with the platform's evolving standards and requirements.

The integration of Symfony's Twig component in Shopware 6.7 ensures that custom filters benefit from enterprise-grade features, making it easier to build sophisticated templating solutions that can handle complex e-commerce requirements while maintaining optimal performance characteristics.

Remember to test thoroughly, document your implementations, and consider the broader implications of your filter designs within the context of the entire Shopware ecosystem to ensure seamless integration and maintainability over time.