Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Hexagonal Maker Bundle Laravel Package

ahmed-bhs/hexagonal-maker-bundle

View on GitHub
Deep Wiki
Context7

layout: default

Guide de Configuration de l'Injection de Dépendances

Table des Matières

  1. Vue d'Ensemble
  2. Bases Autowiring Symfony
  3. Liaison Ports vers Adaptateurs
  4. Liaisons Spécifiques Environnement
  5. Services Taggués
  6. Décoration de Services
  7. Exemples Configuration Complète
  8. Dépannage

Vue d'Ensemble

En architecture hexagonale, l'Injection de Dépendances (DI) est critique pour lier ports (interfaces) à adaptateurs (implémentations). Le conteneur DI de Symfony gère ce câblage automatiquement avec configuration appropriée.

Le Problème que DI Résout

// ❌ Sans DI : Dépendance codée en dur
class RegisterUserHandler
{
    public function __invoke(RegisterUserCommand $command): void
    {
        $repository = new DoctrineUserRepository(); // ❌ Couplage fort!
        // ...
    }
}

// ✅ Avec DI : Dépendance injectée
class RegisterUserHandler
{
    public function __construct(
        private UserRepositoryInterface $users // ✅ Interface, pas implémentation
    ) {}

    public function __invoke(RegisterUserCommand $command): void
    {
        $this->users->save(...); // Utilise implémentation configurée
    }
}

**Configuration Conteneur DI :** Dit à Symfony "quand quelqu'un a besoin de `UserRepositoryInterface`, donne-lui `DoctrineUserRepository`".

---

## Bases Autowiring Symfony

### Qu'est-ce que l'Autowiring ?

```php
// Handler avec dépendances type-hintées
class RegisterUserHandler
{
    public function __construct(
        private UserRepositoryInterface $users,      // Autowired
        private EmailSenderInterface $emailSender,   // Autowired
        private EventDispatcherInterface $events,    // Autowired
    ) {}
}

Symfony voit ces type hints et fournit automatiquement services corrects.

---

### Activer Autowiring

```yaml
services:
    _defaults:
        autowire: true      # Activer autowiring
        autoconfigure: true # Configurer automatiquement services (tags, etc.)
        public: false       # Services privés par défaut

    # Auto-enregistrer toutes classes dans src/ comme services
    App\:
        resource: '../src/'
        exclude:
            - '../src/DependencyInjection/'
            - '../src/*/Domain/Model/'      # Exclure entités
            - '../src/*/Domain/ValueObject/' # Exclure value objects
            - '../src/Kernel.php'


**Comment ça marche :**
1. Symfony scanne répertoire `src/`

3. Autowire dépendances constructeur

---

## Liaison Ports vers Adaptateurs

### Configuration de Base

```php
// Handler nécessite UserRepositoryInterface
class RegisterUserHandler
{
    public function __construct(
        private UserRepositoryInterface $users // ❌ Interface, ne peut être instanciée!
    ) {}
}

**Solution :** Lier interface à implémentation dans `services.yaml`.

---

```yaml
services:
    # Lier interface → implémentation
    App\User\Domain\Port\UserRepositoryInterface:
        class: App\User\Infrastructure\Persistence\DoctrineUserRepository


**Explication :**

- Symfony fournit `DoctrineUserRepository`

---

```yaml
services:
    # Implémentation
    App\User\Infrastructure\Persistence\DoctrineUserRepository:
        # Autowired par défaut

    # Alias : interface → implémentation
    App\User\Domain\Port\UserRepositoryInterface:
        alias: App\User\Infrastructure\Persistence\DoctrineUserRepository


**Bénéfices :**

- Plus facile changer implémentations

---

```yaml
services:
    _defaults:
        bind:
            # Lier automatiquement cette interface à cette implémentation
            # pour TOUS les services
            App\User\Domain\Port\UserRepositoryInterface: '[@App](https://github.com/App)\User\Infrastructure\Persistence\DoctrineUserRepository'

**Quand utiliser :** Interface utilisée dans beaucoup d'endroits, éviter répéter configuration.

---

## Liaisons Spécifiques Environnement

### Le Problème : Implémentations Différentes pour Environnements Différents

- **Développement :** Utiliser repository en mémoire (tests rapides)

- **Production :** Utiliser repository Doctrine (vraie base données)

---

### Solution : Configuration Spécifique Environnement

