Claude Code Plugins

Community-maintained marketplace

Feedback

symfony:interfaces-and-autowiring

@MakFly/superpowers-symfony
14
0

Master Symfony's Dependency Injection with autowiring, interface binding, service decoration, and tagged services for flexible architecture

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name symfony:interfaces-and-autowiring
description Master Symfony's Dependency Injection with autowiring, interface binding, service decoration, and tagged services for flexible architecture

Interfaces and Autowiring in Symfony

Basic Autowiring

Symfony automatically injects dependencies based on type-hints:

<?php
// src/Service/OrderService.php

namespace App\Service;

use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;

class OrderService
{
    public function __construct(
        private EntityManagerInterface $em,
        private LoggerInterface $logger,
    ) {}

    public function createOrder(array $data): Order
    {
        $this->logger->info('Creating order', $data);
        // ...
    }
}

No configuration needed - Symfony wires it automatically.

Interface Binding

Define Interface

<?php
// src/Service/PaymentGatewayInterface.php

namespace App\Service;

interface PaymentGatewayInterface
{
    public function charge(int $amount, string $currency): PaymentResult;
    public function refund(string $transactionId, int $amount): RefundResult;
}

Implementation

<?php
// src/Service/StripePaymentGateway.php

namespace App\Service;

class StripePaymentGateway implements PaymentGatewayInterface
{
    public function __construct(
        private string $apiKey,
    ) {}

    public function charge(int $amount, string $currency): PaymentResult
    {
        // Stripe implementation
    }

    public function refund(string $transactionId, int $amount): RefundResult
    {
        // Stripe implementation
    }
}

Bind Interface to Implementation

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

    App\:
        resource: '../src/'
        exclude:
            - '../src/DependencyInjection/'
            - '../src/Entity/'
            - '../src/Kernel.php'

    # Bind interface to implementation
    App\Service\PaymentGatewayInterface: '@App\Service\StripePaymentGateway'

    # Or with parameters
    App\Service\StripePaymentGateway:
        arguments:
            $apiKey: '%env(STRIPE_API_KEY)%'

Use in Services

class OrderService
{
    public function __construct(
        private PaymentGatewayInterface $paymentGateway, // Autowired!
    ) {}
}

Service Decoration

Wrap a service to add behavior without modifying it:

<?php
// src/Service/LoggingPaymentGateway.php

namespace App\Service;

use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;

#[AsDecorator(decorates: StripePaymentGateway::class)]
class LoggingPaymentGateway implements PaymentGatewayInterface
{
    public function __construct(
        #[AutowireDecorated]
        private PaymentGatewayInterface $inner,
        private LoggerInterface $logger,
    ) {}

    public function charge(int $amount, string $currency): PaymentResult
    {
        $this->logger->info('Charging payment', [
            'amount' => $amount,
            'currency' => $currency,
        ]);

        $result = $this->inner->charge($amount, $currency);

        $this->logger->info('Payment result', [
            'success' => $result->isSuccessful(),
            'transactionId' => $result->getTransactionId(),
        ]);

        return $result;
    }

    public function refund(string $transactionId, int $amount): RefundResult
    {
        $this->logger->info('Processing refund', [
            'transactionId' => $transactionId,
            'amount' => $amount,
        ]);

        return $this->inner->refund($transactionId, $amount);
    }
}

Tagged Services

Define Tag

<?php
// src/Export/ExporterInterface.php

namespace App\Export;

use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;

#[AutoconfigureTag('app.exporter')]
interface ExporterInterface
{
    public function supports(string $format): bool;
    public function export(array $data): string;
}

Implementations

<?php
// src/Export/CsvExporter.php

namespace App\Export;

class CsvExporter implements ExporterInterface
{
    public function supports(string $format): bool
    {
        return $format === 'csv';
    }

    public function export(array $data): string
    {
        // CSV export logic
    }
}

// src/Export/JsonExporter.php

class JsonExporter implements ExporterInterface
{
    public function supports(string $format): bool
    {
        return $format === 'json';
    }

    public function export(array $data): string
    {
        return json_encode($data, JSON_PRETTY_PRINT);
    }
}

Inject All Tagged Services

<?php
// src/Service/ExportService.php

namespace App\Service;

use App\Export\ExporterInterface;
use Symfony\Component\DependencyInjection\Attribute\AutowireIterator;

class ExportService
{
    /**
     * @param iterable<ExporterInterface> $exporters
     */
    public function __construct(
        #[AutowireIterator('app.exporter')]
        private iterable $exporters,
    ) {}

    public function export(array $data, string $format): string
    {
        foreach ($this->exporters as $exporter) {
            if ($exporter->supports($format)) {
                return $exporter->export($data);
            }
        }

        throw new \InvalidArgumentException("Unsupported format: {$format}");
    }

    public function getSupportedFormats(): array
    {
        $formats = [];
        foreach ($this->exporters as $exporter) {
            // Each exporter reports what it supports
        }
        return $formats;
    }
}

Named Autowiring

When you have multiple implementations:

# config/services.yaml
services:
    App\Service\StripePaymentGateway:
        arguments:
            $apiKey: '%env(STRIPE_API_KEY)%'

    App\Service\PaypalPaymentGateway:
        arguments:
            $clientId: '%env(PAYPAL_CLIENT_ID)%'

    # Named bindings
    App\Service\PaymentGatewayInterface $stripeGateway: '@App\Service\StripePaymentGateway'
    App\Service\PaymentGatewayInterface $paypalGateway: '@App\Service\PaypalPaymentGateway'
class PaymentService
{
    public function __construct(
        private PaymentGatewayInterface $stripeGateway, // Stripe
        private PaymentGatewayInterface $paypalGateway, // PayPal
    ) {}
}

Lazy Services

Load service only when actually used:

use Symfony\Component\DependencyInjection\Attribute\Lazy;

#[Lazy]
class ExpensiveService
{
    public function __construct()
    {
        // Heavy initialization
    }
}

Debug Commands

# List all services
bin/console debug:container

# Find specific service
bin/console debug:container OrderService

# Show autowiring candidates
bin/console debug:autowiring

# Show autowiring for specific type
bin/console debug:autowiring Payment

Best Practices

  1. Program to interfaces: Depend on interfaces, not implementations
  2. Constructor injection: Always use constructor injection
  3. Final classes: Make services final by default
  4. Readonly properties: Use private readonly for dependencies
  5. Minimal interfaces: Keep interfaces focused (ISP)
  6. Decorate, don't modify: Use decoration for cross-cutting concerns