Laravel — P49: Dependency Injection

Implement effective DI patterns for cleaner, testable Laravel code

I hope that the last article made sense. Dependency Injection really is a simple concept. I feel like you should understand it in its entirety before you state that you know “Dependency Injection in Laravel.”

Before we get into Dependency Injection again, you should understand Laravel’s service container. What is a service container in Laravel? It’s simply a tool that manages dependencies and it is this tool that actually performs the dependency injection.

https://medium.com/geekculture/laravel-p48-dependency-injection-concept-high-level-php-concept-482e9f13d97b

In the previous article, we looked at an extremely simple dependency injection example.

<?php

class Driver {

    private Car $car;

    public function __construct(Car $car)
    {
        $this->car = $car;
    }
}

The interesting part is that if you leave out the implementation details, we can type hint our class and inject it into our routes or controller methods.

class Car
{
    //
}
 
Route::get('/', function (Car $car) {
    die(get_class($car));
});

The route automatically resolves the class, which is pretty amazing. You don’t have to specify how to do this. It just does it. The most frequently injected class is the Request. When we submit a form, Laravel does its thing and loads our Request $request object with all of our form data so that we can access an input field (like email) with $request->email.

You simply type-hint Request $request and you’re ready to go.

<form action="/personalcars" method="post" enctype="multipart/form-data">
    @csrf
    <div class="flex ...">
        <label class="mb-2 ..." for="year">Year</label>
        <input 
            class="border ...0" 
            type="number" 
            name="year" 
            id="year" 
            min="1920" 
            max="{{ date('Y') + 1 }}" 
            placeholder="2003"
        >
    </div>
</div>
use Illuminate\Http\Request;
 
Route::post('/personalcars/', function (Request $request) {
    return $request->year;
});

Without worrying about how to bring in Request or how to resolve it, you simply type hint it and Laravel automatically resolves the dependency for you. You can do the same thing in controllers. We’ve actually seen it in action already.

<?php

namespace App\Http\Controllers;

use App\Models\Image;
use App\Models\PersonalCar;
use App\Models\PersonalCarBrand;
use App\Models\PersonalCarModel;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class PersonalCarController extends Controller
{
    // ...

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     */
    public function update(Request $request, $id)
    {
        $validated = $request->validate([
            'year' => 'required|integer',
            'make' => 'required|max:255',
            'model' => 'required|max:255',
            'is_manual' => 'required|boolean',
            'exterior_color' => 'required|max:255',
            'purchase_amount' => 'numeric',
            'current_value' => 'numeric',
            'sales_amount' => 'numeric|nullable',
            'date_purchased' => 'required|date',
            'date_sold' => 'date|nullable',
            'file' => 'image|mimes:jpg,jpeg,png,gif|max:2048|nullable',
        ]);

        $car = PersonalCar::find($id);

        $brand = PersonalCarBrand::firstOrCreate([
            'name' => $request->make,
        ], [
            'slug' => str_replace(" ", "-", strtolower($request->make))
        ]);

        $model = PersonalCarModel::firstOrCreate([
            'name' => $request->model,
        ], [
            'slug' => str_replace(" ", "-", strtolower($request->model))
        ]);

        $car->year            = $request->year;
        $car->brand()->associate($brand);
        $car->model()->associate($model);
        $car->is_manual       = $request->is_manual;
        $car->exterior_color  = $request->exterior_color;
        $car->purchase_amount = $request->purchase_amount;
        $car->current_value   = $request->current_value;
        $car->sales_amount    = $request->sales_amount == 0 ? 0 : $request->sales_amount;
        $car->date_purchased  = $request->date_purchased;
        $car->date_sold       = $request->date_sold;

        $car->save();

        if ( $request->file('file') !== null ) {
            $image_path = $request->file('file')->store('images', 'public');

            $image = Image::create([
                'url' => $image_path,
                'alt' => $request->year . " " . $brand->name . " " . $model->name,
            ]);

            $car->images()->attach($image);
        }

        return redirect()->to('/personalcars/' . $id . '/edit')->with('status', 'Your car has been updated.');
    }
}

In our update method, we simply type-hinted Request $request and Laravel automatically resolved our dependency. We also passed the $id from our route. We didn’t need to pass $request. We simply type-hinted it and Laravel resolved it.

Route::prefix('/personalcars')->group(function() {
    // ...
    Route::put('/{id}', [PersonalCarController::class, 'update']);
});

Why would we want to do this though? Why would Laravel want for us to utilize Dependency Injection instead of just instantiating the Request object?