```yaml
services:
    _defaults:
        autowire: true
        autoconfigure: true

    # Enregistrer toutes implémentations
    App\User\Infrastructure\Persistence\DoctrineUserRepository:
    App\User\Infrastructure\Persistence\InMemoryUserRepository:

    # Liaison par défaut (production)
    App\User\Domain\Port\UserRepositoryInterface:
        alias: App\User\Infrastructure\Persistence\DoctrineUserRepository

---

```yaml
services:
    # Surcharger liaison pour environnement test
    App\User\Domain\Port\UserRepositoryInterface:
        alias: App\User\Infrastructure\Persistence\InMemoryUserRepository


**Résultat :**

- Production → utilise `DoctrineUserRepository`

---

```yaml
# Optionnel : utiliser en mémoire pour feedback dev rapide
services:
    App\User\Domain\Port\UserRepositoryInterface:
        alias: App\User\Infrastructure\Persistence\InMemoryUserRepository

    # Ou activer logging debug
    App\User\Infrastructure\Persistence\DoctrineUserRepository:
        decorates: App\User\Infrastructure\Persistence\DoctrineUserRepository
        arguments:
            $decorated: '@.inner'
            $logger: '[@logger](https://github.com/logger)'

---

```yaml
# config/services.yaml (défaut : production)
services:
    # Production : vrai SMTP
    App\Notification\Domain\Port\EmailSenderInterface:
        alias: App\Notification\Infrastructure\Email\SymfonyEmailSender

# config/services_test.yaml
services:
    # Test : fake en mémoire
    App\Notification\Domain\Port\EmailSenderInterface:
        alias: App\Notification\Infrastructure\Email\InMemoryEmailSender

# config/services_dev.yaml
services:
    # Dev : logger emails au lieu d'envoyer
    App\Notification\Domain\Port\EmailSenderInterface:
        alias: App\Notification\Infrastructure\Email\LoggingEmailSender

---

## Services Taggués

### Le Problème : Multiples Implémentations Même Interface

```php
interface EventSubscriberInterface
{
    public function handle(DomainEvent $event): void;
}

class SendEmailSubscriber implements EventSubscriberInterface { /* ... */ }
class LogEventSubscriber implements EventSubscriberInterface { /* ... */ }
class UpdateCacheSubscriber implements EventSubscriberInterface { /* ... */ }

**Besoin :** Injecter toutes implémentations, pas juste une.

---

### Solution : Services Taggués

```yaml
services:
    # Taguer chaque implémentation
    App\Notification\Infrastructure\Event\SendEmailSubscriber:
        tags: ['app.event_subscriber']

    App\Notification\Infrastructure\Event\LogEventSubscriber:
        tags: ['app.event_subscriber']

    App\Notification\Infrastructure\Event\UpdateCacheSubscriber:
        tags: ['app.event_subscriber']

---

```yaml
services:
    # Event dispatcher reçoit tous subscribers
    App\Shared\Infrastructure\Event\EventDispatcher:
        arguments:
            $subscribers: !tagged_iterator app.event_subscriber

---

```php
use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;

class EventDispatcher implements EventDispatcherInterface
{
    public function __construct(
        #[TaggedIterator('app.event_subscriber')]
        private iterable $subscribers // Tous services taggués injectés ici
    ) {}

    public function dispatch(DomainEvent $event): void
    {
        foreach ($this->subscribers as $subscriber) {
            $subscriber->handle($event);
        }
    }
}

---

### Auto-Tagging avec Interfaces

```yaml
services:
    _instanceof:
        # Taguer automatiquement toutes classes implémentant EventSubscriberInterface
        App\Shared\Domain\Event\EventSubscriberInterface:
            tags: ['app.event_subscriber']

**Maintenant vous n'avez pas besoin de taguer manuellement chaque implémentation !**

---

```yaml
services:
    _instanceof:
        # Auto-taguer tous query handlers
        App\Shared\Application\Query\QueryHandlerInterface:
            tags: ['app.query_handler']

    # Query bus reçoit tous handlers
    App\Shared\Infrastructure\Query\QueryBus:
        arguments:
            $handlers: !tagged_iterator app.query_handler
class QueryBus
{
    public function __construct(
        #[TaggedIterator('app.query_handler')]
        private iterable $handlers
    ) {}

    public function dispatch(Query $query): mixed
    {
        foreach ($this->handlers as $handler) {
            if ($handler->supports($query)) {
                return $handler->handle($query);
            }
        }

        throw new NoHandlerFoundException();
    }
}

---

## Décoration de Services

### Le Problème : Ajouter Fonctionnalité Sans Modifier Code

**Exemple :** Ajouter cache au repository sans changer code repository.

