Shopware 6.7 introduces significant enhancements to the order document generation system, providing developers with more flexibility and control over how invoices, delivery notes, and other documents are created and processed. This blog post will explore the technical implementation details of creating custom order document generation functionality in Shopware 6.7.
Understanding Order Document Architecture
In Shopware 6.7, the order document system is built around several core components that work together to generate, store, and deliver documents. The primary architecture consists of:
- Document Generator Services: Responsible for creating document content
- Document Template System: Handles template rendering and customization
- Document Repository: Manages document storage and retrieval
- Document Context: Provides context information for document generation
The system leverages Symfony's dependency injection container and follows a service-oriented architecture pattern.
Core Interfaces and Classes
Before diving into implementation, it's crucial to understand the key interfaces that form the foundation of document generation:
// Document generator interface
interface DocumentGeneratorInterface
{
public function generate(array $data, Context $context): DocumentResponse;
}
// Document context interface
interface DocumentContextInterface
{
public function getSalesChannelId(): string;
public function getLanguageId(): string;
public function getCurrencyId(): string;
}
Shopware 6.7 introduces enhanced interfaces and classes that provide more granular control over the document generation process.
Creating a Custom Document Generator
To implement a custom order document generator, we need to create a service that implements the appropriate interface and register it within the dependency injection container.
<?php declare(strict_types=1);
namespace MyPlugin\Document;
use Shopware\Core\Checkout\Order\OrderEntity;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
class CustomOrderDocumentGenerator
{
private DocumentGeneratorInterface $documentGenerator;
private OrderRepository $orderRepository;
public function __construct(
DocumentGeneratorInterface $documentGenerator,
OrderRepository $orderRepository
) {
$this->documentGenerator = $documentGenerator;
$this->orderRepository = $orderRepository;
}
public function generateCustomDocument(string $orderId, Context $context): string
{
$criteria = new Criteria([$orderId]);
$criteria->addAssociation('lineItems');
$criteria->addAssociation('deliveries');
$criteria->addAssociation('transactions');
/** @var OrderEntity $order */
$order = $this->orderRepository->search($criteria, $context)->first();
if (!$order) {
throw new \InvalidArgumentException('Order not found');
}
// Prepare document data
$documentData = [
'order' => $order,
'customField' => $this->prepareCustomData($order),
'timestamp' => time(),
'version' => '6.7'
];
// Generate document using Shopware's document system
$response = $this->documentGenerator->generate(
$documentData,
$context
);
return $response->getDocument();
}
private function prepareCustomData(OrderEntity $order): array
{
return [
'totalAmount' => $order->getTotalPrice(),
'orderNumber' => $order->getOrderNumber(),
'customerName' => $order->getOrderCustomer()->getFirstName() . ' ' . $order->getOrderCustomer()->getLastName(),
'generatedAt' => date('Y-m-d H:i:s'),
'customMetadata' => [
'shopwareVersion' => '6.7',
'documentType' => 'custom-order-report'
]
];
}
}
Template System Integration
Shopware 6.7 enhances the template system with improved Twig integration and better template inheritance capabilities. Custom documents can leverage these improvements to create more sophisticated layouts.
// Custom document template example
class CustomDocumentTemplate
{
public function renderTemplate(array $data): string
{
$twig = new \Twig\Environment($loader, [
'cache' => '/var/cache/shopware/templates',
'auto_reload' => true,
'strict_variables' => true
]);
// Load custom template
$template = $twig->load('custom-order-document.twig');
return $template->render([
'orderData' => $data['order'],
'customFields' => $data['customField'],
'metaData' => [
'version' => '6.7',
'generated' => date('Y-m-d H:i:s')
]
]);
}
}
Advanced Document Generation with Custom Logic
For more complex scenarios, we can implement custom logic that handles specific business requirements:
<?php declare(strict_types=1);
namespace MyPlugin\Document;
use Shopware\Core\Checkout\Order\OrderEntity;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
class AdvancedOrderDocumentGenerator
{
private DocumentGeneratorInterface $documentGenerator;
private OrderRepository $orderRepository;
private CustomFieldService $customFieldService;
public function __construct(
DocumentGeneratorInterface $documentGenerator,
OrderRepository $orderRepository,
CustomFieldService $customFieldService
) {
$this->documentGenerator = $documentGenerator;
$this->orderRepository = $orderRepository;
$this->customFieldService = $customFieldService;
}
public function generateAdvancedDocument(string $orderId, Context $context): DocumentResponse
{
// Load order with all required associations
$criteria = new Criteria([$orderId]);
$criteria->addAssociation('lineItems');
$criteria->addAssociation('deliveries');
$criteria->addAssociation('transactions');
$criteria->addAssociation('customer');
$criteria->addAssociation('addresses');
/** @var OrderEntity $order */
$order = $this->orderRepository->search($criteria, $context)->first();
if (!$order) {
throw new \InvalidArgumentException('Order not found');
}
// Apply business logic based on order properties
$documentType = $this->determineDocumentType($order);
$customData = $this->prepareAdvancedData($order, $documentType);
// Create document context with enhanced information
$documentContext = new DocumentContext(
$context,
$order->getSalesChannelId(),
$order->getCurrencyId()
);
$documentData = [
'order' => $order,
'context' => $documentContext,
'customData' => $customData,
'meta' => [
'type' => $documentType,
'version' => '6.7',
'timestamp' => time()
]
];
return $this->documentGenerator->generate($documentData, $context);
}
private function determineDocumentType(OrderEntity $order): string
{
$orderTotal = $order->getTotalPrice();
if ($orderTotal > 1000) {
return 'premium-invoice';
} elseif ($orderTotal > 500) {
return 'standard-invoice';
}
return 'basic-invoice';
}
private function prepareAdvancedData(OrderEntity $order, string $type): array
{
$data = [
'orderSummary' => [
'totalAmount' => $order->getTotalPrice(),
'currency' => $order->getCurrencyShortName(),
'itemsCount' => $order->getLineItems()->count(),
'deliveryMethod' => $order->getDeliveries()?->first()?->getMethod() ?? 'standard'
],
'customerInfo' => [
'name' => $order->getOrderCustomer()->getFirstName() . ' ' . $order->getOrderCustomer()->getLastName(),
'email' => $order->getOrderCustomer()->getEmail(),
'company' => $order->getOrderCustomer()->getCompany()
],
'documentType' => $type,
'specialFeatures' => $this->extractSpecialFeatures($order)
];
return array_merge($data, $this->customFieldService->getCustomFields($order));
}
private function extractSpecialFeatures(OrderEntity $order): array
{
$features = [];
// Check for special products
foreach ($order->getLineItems() as $lineItem) {
if ($lineItem->getType() === 'product' && $lineItem->getPayload()['isGift'] ?? false) {
$features['giftItems'][] = $lineItem->getLabel();
}
if ($lineItem->getType() === 'promotion') {
$features['discounts'][] = $lineItem->getLabel();
}
}
return $features;
}
}
Service Registration and Configuration
To make our custom document generator available within the Shopware system, we need to register it properly:
# config/services.yaml
services:
MyPlugin\Document\CustomOrderDocumentGenerator:
public: true
arguments:
$documentGenerator: '@Shopware\Core\Checkout\Order\SalesChannel\OrderService'
$orderRepository: '@Shopware\Core\Checkout\Order\OrderRepository'
MyPlugin\Document\AdvancedOrderDocumentGenerator:
public: true
arguments:
$documentGenerator: '@Shopware\Core\Checkout\Order\SalesChannel\OrderService'
$orderRepository: '@Shopware\Core\Checkout\Order\OrderRepository'
$customFieldService: '@Shopware\Core\Framework\DataAbstractionLayer\FieldSerializer\CustomFieldSerializer'
Event-Based Document Generation
Shopware 6.7 also supports event-driven document generation, allowing for more flexible integration points:
<?php declare(strict_types=1);
namespace MyPlugin\Event;
use Shopware\Core\Checkout\Order\OrderEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\EventDispatcher\Event;
class OrderDocumentEventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
OrderEvents::ORDER_STATE_CHANGED => 'onOrderStateChanged',
OrderEvents::ORDER_COMPLETED => 'onOrderCompleted'
];
}
public function onOrderStateChanged(Event $event): void
{
// Custom logic when order state changes
$order = $event->getOrder();
if ($order->getStateMachineState()->getTechnicalName() === 'completed') {
$this->generateCustomDocument($order);
}
}
public function onOrderCompleted(Event $event): void
{
// Generate document automatically when order is completed
$order = $event->getOrder();
$this->generateCustomDocument($order);
}
private function generateCustomDocument(OrderEntity $order): void
{
// Implementation of document generation logic
$generator = new CustomOrderDocumentGenerator(
$this->documentGenerator,
$this->orderRepository
);
try {
$generator->generateCustomDocument($order->getId(), Context::createDefaultContext());
} catch (\Exception $e) {
// Handle error appropriately
throw new \RuntimeException('Failed to generate custom document: ' . $e->getMessage());
}
}
}
Performance Considerations
When implementing custom document generation, several performance considerations should be taken into account:
- Caching Strategy: Implement proper caching for frequently accessed templates and data
- Batch Processing: For bulk operations, consider batch processing to reduce memory consumption
- Asynchronous Processing: Use Symfony's messenger component for heavy document generation tasks
- Database Optimization: Ensure proper indexing on order-related tables
// Example of asynchronous document generation
class AsyncDocumentGenerator
{
public function generateAsync(string $orderId, Context $context): void
{
$message = new DocumentGenerationMessage($orderId);
$this->messageBus->dispatch($message);
}
}
Testing Custom Document Generation
Proper testing ensures that custom document generation works as expected:
<?php declare(strict_types=1);
namespace MyPlugin\Test\Document;
use PHPUnit\Framework\TestCase;
use Shopware\Core\Checkout\Order\OrderEntity;
use Shopware\Core\Framework\Context;
class CustomOrderDocumentGeneratorTest extends TestCase
{
public function testGenerateCustomDocument(): void
{
$context = Context::createDefaultContext();
$orderId = 'test-order-id';
// Mock dependencies
$orderRepository = $this->createMock(OrderRepository::class);
$documentGenerator = $this->createMock(DocumentGeneratorInterface::class);
// Setup mock expectations
$orderEntity = new OrderEntity();
$orderEntity->setId($orderId);
$orderRepository->expects($this->once())
->method('search')
->willReturn(new SearchResult([0 => $orderEntity]));
$documentGenerator->expects($this->once())
->method('generate')
->willReturn(new DocumentResponse());
// Test the generator
$generator = new CustomOrderDocumentGenerator(
$documentGenerator,
$orderRepository
);
$result = $generator->generateCustomDocument($orderId, $context);
$this->assertIsString($result);
}
}
Conclusion
Shopware 6.7's enhanced document generation system provides powerful capabilities for creating custom order documents with minimal overhead. By leveraging the existing architecture and following best practices, developers can implement sophisticated document generation workflows that meet specific business requirements while maintaining performance and scalability.
The key to successful implementation lies in understanding the core components, properly registering services, handling events appropriately, and considering performance implications. With these foundations in place, custom order document generation becomes a seamless part of any Shopware 6.7 solution.