---
title: Introduction to the Basic Architectural Pattern | Shopware Community Hub
description: Get an overview of the basic architectural pattern used in Shopware.
canonical_url: >-
  https://hub.shopware.comhttps://hub.shopware.com/learn/unit/introduction-to-the-basic-architectural-pattern
---

# Introduction to the Basic Architectural Pattern

<LearningObjectives>

- Understand the **core components** of Shopware that come from Symfony.
- Learn the roles of **services**, **controllers**, and **event subscribers** in Shopware.
- Understand how **Dependency Injection (DI)** wires services and keeps code modular and maintainable.

</LearningObjectives>

# Introduction to the Basic Architectural Pattern

In the previous unit, you created a minimal plugin. Now it is time to understand the basic structure behind plugin development.

Shopware is built on **Symfony** and follows its architectural patterns. In this learning unit, you will explore the key components that form the backbone of a typical Shopware plugin: Services, controllers, event subscribers, and dependency injection.

Keep in mind that this is not the complete picture. It is a simplified overview to help you get oriented before you build a storefront controller in the next learning unit.

## The Basic Mental Model

Think of a plugin as a place for your PHP classes.

- **Services** hold business logic.
- **Controllers** handle HTTP requests.
- **Subscribers** react to events.
- The **DI container** wires them together, so you declare *what* you need, not *how* to build it.

## Architecture of Shopware

Let's start with a short overview of the architectural pattern behind Shopware. The following components are essential building blocks:

- **Services** — reusable business logic
- **Controllers** — HTTP entry points
- **Subscribers** — hooks into Shopware/Symfony events
- **Dependency Injection (DI)** — connects the pieces

In Symfony/Shopware, services, controllers, and subscribers are registered in the **Dependency Injection Container** (via `services.xml` or autowiring) and injected where needed.

![Basic architectural overview](assets/images/backend_basic-architectural-overview.jpg)

## What are Services?

`Services` are plain PHP classes that contain your **business logic** in methods. They are reusable helpers that can be called from controllers, event subscribers, and other services.

Using services, you can centralize your business logic in one place. This makes your projects easier to maintain and avoids duplication.

## What are Controllers?

`Controllers` are one possible *entry-point* to trigger your business logic via an HTTP-Request. They usually handle incoming HTTP requests, delegate logic to services, and return a response (often HTML via Twig).

## What are Event Subscribers?

An `event subscriber` is a plain PHP class, where you can hook into predefined Shopware or Symfony events by implementing your own methods. Event subscribers are also entry points to add your business logic at the right point in the workflow without losing the core logic.
For example, if you want to add logic to the product detail page, you can listen to the `ProductPageLoadedEvent`.

## What is Dependency Injection?

In object-oriented PHP, classes often need other objects to do their work. For example, a controller may need a service, and a service may need a repository, logger, or HTTP client. These needed objects are called **dependencies**.

Dependency Injection means that a class does not create its dependencies by itself. Instead, the dependencies are passed into the class through the constructor.

The important difference is where the dependencies are created.

Without a DI container, every manual `new` call has to provide the required constructor arguments itself. This means the code that creates an object also needs to know two things:

- **What** the class needs.
- **How** these dependencies are created.

This can quickly become hard to maintain. The same setup code may appear in multiple places, and a small change in one dependency can require changes in many files. This leads to code that is harder to read, maintain, and test.

In Symfony/Shopware, this wiring is handled by the **Dependency Injection Container**. Your class declares what it needs, and the container handles how these dependencies are created and passed into the class.

### Without a DI Container

Without a DI Container, every place that creates `OrderService` manually also has to create and pass its dependencies manually.

```php
<?php declare(strict_types=1);

namespace TestPlugin\Controller;

use TestPlugin\Service\OrderService;

// Simplified Example
use TestPlugin\Service\Mailer;
use TestPlugin\Service\SmtpClient;
use TestPlugin\Service\Logger;

class OrderController
{
    public function sendOrderMail(): void
    {
        $orderService = new OrderService(
            new Mailer(new SmtpClient('host', 'user', 'pass')),
            new Logger('/var/logs/app.log')
        );
        
        $orderService->sendMail();
    }
}
?>
```

In this example, the `OrderController` only wants to use `OrderService`. But it also needs to know how to create `Mailer`, `SmtpClient`, and `Logger`.

If another class also needs `OrderService`, the same setup may be repeated there:

```php
<?php declare(strict_types=1);

namespace TestPlugin\Subscriber;

use TestPlugin\Service\OrderService;

// Simplified Example
use TestPlugin\Service\Mailer;
use TestPlugin\Service\SmtpClient;
use TestPlugin\Service\Logger;

class OrderSubscriber
{
    public function onOrderPlaced(): void
    {
        $orderService = new OrderService(
            new Mailer(new SmtpClient('host', 'user', 'pass')),
            new Logger('/var/logs/app.log')
        );
        
        $orderService->sendMail();
    }
}
?>
```