---

### Solution : Pattern Decorator avec DI

```php
namespace App\User\Infrastructure\Persistence;

final readonly class CachedUserRepository implements UserRepositoryInterface
{
    public function __construct(
        private UserRepositoryInterface $decorated, // Repository original
        private CacheInterface $cache,
    ) {}

    public function findById(UserId $id): ?User
    {
        return $this->cache->get(
            "user:{$id}",
            fn() => $this->decorated->findById($id) // Déléguer à l'original
        );
    }

    public function save(User $user): void
    {
        $this->decorated->save($user);
        $this->cache->delete("user:{$user->getId()}"); // Invalider cache
    }
}

---

```yaml
services:
    # Repository original
    App\User\Infrastructure\Persistence\DoctrineUserRepository:

    # Décorateur enveloppe l'original
    App\User\Infrastructure\Persistence\CachedUserRepository:
        decorates: App\User\Infrastructure\Persistence\DoctrineUserRepository
        arguments:
            $decorated: '@.inner' # @.inner = le service décoré


**Résultat :**
- Quiconque demande `DoctrineUserRepository` obtient `CachedUserRepository`

- Cache ajouté de façon transparente

---

### Priorité Décoration

```yaml
services:
    App\User\Infrastructure\Persistence\DoctrineUserRepository:

    # Premier décorateur : cache
    App\User\Infrastructure\Persistence\CachedUserRepository:
        decorates: App\User\Infrastructure\Persistence\DoctrineUserRepository
        decoration_priority: 10 # Priorité plus élevée = couche externe
        arguments:
            $decorated: '@.inner'

    # Second décorateur : logging
    App\User\Infrastructure\Persistence\LoggingUserRepository:
        decorates: App\User\Infrastructure\Persistence\DoctrineUserRepository
        decoration_priority: 5 # Priorité plus basse = couche interne
        arguments:
            $decorated: '@.inner'

Handler → LoggingUserRepository → CachedUserRepository → DoctrineUserRepository → Base de Données



Exemples Configuration Complète

# config/services.yaml
services:
    _defaults:
        autowire: true
        autoconfigure: true

    # Auto-enregistrer tous services
    App\:
        resource: '../src/'
        exclude:
            - '../src/*/Domain/Model/'
            - '../src/*/Domain/ValueObject/'
            - '../src/Kernel.php'

    # Lier ports à adaptateurs
    App\User\Domain\Port\UserRepositoryInterface:
        alias: App\User\Infrastructure\Persistence\DoctrineUserRepository

    App\User\Domain\Port\PasswordHasherInterface:
        alias: App\User\Infrastructure\Security\SymfonyPasswordHasher

    App\Shared\Domain\Port\EmailSenderInterface:
        alias: App\Shared\Infrastructure\Email\SymfonyEmailSender

    App\Shared\Domain\Port\EventDispatcherInterface:
        alias: App\Shared\Infrastructure\Event\SymfonyEventDispatcher

---

```yaml
services:
    # Côté écriture
    App\Order\Domain\Port\OrderRepositoryInterface:
        alias: App\Order\Infrastructure\Persistence\DoctrineOrderRepository

    # Côté lecture (CQRS)
    App\Order\Application\Query\OrderQueryInterface:
        alias: App\Order\Infrastructure\Query\SqlOrderQuery

    # Services externes
    App\Order\Domain\Port\PaymentProcessorInterface:
        alias: App\Order\Infrastructure\Payment\StripePaymentProcessor

    App\Order\Domain\Port\InventoryServiceInterface:
        alias: App\Order\Infrastructure\Inventory\HttpInventoryService

---

```yaml
services:
    # Résolveur tenant
    App\Shared\Infrastructure\Tenancy\TenantResolver:

    # Repository tenant-aware
    App\User\Infrastructure\Persistence\TenantAwareUserRepository:
        arguments:
            $tenantResolver: '[@App](https://github.com/App)\Shared\Infrastructure\Tenancy\TenantResolver'

    # Lier interface à implémentation tenant-aware
    App\User\Domain\Port\UserRepositoryInterface:
        alias: App\User\Infrastructure\Persistence\TenantAwareUserRepository

---

```yaml
services:
    # Repository de base
    App\Product\Infrastructure\Persistence\DoctrineProductRepository:

    # Décorateur : cache
    App\Product\Infrastructure\Persistence\CachedProductRepository:
        decorates: App\Product\Infrastructure\Persistence\DoctrineProductRepository
        decoration_priority: 10
        arguments:
            $decorated: '@.inner'
            $cache: '[@cache](https://github.com/cache).app'

    # Décorateur : logging
    App\Product\Infrastructure\Persistence\LoggingProductRepository:
        decorates: App\Product\Infrastructure\Persistence\DoctrineProductRepository
        decoration_priority: 5
        arguments:
            $decorated: '@.inner'
            $logger: '[@logger](https://github.com/logger)'

    # Lier interface à la base (décorateurs l'enveloppent automatiquement)
    App\Product\Domain\Port\ProductRepositoryInterface:
        alias: App\Product\Infrastructure\Persistence\DoctrineProductRepository

