DEV Community

Cover image for How to implement Dependency Injection in Laravel Livewire
Kirill
Kirill

Posted on

How to implement Dependency Injection in Laravel Livewire

I like to use Dependency Injection (DI) in my code. It helps me to keep my code clean and reduces code coupling.

What is Dependency Injection

Dependency Injection is a design pattern that allows for the separation of concerns by removing the responsibility of creating objects and their dependencies from the class that uses them. Instead, these dependencies are provided or injected into the class by a third-party or a container.

Here's an example in PHP:

class UserService {
    private $userRepository;

    public function __construct(UserRepository $userRepository) {
        $this->userRepository = $userRepository;
    }

    public function getUsers() {
        return $this->userRepository->findAll();
    }
}

class UserRepository {
    public function findAll() {
        // fetch users from the database
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, the UserService requires a UserRepository object to fetch users from the database. Instead of creating the UserRepository object inside the UserService, we inject it via the constructor. This allows for better separation of concerns and makes the UserService more flexible, as we can easily swap out the UserRepository implementation without changing the UserService code. You can read more about how it works in Laravel in documentation.

How to use DI with Livewire

When we use Livewire components, we can’t use the __construct method inside it because Livewire needs $id for a component.

I started to research and found this thread on GitHub. Some developers recommend using the mount() method like this:

use Livewire\Component;
use Psr\Log\LoggerInterface;

class Foo extends Component
{
    protected LoggerInterface $logger;

    public function mount(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    public function render()
    {
        $this->logger->info('component rendered');
        return view('livewire.foo');
    }

    public function action()
    {
        $this->logger->info('action triggered');
    }
}
Enter fullscreen mode Exit fullscreen mode

The problem with mount() DI is that it doesn't work in some cases. For example, when you click on the button and call action(), your $this->logger will be empty. This happens because the mount() method isn't called when the user interacts with the component.

The good news for us is that in version 2.6 of Livewire developers added boot() hook. This hook will be called every time you use your component. Here's how you can use DI inside your Livewire component:

class YourComponent extends Livewire\Component
{
    private SomeService $someService;
    private OneMoreService $oneMoreService;

    public function boot(
        SomeService $someService,
        OneMoreService $oneMoreService
    )
    {
        $this->someService = $someService;
        $this->oneMoreService = $oneMoreService;
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (8)

Collapse
 
dariusdauskurdis profile image
Darius Dauskurdis • Edited

Before I found your example I implemented like that:


<?php

namespace App\Http\Livewire;

use Livewire\Component;
use App\Services\SomeService;
use Illuminate\Support\Facades\App;

class Foo extends Component
{
    protected $someService;

    public function __construct()
    {
        $this->someService = App::make(SomeService::class);
    }

    public function mount()
    {
         $this->someService->doSomething();
    }

    public function render()
    {
        return view('livewire.foo');
    }
}

Enter fullscreen mode Exit fullscreen mode

But I like your example too. Thanks for sharing!

Collapse
 
kaper99 profile image
kaper99

It's not DI

Collapse
 
iamkirillart profile image
Kirill

Its kinda. You specified that your component depends on some class, but the realization can be different depending on the environment and runtime.

But yes, your component started depend on App class and it brings some magic.

Collapse
 
giuliano1993 profile image
Giuliano1993

This post explained exactly what i needed when i needed it! Thx so much!
Just for the record: this thing didn't change with Livewire 3; boot method is still the good way for injecting dependencies in a component 😄

Collapse
 
chatisk profile image
chatisk

How can I DI when a component A emits an event with a parameter array $data and on a component B which listens to that event, inject a DTO class ?

(Component B)
public function showComp(MyDTO $data).

Collapse
 
dissanayakeg profile image
dissanayakeG • Edited

What if we use a computed property?

class YourComponent extends Livewire\Component
{
    private SomeService $someService;

    #[Computed]
    public function someService()
    {
        return resolve(SomeService::class);
    }

    public function action()
    {
        $this->someService->info('action triggered');
    }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
iamkirillart profile image
Kirill

Honestly havent work with computed properties. Anyway resolve() is not about DI cause its magic method and your code now depends on resolve() method.

Did you find a solution?

Collapse
 
dissanayakeg profile image
dissanayakeG

The code I provided is working perfectly. I guess it is a better way to do this because computed properties cache data. so it makes things faster.