Dependency Injection (DI) is the architectural backbone of Shopware 6, and with version 6.7, its implementation has become more robust, predictable, and developer-friendly than ever before. If you’ve ever struggled to understand why your plugin’s listener doesn’t fire, or why a service returns null during runtime, the answer almost always traces back to how dependencies are registered, resolved, and bound within Shopware’s container. This guide breaks down exactly how DI works in Shopware 6.7+, moving beyond surface-level definitions to explain the mechanics, modern patterns, and practical strategies you need to build extensions that scale gracefully.
The Symfony Foundation Beneath the Surface
Shopware 6 does not reinvent the wheel when it comes to dependency injection. Instead, it leverages Symfony’s DependencyInjection component as its core container engine. This means Shopware inherits Symfony’s rigorous service resolution pipeline: definition parsing, argument compilation, alias resolution, tag filtering, and container optimization during cache warmup. The result is a container that is both highly performant and deeply compatible with PHP ecosystem standards.
In Shopware 6.7, the container operates in two distinct phases: definition time (when services.xml or auto-wiring configurations are read) and runtime (when the compiled cache file serves pre-resolved service graphs). Understanding this separation is critical. Misconfigurations often manifest at runtime because the container silently falls back to defaults or throws strict type errors during compilation.
Auto-Wiring vs Explicit Configuration
Shopware 6.7 heavily promotes auto-wiring as the default approach. When you register a service in your plugin’s services.xml without specifying <argument> nodes, the container inspects the constructor signature and resolves matching services by type hinting. This works beautifully for most business logic classes:
<service id="my_plugin.service.order_handler"
class="MyPlugin\Service\OrderHandler">
<autoconfigure />
</service>
Because of <autoconfigure />, Shopware 6.7 automatically applies framework-specific defaults, enables tagging inheritance where applicable, and binds constructor parameters to their corresponding service IDs or aliases. You no longer need to manually wire every dependency unless you want to override default behavior.
Explicit configuration remains necessary when:
- Binding scalar values (strings, arrays, environment variables)
- Using named arguments or interface implementations
- Registering tagged services for framework discovery
- Controlling instantiation strategy (lazy, shared, prototype)
Shopware 6.7 simplifies this with modern XML syntax and strict type enforcement. The container will now throw explicit compilation errors if a required dependency cannot be resolved, rather than failing silently at runtime.
Tags: How Shopware Discovers Your Services
Auto-wiring handles object instantiation, but tags tell the framework what your service does. Shopware’s internal systems (controllers, event dispatchers, entity subscribers, CLI commands) rely on tag filters to locate and integrate custom services. Common tags in 6.7+ include:
kernel.event_listener– Subscribes to Symfony eventsdoctrine.event_subscriber– Hooks into EntityManager lifecycleshopware.controller.resolver– Customizes controller argumentsmonolog.logger.channel– Registers dedicated log channels
In 6.7, you can define tags directly in services.xml without boilerplate. The container validates tag attributes during compilation and applies them to the appropriate system registrars. For example:
<service id="my_plugin.subscriber.cart_update"
class="MyPlugin\Subscriber\CartUpdateSubscriber">
<tag name="kernel.event_listener" event="checkout_cart_before_calculate_totals" priority="-10" />
<argument type="service" id="monolog.logger.my_plugin" />
</service>
Note the priority attribute and explicit <argument> binding. Shopware 6.7 enforces stricter ordering and requires explicit scalar bindings. Missing a required argument during compilation will now halt cache warming with a clear trace, saving hours of debugging.
Constructor Injection & Modern PHP Practices
Shopware 6.7 assumes PHP 8.1+ as the baseline. This means you can leverage readonly properties, named arguments, and strict types to write cleaner, more maintainable services:
namespace MyPlugin\Service;
use Psr\Log\LoggerInterface;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Doctrine\ORM\Event\PostPersistEventArgs;
final class PostOrderNotifier implements EventSubscriberInterface
{
public function __construct(
private readonly EntityRepository $orderRepository,
private readonly LoggerInterface $logger,
private readonly string $notificationEndpoint
) {}
public static function getSubscribedEvents(): array
{
return ['entity.postPersist' => 'onOrderCreated'];
}
public function onOrderCreated(PostPersistEventArgs $args): void
{
$entity = $args->getObject();
if ($entity instanceof \Shopware\Core\Checkout\Order\OrderEntity) {
$this->logger->info('New order detected', ['orderId' => $entity->getId()]);
// Async notification logic here
}
}
}
By using readonly properties and type-hinted constructors, you guarantee immutability and eliminate the need for setter injection. The container injects dependencies at instantiation, ensuring they are never null. This pattern aligns with PSR-14 event subscriber standards and Shopware’s own architectural guidelines.
Service Discovery & Plugin Registration Flow
In Shopware 6.7, plugins and extensions no longer require manual service registration in the core kernel. When you create an extension via shopware scaffold, the framework automatically scans src/ for classes implementing Shopware\Core\Framework\Plugin or extending AbstractExtension. During boot(), your extension registers routes, middleware, and services into the container.
The DI component only loads definitions from bundles explicitly marked as active. This means:
- Unused extensions don’t pollute the service graph
- Compilation times remain predictable
- Multi-store setups can share base services while overriding per-shop configurations
Shopware 6.7 also introduces better isolation between shop instances. Service aliases and parameter overrides can now be scoped to specific stores or contexts without breaking global resolution.
Advanced Resolution: Compiler Passes & Lazy Services
When auto-wiring and tags aren’t enough, Shopware 6.7 provides access to Symfony’s compiler pass system. These runs during container compilation and allow you to modify service definitions programmatically. Common use cases in 6.7+ include:
- Replacing core services with custom implementations conditionally
- Injecting configuration derived at compile time
- Optimizing service graphs for production caches
Lazy services remain fully supported and are highly recommended for heavy or rarely used dependencies. Declare a service as lazy using <tag name="container.lazy" /> (or via modern attribute syntax where applicable), and the container will inject a proxy that instantiates the actual class only when first accessed. This dramatically reduces boot time and memory consumption during cache warmup and CLI operations.
Testing & Maintenance Best Practices
A well-wired DI system is inherently testable. Because dependencies are injected rather than instantiated, you can pass mocks directly to constructors without touching Container::get(). Avoid static service lookups in business logic; they break encapsulation and make upgrade paths brittle. Instead, pass interfaces or abstract factories when dynamic resolution is unavoidable.
When refactoring existing plugins for 6.7+:
- Replace
->get()calls with constructor injection - Remove manual tag registration if auto-configuration covers it
- Validate service IDs match exact class names or explicit aliases
- Use
--env=prodduring cache warming to catch compilation errors early
Shopware 6.7’s container diagnostics (bin/console debug:container) now show dependency trees, resolved arguments, and tag mappings in real-time. Leverage this tooling to audit your extensions before deployment.
Conclusion
Dependency Injection in Shopware 6.7 is no longer a hurdle—it’s a powerful contract between your code and the commerce platform. By embracing auto-wiring, explicit tagging, constructor injection, and modern PHP semantics, you build extensions that are faster, safer, and inherently upgrade-compatible. The container manages complexity so you can focus on business logic. Respect its boundaries, leverage its automation, and Shopware 6.7 will reward you with a stable, predictable foundation for years to come.