MVC Lesson 12: Dependency Injection & Container in depth

Dependency Injection works by scanning the list of dependencies from within the construct methods parameters list before an instance of home controller class is created.

The container view the list of parameters during this process.

It will look at datatype. it will compare datatype of our parameters with the list of destinations,if there is a match, the corresponding factory fn() is called and instance return by the fn() is assigned as the value of parameter.

After all , dependencies have been created, the instance of controller is created along with the array of dependencies.

By this way our controller is no longer responsible for creating instance of classes if it needs a dependency.All it has to do is ask for it from the container by listing it inside the construct method.

Too complex to understand? Let’s understand using simple analogy.

🧩 Analogy: Chef and a Recipe Book (Container)

🎯 Situation:

You’re a Chef (Controller), and you want to make Pizza.

To make pizza, you need:

  • Dough
  • Sauce
  • Cheese

But instead of making these from scratch, you tell your assistant:

“Hey, I need a Pizza. Here are the ingredients I need: Dough, Sauce, Cheese.”

🧠 What the Assistant (Container) does:

  1. He checks a recipe book (definitions list).
  2. He sees:
    • For Dough, there’s a factory method: “Mix flour and water”
    • For Sauce, there’s: “Cook tomatoes”
    • For Cheese, there’s: “Use mozzarella block”
  3. He creates each ingredient, based on those instructions.
  4. He gives all ingredients to you (the Chef).
  5. You make the Pizza, with everything ready.

🧑‍💻 How it maps to PHP:

Code:

class PizzaController {
    public function __construct(Dough $dough, Sauce $sauce, Cheese $cheese) {
        // Dependencies (ingredients) are passed in
    }
}

What the Container does:

  1. Sees the constructor needs: Dough, Sauce, Cheese
  2. Checks its registry:
    • Has a way to create each (e.g., a factory function or class binding)
  3. Automatically creates those objects
  4. Passes them into the PizzaController
  5. Done — you now have an instance with everything injected!

💬 Final Summary in Simple Words

A container is like a smart assistant.
If a class (like a Controller) says: “I need X, Y, and Z”,
the container will:

  • Read the list,
  • Create each item,
  • Inject them automatically when creating the class.

This way, the class doesn’t make anything itself — it just asks for what it needs.

Let’s break down the above text in PHP terms.

🧾 Original text:

“Dependency injection works by scanning the list of dependencies from within the constructor method’s parameter list before an instance of the class is created…”

✅ PHP Explanation:

When you define a class like this:

class HomeController {
    public function __construct(UserService $userService) {
        $this->userService = $userService;
    }
}

You’re telling PHP:

“Whenever someone wants to create an object of HomeController, make sure you give it an instance of UserService.”

Now, if you’re using Laravel or a custom container, it will automatically look at the constructor and say:

“Oh, this needs a UserService. Let me build it first.”


🧾 Original text:

“The container views the list of parameters during this process…”

✅ PHP Explanation:

The container (like Laravel’s service container) inspects the constructor using a feature called reflection in PHP. That means it looks at:

  • What the parameters are
  • What their types are (like UserService or Mailer)

🧾 Original text:

“It will look at datatypes. It will compare datatypes of our parameters with the list of definitions…”

✅ PHP Explanation:

Inside Laravel (or your container), there’s a list (or map) of what class maps to what builder/factory. Example:

App::bind(UserService::class, function () {
    return new UserService(new UserRepository());
});

So when the container sees UserService is needed, it knows how to create it automatically.


🧾 Original text:

“If there is a match, the corresponding factory function is called…”

✅ PHP Explanation:

If the container finds a match for UserService, it calls the function or class definition (called a factory or binding), and returns the ready-made object.


🧾 Original text:

“After all dependencies have been created, the instance of the controller is created…”

✅ PHP Explanation:

Once all the needed objects (UserService, Mailer, etc.) are ready, the container finally does:

return new HomeController($userService, $mailer);

So you’re not doing this manually — the container handles it for you.


🧾 Original text:

“By this way, our controller is no longer responsible for creating instance of classes if it needs a dependency…”

✅ PHP Explanation:

The controller doesn’t write code like:

$this->userService = new UserService();

Instead, the controller just lists what it needs in the constructor, and gets it injected automatically.


🟩 Final Wrap-up

If you’re new to PHP:

  • When you define dependencies in the __construct() method with type hints, you’re asking the container to inject those objects for you.
  • You don’t have to write new ClassName() inside your class.
  • The container handles object creation and passes them where needed.

It’s like PHP + container = Auto-magic object delivery 🚚

✅ Why You Should Learn Dependency Injection (DI) in Custom PHP MVC:

1. Foundation for Laravel and Modern PHP

  • Laravel relies heavily on dependency injection.
  • If you learn DI now in your own custom PHP MVC, you’ll understand how Laravel works under the hood.
  • It’ll be super easy to shift to Laravel later because you’ll already “think the Laravel way”.

💡 Laravel’s service container does automatic DI. If you don’t understand DI, Laravel’s controller injections will feel like magic (and confusing).


2. Cleaner and More Testable Code

  • In custom MVC, DI helps you write clean, flexible, and testable code.
  • Example: You can swap a real database class with a fake one in testing:
// Real
$controller = new HomeController(new DBConnection());

// Test
$controller = new HomeController(new FakeDB());

Without DI, you’d have to change the class itself — that’s messy.


3. Learn How Frameworks Work

  • By implementing DI yourself (even in a basic way), you learn how Laravel, Symfony, and other frameworks build and inject services.
  • This gives you confidence when you use them — you’ll know what’s happening under the hood.

4. You’ll Write More Scalable Code

  • DI promotes loose coupling, which means your code becomes more flexible as your app grows.
  • In large apps, DI is not optional — it’s necessary to keep things manageable.

🧾 Is It Worth Learning DI Before Laravel?

👉 Yes, 100%.

  • If you understand DI in plain PHP, Laravel becomes easier to learn and master.
  • You’ll understand what Laravel is doing when it magically gives you objects like Request, Mail, Logger, etc. in constructors.

🧠 In Simple Words:

If Laravel is a car, Dependency Injection is the engine.
You don’t need to build the full engine, but you should understand how it runs if you ever want to drive like a pro.

Simple example:

<?php

echo 'Let\'s try Dependency Injection';

class One
{
    public $name;
    public function __construct()
    {
        $this->name = "Dilip";
    }
}

class Two
{
    public $newname;
    public function __construct(One $one)
    {
        $this->newname = $one->name;
    }
}

$obj1 = new One;
$obj2 = new Two($obj1);
var_dump($obj2);

echo "</br>My name is $obj2->newname";


// This way we can implement multiple DB with single project.
// $db1 = new DB1(param);
// $db2 = new DB2(param);
// $app->connect($db1); OR
// $app->connect($db2);

🧠 Your Thought:

Using Dependency Injection, some common properties (like email, login status, session, isAdmin, etc.) are automatically available inside classes/services.

✅ Correct. Here’s how:

When you use Dependency Injection, you inject pre-built objects (like User, Request, Session, etc.) into your classes.

These objects:

  • Already contain useful information
  • Are often pre-filled by the framework/container
  • Can be used across multiple services/controllers

🧾 Example (in Laravel):

class DashboardController {
    public function __construct(Request $request) {
        if ($request->user()->isAdmin()) {
            // do admin stuff
        }
    }
}
  • Laravel’s container injects the Request object.
  • The Request already knows:
    • Who the user is ($request->user())
    • What their email is
    • What their role is (isAdmin()), etc.

You didn’t build any of this manually. The container:

  • Created the Request object
  • Populated it with session, cookies, login state
  • Gave it to your controller automatically

🔁 Same Concept in Custom PHP MVC (if you build it):

You can create and inject services like:

class AuthService {
    public function isAdmin() {
        return $_SESSION['role'] === 'admin';
    }

    public function getEmail() {
        return $_SESSION['email'] ?? null;
    }
}

Then inject it like:

$controller = new DashboardController(new AuthService());

Now your controller has access to user info without using globals or manually checking session inside every controller.


✅ Final Thoughts:

Yes — Dependency Injection lets you pass in ready-to-use objects that already have useful data like:

  • Logged-in user’s email
  • Whether they are admin
  • Request time, cookies, session values
  • Current timezone, locale, etc.

This keeps your code clean, reusable, and testable — and Laravel does all of this behind the scenes using its service container.

🧠 Your Idea:

Using Dependency Injection, I can access the properties or methods of another class (like a “main class”) without extending it, because the container already created it and set everything for me.

✅ Yes, that’s exactly right.

Let’s break it down:


🔍 Without Dependency Injection

You might try to inherit from a class just to get access to some useful methods or properties:

class MainService {
    protected $email;

    public function __construct() {
        $this->email = $_SESSION['email'] ?? null;
    }
}

class SubController extends MainService {
    public function showEmail() {
        echo $this->email;
    }
}

But inheritance is not flexible, and it’s bad if you’re only inheriting to use one or two things.


✅ With Dependency Injection (Better Way)

You don’t need to inherit anything. Instead, you inject the object you want to use:

class MainService {
    public $email;

    public function __construct() {
        $this->email = $_SESSION['email'] ?? null;
    }
}

class SubController {
    public $main;

    public function __construct(MainService $main) {
        $this->main = $main;
    }

    public function showEmail() {
        echo $this->main->email;
    }
}

Now, you’re using MainService without extending it.
You get access to its properties and methods like this:

$main = new MainService();
$controller = new SubController($main);
$controller->showEmail();

💡 What the Container Does (Automatically):

If you’re using a container (like Laravel), you don’t even do the new MainService() part — the container does it for you, including all property setup.

$controller = $container->make(SubController::class);

The container:

  • Sees that SubController needs MainService
  • Creates MainService and runs its constructor
  • Fills in $email (or other needed data)
  • Injects it into SubController

✅ Final Summary

  • You can use another class’s properties/methods without inheritance
  • ✅ Because the container already creates and sets up the dependency
  • ✅ This is better than inheritance because it avoids tight coupling and keeps your code flexible