Laravel5 dependency injection on Model
Thanks to @svmm for referencing the question mentioned in the comments. I found that you cannot use dependency injection on Models because you would have to change the signature on the constructor which doesn't work with the Eloquent framework.
What I did as an intermediate step, while refactoring the code, is use App::make
in the constructor to create the object, such as:
class Surface extends Model{
public function __construct()
{
$this->zipCode = App::make('App\Repositories\ZipCodeRepositoryInterface');
}
That way the IoC will still grab the implemented repository. I am only doing this until I can pull the functions into the repository to remove the dependency.
However it might not be a good practice to inject services into your models either by constructor or method injection, think about designing the system in such a way that you do not need to do that and instead maybe inject the model into a service.
Let's see an example(just a dummy example in order to get to the point!).
- Say we have Basket and Order models, and we want to add orders to basket
- And we have a discount service that calculates discount based on orders
- Every time user adds an order to basket we need to calculate new discount and set it on basket
one approach is:
class OrderController
{
function store(User $user, Order $order)
{
$basket = $user->getBasket();
$basket->addOrder($order);
}
}
class Basket
{
private $discountService;
public function __construct(DiscountService $discountService)
{
$this->discountService = $discountService;
}
function addOrder(Order $order)
{
$this->orders[] = $order;
$discount = $this->discountService->calculateFor($this->orders);
$this->discount = $discount;
}
}
class DiscountService
{
function calculateFor(array $orders) {
// code for calculating discount;
return $discount;
}
}
In this approach we injected discount service into Basket model
Another better approach would be like this:
class OrderController
{
private $discountService;
public function __construct(DiscountService $discountService)
{
$this->discountService = $discountService;
}
function store(User $user, Order $order)
{
$basket = $user->getBasket();
$basket->addOrder($order);
$this->discountService->setDiscount($basket);
}
}
class Basket
{
function addOrder(Order $order)
{
$this->orders[] = $order;
}
function getOrders()
{
return $this->orders;
}
function setDiscount(int $discount)
{
$this->discount = $discount;
}
}
class DiscountService
{
function setDiscount(Basket $basket) {
$discount = $this->calculateFor($basket->getOrders());
$basket->setDiscount($discount);
}
private function calculateFor(array $orders)
{
// code for calculating discount
return $discount;
}
}
- In the first approach basket is making the decision about having discount, but this is not basket's concern
- In the first approach basket depends on discount service, but in real world you don't need a discount service to have a basket
In Laravel 5.7 you can use the global resolve(...)
method. I don't think the global App
is defined in more recent version of Laravel.
$myService = resolve(ServiceName::class);
Resolving in Laravel docs