Elevate your Laravel code with cleaner, modular dependencies
You’re going to hear the term “dependency injection” many times throughout your Laravel career, so you might as well get acquainted with it. Before we get into Laravel, let’s look at Dependency Injection as a concept in PHP (or really any programming language).
https://medium.com/geekculture/laravel-p47-final-touchups-cmp-5b2764c43565
We should know that we can pass objects as arguments to methods. We can also type hint with the inherited class, which is an overview of polymorphism in PHP. We can pass the object as an argument to the constructor. The constructor will then set the property inside the class. This is called dependency injection.
In my PHP tutorial series, we looked at dependency injection. We can use that code to refresh ourselves. To begin, let’s create a couple of classes: Lamborghini and Ferrari. Each one of those classes will require the Car class.
<?php
require_once("Car.php");
class Lamborghini extends Car {
}
<?php
require_once("Ferrari.php");
class Ferrari extends Car {
}
We can now create our test class and instantiate those objects.
<?php
// Object Arguments
require_once("Lamborghini.php");
require_once("Ferrari.php");
$lamborghini_diablo = new Lamborghini(1999, "Lamborghini", "Diablo");
$ferrari_f355 = new Ferrari(1996, "Ferrari", "F355");
Do you think the above code will work? We haven’t added any constructor to our Lamborghini and Ferrari classes, but we’re passing arguments to the constructor. Believe it or not, it actually will work. Why? Because we’ve defined our constructor arguments within our Car
parent class. If there’s no constructor within our child class, it will automatically pass arguments to our parent class. Let’s take a quick look at our Car
class constructor.
<?php
require_once('Vehicle.php');
require_once('Engine.php');
require_once('Transmission.php');
class Car extends Vehicle
{
use Transmission, Engine {
Transmission::check_oil_level insteadof Engine;
Engine::check_oil_level as check_engine_oil_level;
}
const HAS_HEADLIGHTS = true;
const HAS_TAIL_LIGHTS = true;
const HAS_TURN_SIGNALS = true;
private int $fuel_type;
private int $hp;
private int $tq;
private string $transmission;
private string $vehicle_type;
private bool $car_on;
public function __construct(int $year,
string $make,
string $model,
string $color = "",
int $fuel_type = 93,
int $hp = -1,
int $tq = -1,
string $transmission = "5 Speed Manual",
string $vehicle_type = "",
float $exterior_height = -1,
float $exterior_width = -1,
float $exterior_length = -1,
string $exterior_um = "in",
float $weight = -1,
string $weight_um = "lbs",
bool $car_on = false
)
{
$this->year = $year;
$this->make = $make;
$this->model = $model;
$this->color = $color;
$this->fuel_type = $fuel_type;
$this->hp = $hp;
$this->tq = $tq;
$this->transmission = $transmission;
$this->vehicle_type = $vehicle_type;
$this->exterior_height = $exterior_height;
$this->exterior_width = $exterior_width;
$this->exterior_length = $exterior_length;
$this->exterior_um = $exterior_um;
$this->weight = $weight;
$this->weight_um = $weight_um;
$this->car_on = $car_on;
}
//...
}
What can we do now? We have access to all of the properties and methods in our parent class (as long as they’re not private). So we can call those properties and methods.
<?php
// Object Arguments
require_once("Lamborghini.php");
require_once("Ferrari.php");
$lamborghini_diablo = new Lamborghini(1999, "Lamborghini", "Diablo");
echo $lamborghini_diablo->check_engine_oil_level();
$ferrari_f355 = new Ferrari(1996, "Ferrari", "F355");
echo $ferrari_f355->check_oil_level();
Great. We still haven’t done any passing of objects as arguments.
What we’re going to do is create a Driver
class. The first method inside of our Driver
class will be the drive()
method since the Driver
should be able to drive()
a car. We can pass the Car
object to it as an argument and then we can use various different Car
methods to help us with driving the car. The two methods that we’ll focus on from our Car
class is the turnOn()
method and the drive()
method.
<?php
class Driver {
public function drive(Car $car) {
echo $car->turnOn();
echo $car->drive();
}
}
We can now go back to our test class and instantiate the object.
<?php
// Object Arguments
require_once("Lamborghini.php");
require_once("Ferrari.php");
$lamborghini_diablo = new Lamborghini(1999, "Lamborghini", "Diablo");
$ferrari_f355 = new Ferrari(1996, "Ferrari", "F355");
$dino = new Driver();
$dino->drive( $lamborghini_diablo );
What just happened? I thought we specified that we have to pass a Car
type object as an argument to the drive()
method. It’s in the drive()
declaration.
public function drive( Car $car )
And here’s your lesson on Polymorphism. Since both our Lamborghini
and Ferrari
classes extend the Car
class, technically they are Cars! Guess what? Since the Car
extends the Vehicle
class, technically the Lamborghini
and Ferrari
classes are Vehicles as well! You’re wondering if we can modify our declaration to say the following:
public function drive( Vehicle $vehicle )
The answer is yes! You absolutely can. And you would still pass the $lamborghini_diablo
or the $ferrari_f355
as an argument to the drive()
method. If we pass the $ferrari_f355
to our drive()
method, we get the following result: Ferrari F355 has been turned on. I’m driving.
Let’s do this a little differently now. We created class properties of int
, string
, etc. type? Just look at the Car
class and you’ll see all of the various different properties. How did we initialize those properties? From the constructor. When instantiating the object, we would pass arguments to the constructor and then the constructor would initialize those properties with the values that were passed through as arguments. Same concept with what we’ll do here in our Driver
class. We’ll specify that we’re going to have a Car
based property and we’re going to pass a Car
argument through our constructor and assign it to our Car
property. Remember, a Car
is just a data type like int
or string
.
<?php
class Driver {
private Car $car;
public function __construct(Car $car)
{
$this->car = $car;
}
}
I don’t want to lose you here now. Remember that once the $car
property has been initialized through our constructor, we’re going to have access to all of our Car
properties and methods, such as turnOn()
and drive()
. Since we know that we must pass a Car
type of object to our constructor and it must be assigned to our $car
property, we can create methods in anticipation that we’re going to have access to those Car
methods. So, let’s recreate our drive()
method inside of our Driver
class.
<?php
class Driver {
private Car $car;
public function __construct(Car $car)
{
$this->car = $car;
}
public function drive() {
echo $this->car->turnOn();
echo $this->car->drive();
echo $this->car->turnOff();
}
}
Do you see how this works? Let’s run an example in our test file and then walk through the code.
<?php
// Object Arguments
require_once("Lamborghini.php");
require_once("Ferrari.php");
require_once("Driver.php");
$lamborghini_diablo = new Lamborghini(1999, "Lamborghini", "Diablo");
$ferrari_f355 = new Ferrari(1996, "Ferrari", "F355");
$dino = new Driver( $ferrari_f355 );
$dino->drive();
$harrison = new Driver( $lamborghini_diablo );
$harrison->drive();
Time for a thorough code walk through.
- PHP enters the test class and requires the
Lamborghini
,Ferrari
, andDriver
classes. - A new
Lamborghini
object is instantiated, which is of typeCar
. - Since the
Lamborghini
class does not contain a constructor, the parent constructor is called and the three arguments are passed to the parent constructor:1999
,Lamborghini
,Diablo
. - A new
Ferrari
object is instantiated, which is also of typeCar
. - Since the
Ferrari
class does not contain a constructor, the parent constructor is called and the three arguments are passed to the parent constructor:1996
,Ferrari
,F355
. - A new
Driver
is created and the$ferrari_f355
object is passed to the constructor as an argument. - The
Driver
class initialized the$car
property inside itself with the$ferrari_f355
object that was just passed to it. The$car
property now has access to all of the methods inside of$ferrari_f355
. - The
drive()
method is invoked. - PHP enters the
drive()
method. It sees that it’s calling 3 different methods all located within the$car
property, which is technically the$ferrari_355
object. - PHP executes the
turnOn()
,drive()
, andturnOff()
methods. - A new
Driver
is created inside of the test file and the$lamborghini_diablo
object is passed to the constructor as an argument. - The
Driver
class initialized the$car
property inside itself with the$lamborghini_diablo
object that was just passed to it. The$car
property now has access to all of the methods inside of$lamborghini_diablo
. - The
drive()
method is invoked. - PHP enters the
drive()
method. It sees that it’s calling 3 different methods all located within the$car
property, which is technically the$lamborghini_diablo
object. - PHP executes the
turnOn()
,drive()
, andturnOff()
methods.
This passing of an argument through the constructor and assigning it to a property is what’s called Dependency Injection.
As long as you visualize the flow of how the argument moves through the code, I’m confident that you’ll understand dependency injection. Complicated name for a simple topic.
We’ll tackle Laravel’s use of dependenct injection in the next article.
Laravel Series
Continue your Laravel Learning.
Give your Laravel project the perfect finishing touches
Laravel – P47: CMP – Final Touchups
In part 47 of our Laravel series, wrap up your application by applying crucial final adjustments. Learn best practices for code refactoring, caching strategies, QA checks, and polishing UI elements to ensure a secure, high-performing, and fully optimized Laravel project.
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.
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.