**Chaîne d'appel :** `Handler → Logging → Caching → Doctrine → BD`

---

## Dépannage

### Problème 1 : "Cannot autowire service: argument is type-hinted with interface"

Cannot autowire service "App\User\Application\Handler\RegisterUserHandler": argument "$users" of method "__construct()" is type-hinted with the interface "App\User\Domain\Port\UserRepositoryInterface" but no implementation is registered.


services:
    App\User\Domain\Port\UserRepositoryInterface:
        alias: App\User\Infrastructure\Persistence\DoctrineUserRepository

---

### Problème 2 : "Service not found"

Service "App\User\Infrastructure\Persistence\DoctrineUserRepository" not found.


services:
    App\:
        resource: '../src/'
        exclude:
            - '../src/*/Domain/Model/'  # ✅ Exclure entités
            # ❌ Ne pas exclure Infrastructure!
        # ❌ Ne pas exclure Infrastructure!
Circular reference detected for service "App\User\Infrastructure\Persistence\DoctrineUserRepository".

Circular reference detected for service "App\User\Infrastructure\Persistence\DoctrineUserRepository".

services:
    App\User\Infrastructure\Persistence\DoctrineUserRepository:
        calls:
            - setLogger: ['[@logger](https://github.com/logger)'] # Injection setter au lieu constructeur
        - setLogger: ['[@logger](https://github.com/logger)'] # Injection setter au lieu constructeur
# config/services_test.yaml
services:
    App\User\Domain\Port\UserRepositoryInterface:
        alias: App\User\Infrastructure\Persistence\InMemoryUserRepository
    alias: App\User\Infrastructure\Persistence\InMemoryUserRepository
services:
    # Définition tag
    _instanceof:
        App\Shared\Domain\Event\EventSubscriberInterface:
            tags: ['app.event_subscriber'] # Nom tag

    # Injection (doit correspondre!)
    App\Shared\Infrastructure\Event\EventDispatcher:
        arguments:
            $subscribers: !tagged_iterator app.event_subscriber # Même nom tag
        $subscribers: !tagged_iterator app.event_subscriber # Même nom tag
App\User\Domain\Port\UserRepositoryInterface:
    alias: App\User\Infrastructure\Persistence\DoctrineUserRepository

App\User\Domain\Port\UserRepositoryInterface:

App\User\Domain\Port\UserRepositoryInterface:
    class: App\User\Infrastructure\Persistence\DoctrineUserRepository

App\User\Domain\Port\UserRepositoryInterface:


**Raison :** Alias plus clairs et plus faciles à surcharger.

config/ ├── services.yaml # Défaut (production) ├── services_dev.yaml # Surcharges développement ├── services_test.yaml # Surcharges test └── services_prod.yaml # Spécifique production (optionnel)


├── services_test.yaml     # Surcharges test


_instanceof:
    App\Shared\Application\Query\QueryHandlerInterface:
        tags: ['app.query_handler']

_instanceof:

App\User\Application\Query\FindUserQueryHandler:
    tags: ['app.query_handler']

App\Order\Application\Query\FindOrderQueryHandler:
    tags: ['app.query_handler']

# ... taguer manuellement chacun
tags: ['app.query_handler']

... taguer manuellement chacun

App\:
    resource: '../src/'
    exclude:
        - '../src/*/Domain/Model/'       # Entités
        - '../src/*/Domain/ValueObject/' # Value objects
        - '../src/*/Application/Command/' # DTOs
        - '../src/*/Application/Query/'   # DTOs
        - '../src/Kernel.php'
    - '../src/*/Application/Command/' # DTOs

    - '../src/Kernel.php'

Résumé Antisèche

Tâche Configuration
Lier port à adaptateur alias: App\...\Implementation

| Auto-taguer par interface | _instanceof: { Interface: tags: [...] } |

| Décorer service | decorates: OriginalService + $decorated: '@.inner' |

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
terminal42/code-quality-tools
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky