# All about the Laravel Container and Dependency Injection

Hey guys,

Today I'll share the "everything" of Laravel Service Container & the Dependency Injection.

Also, some interviewing tips 😉

This one is the most important and you might think it is enormous and complex, but honestly, it is not.

## Glossary

* DI aka Dependency Injection: the simplest way to get the dependent instance for your method/class.
    
* Service Container: the global container in Laravel Lifecycle which holds the source of truth about initializing classes.
    

## What's a Service Container?

If you [read the documentation](https://laravel.com/docs/10.x/container) from Laravel, it tells you the high level of the Service Container and its usage.

But on a deeper level, you'll ask how and why? Let me show you.

Service Container is a **simple key-value container** which holds:

* The way to initialize the given class.
    
* The bound singleton instances.
    
* The bound primitive types values.
    

And Service Container will help you to produce and distribute the build instances. Basically, Factory pattern 😆

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1684299516226/52725f33-4130-4479-baf5-21147c302783.png align="center")

### bind

```php
// AppServiceProvider.php

public function register(): void
{
    $this->app->bind(TransferService::class, function () {
        return new TransferService($this->app['db']);
    });
}
```

You'll use `bind` to give the instruction to Laravel Service Container the "how to initialize this concrete class or interface".

Every time you requested the `TransferService` class using DI, the Laravel Service Container will initialize and return you the **brand new instance**.

```php
class HelloClass {
    public function __construct(
        // #1 instance
        public TransferService $service
    ) {}
}

class AloClass {
    public function __construct(
        // #2 instance
        public TransferService $service
    ) {}
}
```

### singleton

Initialize once, utilize it throughout the entire lifecycle.

```php
public function register(): void
{
    // normal
    $this->app->singleton(TransferService::class);

    // conditional
    $this->app->singleton(TransferService::class, fn () => ...);
}
```

Every time you request the `TransferService` class using Dependency Injection, the Laravel Service Container will initialize it (once), cache it, and return it to you. For subsequent accesses, the Service Container will return the cached instance.

### bind/singleton as a hardcoded string

Basically the `bind` or `singleton` takes the first parameter as the `key`, it can be:

* any string
    
* classpath
    

Depend on your need.

```php
$this->app->bind('seth', fn () => new SethPhat());
$this->app->bind(SethPhat::class);
```

The only difference is that, when using a hardcoded string as a key, you cannot inject your concrete class through the constructor.

To get the concrete class using the hardcoded string, you need to use the global `app` function:

```php
app('seth'); // returns SethPhat instance
app()->make('seth'); // returns SethPhat instance
```

Note: you can eventually bind the "primitive" values as well, but please avoid doing so. Let's use `Config` for that. Service Container **should only produce object instances**.

### bind/singleton with custom parameters

Yes, it is possible, but the same as the above way, we **have to use** the `app` helper.

```php
$this->app->bind(MyInterface::class, function ($app, $params) {
    $service = $params['service'] ?? null;

    return match ($service) {
        'gotenberg' => $this->app->make(GotenbergService::class),
        'mpdf' => $this->app->make(MpdfService::class),
    };
});

// get the instance
app(MyInterface::class)->makeWith([
    'service' => 'gotenberg',
]);
```

## Dependency Injection

### Constructor Injection

From your own classes, you can specify the dependencies:

```php
class RenderService
{
    public function __construct(
        private GotenbergDriver $renderDriver
    ) {}
}
```

### Inject dependencies for Controller

You can use `Contructor Injection`, as well as method injection

```php
class VideoController extends Controller
{
    public function render(
        VideoRenderRequest $request, // custom form request
        Video $video, // eloquent - model route binding
        RenderService $renderService // inject
    ): JsonResponse {
        $result = $renderService->render($video);
        //...
    }
}
```

### Global App Helper

You can use the `app` function helper (or `App` facade) too, really convenient.

```php
app(RenderService::class;) // will return the RenderService instance
App::make(RenderService::class);
app(MyInterface::class)->makeWith([
    'service' => 'gotenberg',
]);
```

## Testing

One of the most significant advantages of DI is that you can mock the dependencies and test your flow easily.

For example I have this class

```php
class RenderService
{
    public function __construct(
        private GotenbergDriver $renderDriver
    ) {}

    public function render(Video $video): bool
    {
        $result = $this->renderDriver->render($video);
        
        if ($result->hasError()) {
            return false;
        }

        return true;
    }
}
```

So to test both cases, we can mock the `GotenbergDriver` to return ok and error for both cases.

Here is how you will create a mock and let Laravel Service Container serves your mocked instance.

```php
// normal way, pass as a param
public testRenderOk()
{
    $video = Video::factory()->create();

    $renderMock = $this->createMock(GotenbergDriver::class);
    $renderMock->method('render')
        ->willReturn(RenderResult::ok(...));

    $service = new RenderService($renderMock);
    $this->assertTrue($service->render($video));  
}

// but if you are using "app" helper, follow this
public testRenderFailed()
{
    $video = Video::factory()->create();

    $renderMock = $this->createMock(GotenbergDriver::class);
    $renderMock->method('render')
        ->willReturn(RenderResult::error(...));
        
    app()->offsetSet(GotenbergDriver::class, $renderMock);
    
    // for example, no constructor injection
    // but use "app" in the "render" method
    $service = new RenderService();
    $this->assertFalse($service->render($video));  
}
```

## Conclusion

I hope this topic has provided you with a deep understanding of the Laravel Service Container, making your life easier while working with it.

Thank you for reading!