Like this it becomes hard to maintain. If the constructor of `OrderService` changes, every manual `new OrderService(...)` call may need to be updated.

### With a DI Container

With a DI Container, `OrderController` does not create `OrderService` manually. It only declares that it needs `OrderService`.

```php
<?php declare(strict_types=1);

namespace TestPlugin\Controller;

use TestPlugin\Service\OrderService;

class OrderController
{
    public function __construct(
        private readonly OrderService $orderService,
    ) { }

    public function sendOrderMail(): void
    {
        $this->orderService->sendMail();
    }
}
?>
```

The same applies to other classes that need `OrderService`:

```php
<?php declare(strict_types=1);

namespace TestPlugin\Subscriber;

use TestPlugin\Service\OrderService;


class OrderSubscriber
{
    public function __construct(
        private readonly OrderService $orderService,
    ) { }

    public function onOrderPlaced(): void
    {
        $this->orderService->sendMail();
    }
}
?>
```

Now `OrderController` and `OrderSubscriber` only declare **what** they need. They do not need to know how `OrderService` is created.

`OrderService` declares its own dependencies in its constructor.

```php
<?php declare(strict_types=1);

namespace TestPlugin\Service;

use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\MailerInterface;

class OrderService
{
    public function __construct(
        private readonly MailerInterface $mailer,
        private readonly LoggerInterface $logger,
    ) { }

    public function sendMail(): void
    {
        // Send the mail and write a log entry
    }
}
?>
```

In Shopware plugins, this wiring is defined in the `services.xml`. The constructor stays in PHP. The `services.xml` file tells the DI container which service should be passed into which constructor argument.

In our example, it will look like this:

```xml
<?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>

      <!-- Controllers -->
      <service id="TestPlugin\Controller\OrderController" public="true">
        <!-- The OrderService -->
        <argument type="service" id="TestPlugin\Service\OrderService"/>
      </service>
      
      <!-- Services -->
      <service id="TestPlugin\Service\OrderService">
        <!-- The Symfony mailer service -->
        <argument type="service" id="mailer"/>
        <!-- The default logger service -->
        <argument type="service" id="logger"/>
      </service>
      
      <!-- Subscribers --> 
      <service id="TestPlugin\Subscriber\OrderSubscriber">
        <!-- The OrderService -->
        <argument type="service" id="TestPlugin\Service\OrderService"/>
        
        <!-- This line is needed when creating subscribers -->
        <tag name="kernel.event_subscriber"/>
      </service>
    </services>
</container>
```

The important part for `OrderService` is the service wiring:

```xml
<service id="TestPlugin\Service\OrderService">
  <argument type="service" id="mailer"/>
  <argument type="service" id="logger"/>
</service>
```

The first argument wires Symfony's mailer service into `MailerInterface`. The second argument wires Symfony's default logger service into `LoggerInterface`.

For now, focus on understanding the concept. You will implement DI in practice in the next units. For now, remember: Controllers, services, and subscribers declare their dependencies in the constructor and the DI Container handles the wiring.

<ArticleQuestionnaire>
  <ArticleQuestionnaireQuestion>Which component is the best place for reusable business logic?</ArticleQuestionnaireQuestion>
  <ArticleQuestionnaireAnswer>Controller</ArticleQuestionnaireAnswer>
  <ArticleQuestionnaireAnswer correct>Service</ArticleQuestionnaireAnswer>
  <ArticleQuestionnaireAnswer>Twig template</ArticleQuestionnaireAnswer>
</ArticleQuestionnaire>

<ArticleQuestionnaire>
  <ArticleQuestionnaireQuestion>What problem does the Dependency Injection container solve?</ArticleQuestionnaireQuestion>
  <ArticleQuestionnaireAnswer>It defines route paths and HTTP methods for controller actions.</ArticleQuestionnaireAnswer>
  <ArticleQuestionnaireAnswer correct>It handles object wiring and injects dependencies into classes.</ArticleQuestionnaireAnswer>
  <ArticleQuestionnaireAnswer>It stores reusable business logic that controllers and subscribers can call.</ArticleQuestionnaireAnswer>
</ArticleQuestionnaire>

## Summary

In this learning unit, you have learned:

- The basic architectural pattern that Shopware follows.
- The purpose and role of core components such as **services**, **controllers**, and **event subscribers**.
- How the **Dependency Injection (DI) container** wires services and keeps your code modular and maintainable.

With this, you have a solid foundation to understand how Shopware's backend is structured and how its components work together behind the scenes.

**Next:** You will add a storefront controller to a plugin and see these pieces in action.
