Dependency Injection (DI) in Yii2

  Add to Bookmark

Introduction

Dependency Injection (DI) is a design pattern that promotes loose coupling by allowing dependencies to be injected rather than hardcoded within classes. Yii2 provides a built-in DI container to manage dependencies efficiently.

In this tutorial, we will explore how DI works in Yii2, its benefits, and two real-world examples:

  1. Using DI in a REST API (Payment Gateway System)
  2. Using DI in a Web Application (Notification System)

Understanding Dependency Injection in Yii2

Why Use Dependency Injection?

  • Reduces tight coupling between classes
  • Improves code maintainability and testability
  • Makes it easier to switch implementations without modifying code

Yii2 Dependency Injection Container

Yii2 provides a built-in DI container accessible via Yii::$container. This container allows you to define and resolve dependencies dynamically.


Example 1: Dependency Injection in a REST API (Payment Gateway System)

Step 1: Define an Interface

First, create an interface that all payment gateways must implement.

namespace app\services;
interface PaymentGatewayInterface {
    public function charge($amount);
}

Step 2: Create Implementations

Stripe Payment Gateway

namespace app\services;
class StripePayment implements PaymentGatewayInterface {
    public function charge($amount) {
        return "Charged $amount via Stripe";
    }
}

PayPal Payment Gateway

namespace app\services;
class PayPalPayment implements PaymentGatewayInterface {
    public function charge($amount) {
        return "Charged $amount via PayPal";
    }
}

Step 3: Register Payment Gateway in DI Container

Modify config/web.php to set a default payment gateway.

return [
    'bootstrap' => [
        function () {
            Yii::$container->set(PaymentGatewayInterface::class, StripePayment::class);
        }
    ],
];

Step 4: Inject Dependency into a Controller

namespace app\controllers;
use Yii;
use yii\web\Controller;
use app\services\PaymentGatewayInterface;
class PaymentController extends Controller {
    private $paymentGateway;
    public function __construct($id, $module, PaymentGatewayInterface $paymentGateway, $config = []) {
        $this->paymentGateway = $paymentGateway;
        parent::__construct($id, $module, $config);
    }
    public function actionCharge() {
        $amount = Yii::$app->request->get('amount', 100);
        return $this->paymentGateway->charge($amount);
    }
}

Example 2: Dependency Injection in a Web Application (Notification System)

Step 1: Define an Interface

namespace app\services;
interface NotificationServiceInterface {
    public function send($recipient, $message);
}

Step 2: Create Implementations

Email Notification Service

namespace app\services;
class EmailNotificationService implements NotificationServiceInterface {
    public function send($recipient, $message) {
        return "Email sent to {$recipient} with message: '{$message}'";
    }
}

SMS Notification Service

namespace app\services;
class SmsNotificationService implements NotificationServiceInterface {
    public function send($recipient, $message) {
        return "SMS sent to {$recipient} with message: '{$message}'";
    }
}

Step 3: Register Notification Service in DI Container

Modify config/web.php to set a default notification service.

return [
    'bootstrap' => [
        function () {
            Yii::$container->set(NotificationServiceInterface::class, EmailNotificationService::class);
        }
    ],
];

Step 4: Using the Notification Service Without Explicit Injection

Since the service is registered in Yii::$container, it can be retrieved anywhere in the application using Yii::$container->get().

Example: Calling Notification Service in a Controller Method

namespace app\controllers;
use Yii;
use yii\web\Controller;
use app\services\NotificationServiceInterface;
class NotificationController extends Controller {
    public function actionSend() {
        $recipient = Yii::$app->request->get('recipient', 'user@example.com');
        $message = Yii::$app->request->get('message', 'Hello from Yii2!');
        $notificationService = Yii::$container->get(NotificationServiceInterface::class);
        $response = $notificationService->send($recipient, $message);
        return $this->render('send', ['response' => $response]);
    }
}

Example: Using Notification Service in a Model

namespace app\models;
use Yii;
use app\services\NotificationServiceInterface;
use yii\db\ActiveRecord;
class User extends ActiveRecord {
    public function afterSave($insert, $changedAttributes) {
        parent::afterSave($insert, $changedAttributes);
        if ($insert) {
            $notificationService = Yii::$container->get(NotificationServiceInterface::class);
            $notificationService->send($this->email, "Welcome to our platform, {$this->name}!");
        }
    }
}

Example: Sending Notification Inside a Component

namespace app\components;
use Yii;
use app\services\NotificationServiceInterface;
class OrderProcessor {
    public function processOrder($order) {
        Yii::$container->get(NotificationServiceInterface::class)->send($order->user->email, "Your order #{$order->id} has been processed.");
    }
}

Conclusion

Summary of Approaches

ApproachProsCons
Injecting in Controller- Makes dependencies explicit- Better for Unit Testing- Requires passing dependency manually
Fetching from DI Container- Can be used anywhere in app- No need to inject into constructor- Harder to mock for testing

Using Dependency Injection in Yii2 allows for clean, maintainable, and testable code. Whether you choose to inject dependencies in controllers or resolve them directly from the DI container, Yii2 provides a flexible and efficient way to manage dependencies in your application.