A class may be dependent on another class, and that class on a class of its own, and so on. You would have to instantiate each of those classes and make sure everything works well together before you have a chance to use it. With Dependency Injection, Laravel’s service container takes care of that instantiation process behind the scenes.

Example of Using Dependency Injection in Laravel

Now that the concept is somewhat floating in your head, let’s see if we can write a more concrete example.

I’ll start by creating a route and a controller.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class DependencyInjectionTestController extends Controller
{
    public function index(Request $request)
    {
        //
    }
}
use App\Http\Controllers\DependencyInjectionTestController;

Route::get('/dependency-injection', [DependencyInjectionTestController::class, 'index']);

In our index method, we can now use $request->input to display a name that we pass to our route.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class DependencyInjectionTestController extends Controller
{
    public function index(Request $request)
    {
        return $request->input('name');
    }
}

To test our route, we simply need to visit a route like the following:

http://0.0.0.0/dependency-injection?name=Dino

The output will be Dino.

Next, let’s create a service and inject it too. I’ll create a directory called Services and add my service named CapitalizeStringService.

As you might have guessed, this service class will just capitalize a string.

<?php

namespace App\Services;

class CapitalizeStringService
{
    public function capitalize($string)
    {
        return strtoupper($string);
    }
}

Now, let’s inject this Service into our index method in the DependencyInjectionTestController.

<?php

namespace App\Http\Controllers;

use App\Services\CapitalizeStringService;
use Illuminate\Http\Request;

class DependencyInjectionTestController extends Controller
{
    public function index(Request $request, CapitalizeStringService $capitalizeStringService)
    {
        $upper = $capitalizeStringService->capitalize( $request->input('name') );

        return $upper;
    }
}

What we’re doing here is first retrieving the name argument from the URL and then passing it to our CapitalizeStringService::capitalize method. The capitalize method converts the string to uppercase and returns it. We then return the string that was generated. If we refresh the page, we get: DINO. Pretty cool. We didn’t have to instantiate our CapitalizeStringService class. We just type-hinted it and Laravel’s service container took care of that.

What if we didn’t want to type-hint and we didn’t want to instantiate directly ourselves? We’ve spoken about the service container but we haven’t really looked at the example. We simply need to use the app() global helper function.

<?php

namespace App\Http\Controllers;

use App\Services\CapitalizeStringService;
use Illuminate\Http\Request;

class DependencyInjectionTestController extends Controller
{
    public function index(Request $request, CapitalizeStringService $capitalizeStringService)
    {
        $upper = $capitalizeStringService->capitalize( $request->input('name') );

        return $upper;
    }

    public function test()
    {
        $request = app()->make(Request::class);
        $capitalizeStringService = app()->make(CapitalizeStringService::class);

        $upper = $capitalizeStringService->capitalize( $request->input('name') );

        return $upper;
    }
}

To get to this method, we’ll use the following route:

Route::get('/dependency-injection/test', [DependencyInjectionTestController::class, 'test']);

And we’ll type the following URL into our address bar.

http://0.0.0.0/dependency-injection/test?name=Dino

It still works. We still get DINO. That’s the service container doing its thing behind the scenes. But instead of going through that process, you simply type-hint the class and Laravel takes care of it for you. In most instances, you’ll never interact with the service container in this way thanks to automatic dependency injection.

When would you want to? If you create a class that implements an interface or if you’re writing Laravel packages. For our purposes, as long as we understand how dependency injection works in Laravel, that’s good enough for us.

https://github.com/dinocajic/youtube-laravel

Laravel Series

Continue your Laravel Learning.

Laravel — P48: Dependency Injection Concept (High-Level PHP Concept)

Elevate your Laravel code with cleaner, modular dependencies

Laravel – P48: Dependency Injection Concept

In part 48 of our Laravel series, dive into the high-level PHP concept of Dependency Injection. Learn how to write cleaner, more testable code by decoupling class dependencies, improving maintainability, and promoting flexible, scalable design patterns within your Laravel applications.

Laravel — P49: Dependency Injection

Implement effective DI patterns for cleaner, testable Laravel code

Laravel – P49: Dependency Injection

In part 49 of our Laravel series, learn how to apply Dependency Injection in practice. Discover how to structure your project for clearer separation of concerns, enhance testability, and simplify code maintenance by fully leveraging Laravel’s built-in DI container.

Laravel — P50: Routing and Dependency Injection

Combine smart routing with seamless DI

Laravel – P50: Routing and Dependency Injection

In part 50 of our Laravel series, see how routing and the framework’s dependency injection container work together. Learn to inject services directly into route closures and controllers to keep code clean, testable, and flexible.

Leave a Reply