php 101 lines · 4 tabs

Collect Tagged Payment Gateways with a Symfony Compiler Pass and Service Locator

Shared by codesnips Jul 2026
4 tabs
<?php

namespace App\Payment;

use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;

#[AutoconfigureTag('app.payment_gateway')]
interface PaymentGatewayInterface
{
    public static function getName(): string;

    public function charge(int $amountCents, string $currency, string $token): ChargeResult;
}
4 files · php Explain with highlit

This snippet shows the idiomatic Symfony way to discover a set of interchangeable strategy services at container-build time and expose them lazily through a service locator. The domain is payment gateways: several PaymentGatewayInterface implementations exist (Stripe, PayPal, etc.), each keyed by a short name, and the application needs to resolve one by name at runtime without knowing all of them up front.

In PaymentGatewayInterface, the contract is minimal: a static getName() used as the locator key plus a charge() method returning a ChargeResult. Declaring getName() on the interface lets the compiler pass derive the tag key automatically, so services do not have to repeat their name in configuration. The #[AutoconfigureTag] attribute means every class implementing the interface is tagged app.payment_gateway without any manual services.yaml wiring.

The heart of the mechanism is PaymentGatewayPass, a CompilerPassInterface registered in the kernel. During compilation it calls findTaggedServiceIds() to enumerate all tagged gateways, reads each service's class, and asks the class for its getName() to build a map of name to ServiceClosureArgument (a reference wrapped so the service is instantiated only when actually pulled). That map is handed to ServiceLocatorTagPass::register(), which returns a locator reference bound as the first argument of PaymentGatewayRegistry. Building this map at compile time is the whole point: the container is optimized and cached, so runtime resolution is a cheap array lookup with zero reflection.

The trade-off is that the locator is lazy but fixed — gateways are enumerated once during the build, so adding one requires a container rebuild, which is exactly what production expects. PaymentGatewayRegistry receives the ContainerInterface locator via the #[AutoconfigureTag]-driven wiring and exposes get(), which throws a clear InvalidArgumentException when a name is unknown rather than failing deep inside the container.

This pattern is the canonical answer whenever a plugin-style collection of implementations must be selected by a runtime value; it avoids injecting every implementation eagerly and keeps registration declarative. The main pitfall is forgetting to register the pass in the kernel, in which case the locator argument stays empty and every lookup fails.


Related snips

kotlin
package com.example.myapp

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp

Dependency injection with Hilt

kotlin android hilt
by Alex Chen 3 tabs
php
<?php

namespace App\Providers;

use App\Contracts\PaymentGateway;
use App\Services\StripePaymentGateway;

Laravel service container and dependency injection

laravel dependency-injection service-container
by Carlos Mendez 2 tabs
php
<?php

namespace App\Event;

use Symfony\Contracts\EventDispatcher\Event;

Symfony: Send a Welcome Email Asynchronously with Messenger and an Event Subscriber

symfony messenger async
by codesnips 4 tabs
typescript
import { ApplicationConfig } from '@angular/core';
import {
  provideHttpClient,
  withInterceptors,
} from '@angular/common/http';
import { retryInterceptor } from './retry.interceptor';

Angular HttpInterceptor With Exponential Backoff and Jittered Retries

angular http-interceptor rxjs
by codesnips 3 tabs
ruby
class CreateStripeEvents < ActiveRecord::Migration[7.1]
  def change
    create_table :stripe_events do |t|
      t.string :stripe_event_id, null: false
      t.string :event_type, null: false
      t.string :status, null: false, default: "received"

Idempotent Stripe Webhook Processing in Rails with a Durable Event Log

rails stripe webhooks
by codesnips 4 tabs
python
from typing import Optional
from pydantic import BaseModel, EmailStr, Field, field_validator, model_validator


class CreateUserRequest(BaseModel):
    model_config = {"extra": "forbid"}

Validating JSON Payloads with Pydantic v2 and Returning Field-Level Errors in FastAPI

fastapi pydantic validation
by codesnips 3 tabs

Share this code

Here's the card — post it anywhere.

Collect Tagged Payment Gateways with a Symfony Compiler Pass and Service Locator — share card
Link copied