Laravel5 dependency injection on Model - laravel-5

I have an Eloquent Model called Surface which is dependent on a ZipCodeRepository object:
class Surface extends Model{
public function __construct(ZipCodeRepositoryInterface $zipCode){...}
and an Address object that hasMany surfaces.
class Address extends Model{
public surfaces() { return $this->hasMany('App/Surface'); }
}
My issue is when I call $address->surfaces I get the following error:
Argument 1 passed to App\Surface::__construct() must be an instance of App\Repositories\ZipCodeRepositoryInterface, none given
I thought the IoC would automatically inject that.

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.

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

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

Related

Parent Controller class to call child overridden methods (Laravel)

I know this might seem anti pattern, and a lot will throw stones at me, but please hear me out.
I want to create a generic Controller to support many reference tables (mostly id, label). So I did something like this:
class GenericController extends Controller
{
public function index($modelName)
{
$x = '\\App\\Models\\'.$modelName;
$data = $model->all();
return view('generic.list', ['model'=>$model, 'data'=>$data]);
}
}
And this way my routes in web.php will be reduced to the minimum like this:
//List
Route::get('/{model}', function ($model) {
return App::call('\App\Http\Controllers\GenericController#index', ['modelName' => $model]);
});
It's working very well with simple CRUD actions like store, update, etc.. However I know I am over simplifying the design because sometimes I need to return a field from a joined table in the index list for example. That's where I am heading into a dead end, sort of.
My first thought was to create a controller for each model that inherits from the GenericController like this:
class CategoryController extends GenericController
{
}
And whenever I need to override the GenericController method, I would simply add it to the child class. However how can I do this from inside the GenericController (call a method in a sub class from parent class)? Because otherwise I will have to create routes for every single model which is against my wish.
So basically I am looking for something like this:
class GenericController extends Controller
{
public function index($modelName)
{
$x = '\\App\\Models\\'.$modelName;
//this thing I'm looking for is something like this:
//Check if we have CategoryController and it has a definition for index
//if yes do something like $data = CategoryController->index();
//otherwise just call $data = $model->all();
return view('generic.list', ['model'=>$model, 'data'=>$data]);
}
}
So I know this seems weird and anti-pattern, but other wise how can I create my generic routes and controller actions?
You are right, this is not really what is called "best practice". However, from a POO standpoint, it is an interesting question.
This what you can do:
class GenericController extends Controller
{
protected function getData(string $model)
{
return $model::all();
}
public function index($modelName)
{
$model = '\\App\\Models\\'.$modelName;
$data = $this->getData($model);
return view('generic.list', ['model'=>$model, 'data'=>$data]);
}
}
By default, the data will be retrieved "the simple way", using $data = $this->getData($model);.
However, if you make a CategoryController:
class CategoryController extends GenericController
{
protected function getData(string $model)
{
return Category::query()->with('something')->where('hello','world')->get();
}
}
You will just have to override the getData method inside your CategoryController.
This is the way to go if you want something clean. Of course, your categories routes will have to use this CategoryController instead of the GenericController.

Laravel - Return related models of parameter in controller

I have the following route:
Route::get('/api/products/{product}', 'ProductController#get');
My Product model looks like this:
class Product extends Model
{
public function ingredients()
{
return $this->belongsToMany(Ingredient::class)->withPivot('value');
}
}
In my controller, the method is:
public function get(Product $product)
{
return $product;
}
This only returns the attributes of the Product object as a JSON. I would also like to return the related ingredients and pivot table values (as it would with the with method), and possibly other related models.
return $product->with('ingredients') creates a collection of all Products, so that doesn't really work, I have to filter it again by the product ID. I can obviously construct the JSON myself, but that becomes tedious if I want multiple related models included. Is there an easy way to accomplish this?
You have three options:
Using $with in model
class Product extends Model
{
protected $with = ['ingredients'];
public function ingredients()
{
return $this->belongsToMany(Ingredient::class)->withPivot('value');
}
}
Load the relation and return product:
public function get(Product $product)
{
$product->ingredients;
return $product;
}
Use the load method on the product:
public function get(Product $product)
{
return $product->load('ingredients');
}

Setting getKeyRouteName dependant on route (web or api)

Tried looking for the answer to this everywhere but having no luck so far...
Basically I want my web route to use a slug for its URL, but I want to use ID for the API route. So...
http://myurl.com/chapter/my-chapter-slug
and
http://myurl.com/api/chapter/1234
Have tried various combinations of things in the getRouteKeyName method (if(Request::route()->named('myapiroute'), if(Request::isJson() etc...) but I think these might be being checked against the page it's running on, rather than the route I'm trying to generate?
I'm thinking maybe I need to extend the base model to have a separate one to use with my API maybe?
So I'd have...
class Chapter extends Model
{
public function getRouteKeyName()
{
return 'slug';
}
....
}
and then...
class ApiChapter extends Chapter
{
public function getRouteKeyName()
{
return 'id';
}
....
}
But not sure how I'd structure this in the most "Laravel" way? Or is there a better/tidier solution?
define your route for example like
Route::get('chapters/{chapter}','ChapterController#show'); // find by slug
Route::get('api/chapters/{chapter}','ApiChapterController#show'); // find by id
for web controller
class ChapterController extends Controller
{
public function show(Request $request,$slug)
{
$instance = Model::whereSlug($slug)->first();
}
}
for api
class ApiChapterController extends Controller
{
public function show(Request $request,$id)
{
$instance = Model::find($id);
}
}
You can define 2 different routes for that but unfortunatelly you will not be able to use model binding and you will have to look for the model like:
public function show(Request $request,$slug) {
$instance = Model::whereSlug($slug)->first();
}
as shown below: https://stackoverflow.com/a/48115385/6525417

call the another function within a function

In laravel I want to call a function within function to make recursive.I caught the route error.how to call the function 'recursive in tableFetch'
class queryTest extends Controller
{
public function tableFetch() {
recursive();
}
function recursive(){
//condition
}
}
I want to do it for check the manager of the given person and then get the manager of the fetched value in query so need to do it recursive
A controller is not a good place for this. Instead, manage it in your Person Model(or whatever you have).
Everyone has a manager. So, your model has HasOne relation to itself.
Person Model:
public function manager()
{
return $this->hasOne(Person::class, 'manager_id');
}
Now if you need to check the manager of given person untill you meet a certain condition you can do it inside the model and get the result in the controller.
public function checkManager()
{
$manager = $this->manager
if (check manager)
return $manager;
//check for the last manager
return $this->manager ? $this->checkManager() : null;
}
Inside controller
function index()
{
$person = Person::find($id);
$manager = $person->checkManager();// this will do the recursive you need
}
Do something like this
class queryTest extends Controller
{
public function tableFetch() {
$this->recursive();
}
function recursive(){
//condition
}
}
you need to ask more precise details about your needs, because Laravel has some complications.
try doing this :
class queryTest extends Controller
{
public function tableFetch() {
$this->recursive();
}
public function recursive() {
//condition
}
}

Laravel 4 - Model properties' names different than database columns

I have one question, that seems to be logical, but I can't find answer for it.
Let's say I have Model Task:
class Task extends Eloquent {
protected $fillable = array('is_done');
}
So, I have one property is_done, but when working on frontend and backend part of application, I would like to have isDone as model property.
Is there a way to say it to framework, to somehow repack it for me? So that I am able to use isDone, throughout application, and that Model takes care of converting it to is_done, when it comes to saving/updating.
This would help me, so I don't have to think about names specified in database (like when using alias in traditional SQL clauses).
Is this possible at all? Does it make sense?
To prevent writing a getter/setter methods for every single attribute of the model, you can override the magic methods from the Eloquent class to access them in camelCase style:
class Model extends Eloquent {
public function __get($key)
{
$snake_key = snake_case($key);
return parent::__get($snake_key);
}
public function __set($key, $value)
{
$snake_key = snake_case($key);
parent::__set($snake_key, $value);
}
public function __isset($key)
{
$snake_key = snake_case($key);
return parent::__isset($snake_key);
}
public function __unset($key)
{
$snake_key = snake_case($key);
parent::__unset($snake_key);
}
}
Would a getter method for your attribute help you? If yes:
<?php
class Task extends Eloquent {
public function isDone()
{
return $this->getAttribute('is_done');
}
}
If not, and you really need to access $Task->isDone: try to overwrite the $key in magic _get() method for $key == 'isDone' (and maybe other attributes) and return the parent::_get() with $key:
<?php
class Task extends Eloquent {
public function __get($key)
{
if($key == 'isDone')
$key = 'is_done';
return parent::__get($key);
}
}
And perhaps, your Eloquent needs an attribute mapper for the attribute magic methods ;)

Resources