Claude Code Plugins

Community-maintained marketplace

Feedback

symfony:interfaces-and-autowiring

@majiayu000/claude-skill-registry
14
0

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

Install Skill

Shared

Installs to .agents/skills, used by Codex, Amp, Warp, Cursor, OpenCode, and more.

CodexAmp
Warp
CursorOpenCode
Cline
Gemini CLI
GitHub Copilot
Personal

Available across projects.

$npx skills-installer add @majiayu000/claude-skill-registry/symfonyinterfaces-and-autowiring --client shared
Project

Writes to .agents/skills.

$npx skills-installer add @majiayu000/claude-skill-registry/symfonyinterfaces-and-autowiring -p --client shared
Note: Review the skill 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