0Pricing
PHP Academy · Lección

Inversión de dependencias en la práctica

Conecte los adaptadores con el núcleo mediante inversión de dependencias.

Inversión de dependencias en la práctica es una lección gratuita de PHP Academy en CoddyKit. Esta es la lección 4 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de PHP Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de PHP Academy incluye 4 lecciones en total.

Inversión frente a inyección

Se suelen confundir dos ideas distintas. La inyección de dependencias es una técnica: pasar los colaboradores en lugar de construirlos. La inversión de dependencias (la D de SOLID) es un principio: la política de alto nivel y el detalle de bajo nivel dependen ambos de una abstracción, y la abstracción pertenece al módulo de alto nivel. Esta lección trata de hacer realidad la segunda idea: conectar adaptadores concretos con un núcleo que define las interfaces.

El principio, con precisión

El DIP establece lo siguiente:

  • Los módulos de alto nivel no deberían depender de los módulos de bajo nivel. Ambos deben depender de abstracciones.
  • Las abstracciones no deberían depender de los detalles. Los detalles deben depender de las abstracciones.

La parte sutil es que la interfaz pertenece al consumidor, no al implementador. Su dominio declara PaymentGateway; el adaptador de Stripe se ajusta a ella, no al contrario.

Defina la abstracción en el núcleo

Coloque la interfaz junto al código que la necesita y exprésela en términos del dominio. No deben filtrarse tipos de Stripe.

<?php
// src/Billing/Application/Port/PaymentGateway.php
interface PaymentGateway
{
    public function charge(Money $amount, CardToken $token): ChargeId;
}

final class Money {
    public function __construct(public readonly int $cents, public readonly string $currency) {}
}
final class CardToken { public function __construct(public readonly string $value) {} }
final class ChargeId { public function __construct(public readonly string $value) {} }

Haga que un adaptador se ajuste a ella

El adaptador de infraestructura implementa la interfaz del núcleo y traduce las llamadas al SDK del proveedor. La flecha de dependencia apunta del adaptador (detalle) a la abstracción (política): se ha logrado la inversión.

<?php
final class StripePaymentGateway implements PaymentGateway
{
    public function __construct(private \Stripe\StripeClient $stripe) {}

    public function charge(Money $amount, CardToken $token): ChargeId {
        $intent = $this->stripe->paymentIntents->create([
            'amount'   => $amount->cents,
            'currency' => strtolower($amount->currency),
            'payment_method' => $token->value,
            'confirm'  => true,
        ]);
        return new ChargeId($intent->id);
    }
}

La inyección por constructor es la opción predeterminada

Inyecte mediante el constructor y declare el tipo de la abstracción. Las dependencias se vuelven explícitas, inmutables e imposibles de olvidar. Evite la inyección mediante setters y propiedades para los colaboradores obligatorios: permite crear objetos a medias.

<?php
final class CheckoutService
{
    public function __construct(
        private PaymentGateway $payments,   // abstraction, not StripeClient
        private OrderRepository $orders,
    ) {}

    public function pay(OrderId $id, CardToken $token): ChargeId {
        $order  = $this->orders->get($id);
        $charge = $this->payments->charge($order->total(), $token);
        $order->markPaid($charge);
        $this->orders->save($order);
        return $charge;
    }
}

La raíz de composición

Toda la configuración concreta se realiza en un único lugar: la raíz de composición, tan cerca de main() o del punto de entrada como sea posible. Ningún otro componente crea instancias de la infraestructura. Este es el único lugar que sabe que existe Stripe.

<?php
// public/index.php — composition root
$stripe   = new \Stripe\StripeClient(getenv('STRIPE_SECRET'));
$gateway  = new StripePaymentGateway($stripe);
$orders   = new PdoOrderRepository(new PDO(getenv('DB_DSN')));
$checkout = new CheckoutService($gateway, $orders);

// Everything below depends only on abstractions
$controller = new CheckoutController($checkout);

Configuración con un contenedor DI

En aplicaciones no triviales, un contenedor (PHP-DI, Symfony) automatiza la configuración. La operación clave es vincular interfaces con implementaciones. El autowiring resuelve los constructores por tipo; solo tiene que declarar el mapa interfaz→clase.

<?php
use function DI\autowire;
use function DI\get;

return [
    PaymentGateway::class => autowire(StripePaymentGateway::class),
    OrderRepository::class => autowire(PdoOrderRepository::class),
    \Stripe\StripeClient::class => fn() => new \Stripe\StripeClient(getenv('STRIPE_SECRET')),
    PDO::class => fn() => new PDO(getenv('DB_DSN')),
];

Cambiar adaptadores demuestra la idea

Como el núcleo solo depende de PaymentGateway, cambiar de proveedor o probar sin conexión requiere modificar una sola línea. Este es un fake utilizado en una prueba unitaria; CheckoutService permanece sin cambios y nunca importa Stripe.

<?php
final class FakeGateway implements PaymentGateway {
    public array $charges = [];
    public function charge(Money $a, CardToken $t): ChargeId {
        $this->charges[] = $a;
        return new ChargeId('ch_test_' . count($this->charges));
    }
}

$fake = new FakeGateway();
$id = $fake->charge(new Money(2500, 'EUR'), new CardToken('tok_visa'));
echo $id->value, ' charges=', count($fake->charges), PHP_EOL; // ch_test_1 charges=1

Evite la trampa del localizador de servicios

Inyectar el propio contenedor y obtener dependencias dentro de los métodos es el antipatrón del localizador de servicios. Oculta las dependencias, anula la comprobación de tipos y vuelve a acoplar el código al contenedor. Inyecte explícitamente lo que necesita.

<?php
// ANTI-PATTERN: hidden dependencies, container leaks everywhere
final class BadCheckout {
    public function __construct(private ContainerInterface $c) {}
    public function pay($id, $token) {
        $gateway = $this->c->get(PaymentGateway::class); // hidden!
        // ...
    }
}

Dónde puede aparecer el contenedor

El contenedor solo es legítimo en una capa: la raíz de composición y el código de integración del framework que la rodea (por ejemplo, una fábrica de controladores). Sus clases de dominio y aplicación deben permanecer independientes del contenedor: reciben objetos normales mediante los constructores y podrían instanciarse manualmente. Una buena prueba es la siguiente: ¿podría configurar toda la aplicación en un único archivo PHP sin contenedor? Si la respuesta es afirmativa, sus dependencias son honestas.

Configuración diferida sin localización de servicios

A veces una dependencia es costosa de construir o solo se necesita de forma condicional. Resista la tentación de inyectar el contenedor; inyecte en su lugar una closure de fábrica. La dependencia sigue siendo explícita y está tipada, mientras que su construcción se difiere hasta que realmente se utiliza.

<?php
final class ReportService
{
    /** @param Closure():PaymentGateway $gatewayFactory */
    public function __construct(private Closure $gatewayFactory) {}

    public function refundIfNeeded(bool $needed): void {
        if (!$needed) return;
        $gateway = ($this->gatewayFactory)(); // built only when required
        // $gateway->charge(...) etc.
    }
}

// Composition root supplies the factory, not the container
$svc = new ReportService(fn() => new StripePaymentGateway($stripe ?? null));
echo 'lazy dependency wired', PHP_EOL;

Comprobación rápida

¿Quién debería ser propietario de la interfaz PaymentGateway?

Resumen

Ha conectado los adaptadores al núcleo de la forma correcta:

  • Inversión ≠ inyección: la inyección es el mecanismo; la inversión consiste en que el consumidor sea propietario de la abstracción.
  • El núcleo define la interfaz; los adaptadores de infraestructura se ajustan a ella.
  • Utilice la inyección por constructor de abstracciones y realice toda la configuración concreta en una única raíz de composición (o en la configuración del contenedor que vincula interfaz→clase).
  • Cambiar adaptadores y usar fakes en las pruebas se convierte en un cambio de una sola línea.
  • Evite el antipatrón del localizador de servicios; mantenga el contenedor únicamente en el extremo.

Preguntas frecuentes

¿La lección «Inversión de dependencias en la práctica» es gratis?

Sí — el texto completo de «Inversión de dependencias en la práctica» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de PHP Academy, actualiza a CoddyKit PRO. El curso de PHP Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Inversión de dependencias en la práctica»?

Conecte los adaptadores con el núcleo mediante inversión de dependencias. Practicas PHP Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar PHP Academy?

No se requiere experiencia previa. PHP Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 4 de 4.

¿Cuánto tiempo toma la lección «Inversión de dependencias en la práctica»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de PHP Academy?

Sí. Cada lección de PHP Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. De la arquitectura por capas a la arquitectura limpia
  2. Puertos y adaptadores explicados
  3. Casos de uso y servicios de aplicación
  4. Inversión de dependencias en la práctica
← Volver a PHP Academy