Make Hidden child's relation /Laravel Eloquent - laravel

Model structure:
AccessoryGroup (hasMany: accessories)
Accessory (belongsTo: accessory_group)
Get all accessory groups with accessories (with accesory_group)
In accessories I needed accessory_group relation, to create some custom attribute (appends)
But after usage I don't won't my Api to return relation from accessories->accessory_group
AccessoryGroup
::with([
'accessories' => function ($query) {
$accessory = $query->getRelated();
// Can I somehow use this $accessory relation for my problem
// something like $accessory->makeHidden('accessory_group'); - not working
},
'accessories.accessory_group',
])
->get();
when i add public $hidden = ['accesory_group']; in Accessory model I get what i want, but then it's always hidden (always need to use makeVisible)

From your question what I get is you don't want to return the relation in return to your API response. I'm not prefer to load the data from relation again if you have that in parent model so suggest you do that in controller part to build your business logic.
But specific to your current problem you can do like this
$accessoryGroups = AccessoryGroup::query()
->with([
'accessories',
'accessories.accessory_group',
])
->get();
// do your work here
// unset the relationship after your work complete
$accessoryGroups->each(function($accessoryGroup) {
$accessoryGroup->accessories->each(function($category) {
$category->unsetRelation('accessory_group');
});
});
// return api response

Related

Laravel eloquent for four tables

I'm new to Laravel. I am developing a project. and in this project I have 4 tables related to each other
-Users
-Orders
-OrderParcels
-Situations
When listing the parcels of an order, I want to get the information of that order only once, the user information of that order once again, and list the parcels as a table under it. so far everything ok. but I also want to display the status of the parcels listed in the table as names. I couldn't add the 4th table to the query. do you have a suggestion? I'm putting pictures that explain the structure below.
My current working code is
$orderParcels = Orders::whereId($id)
->with('parcels')
->with('users:id,name')
->first();
and my 'orders' model has method
public function parcels(){
return $this->hasMany(OrderParcels::class);
}
public function users(){
return $this->hasOne(User::class,'id','affixer_id');
}
Note[edit]: I already know how to connect like this
$orderParcels = DB::table('order_parcels as op')
->leftjoin('orders as o','op.orders_id','o.id')
->leftjoin('users as u','o.affixer_id','u.id')
->leftjoin('situations as s','op.status','s.id')
->select('op.*','o.*','u.name','s.situations_name')
->where('op.orders_id',$id)->get();
but this is not working for me, for each parcels record it returns me orders and user info. I want once orders info and once user info.
Laravel provides an elegant way to manage relations between models. In your situation, the first step is to create all relations described in your schema :
1. Model Order
class User extends Model {
public function parcels()
{
return $this->hasMany(OrderParcels::class);
}
public function users()
{
return $this->hasOne(User::class,'id','affixer_id');
}
}
2. Model Parcel
class Parcel extends Model {
public function situations()
{
return $this->hasOne(Situation::class, ...);
}
}
Then, you can retrieve all desired informations simply like this :
// Retrieve all users of an order
$users = $order->users; // You get a Collection of User instances
// Retrieve all parcels of an order
$parcels = $order->parcels; // You get a Collection of User instances
// Retrieve the situation for a parcel
$situations = $parcel->situations // You get Situation instance
How it works ?
When you add a relation on your model, you can retrieve the result of this relation by using the property with the same name of the method. Laravel will automatically provide you those properties ! (e.g: parcels() method in your Order Model will generate $order->parcels property.
To finish, in this situation where you have nested relations (as describe in your schema), you should use with() method of your model to eager load all the nested relation of order model like this :
$orders = Orders::with(['users', 'parcels', 'parcels.situations'])->find($id)
I encourage you to read those stubs of Laravel documentation :
Define model relations
Eager loading
Laravel Collection
Good luck !
Use join to make a perfect relations between tables.
$output = Orders::join('users', 'users.id', '=', 'orders.user_id')
->join('order_parcels', 'order_parcels.id', '=', 'orders.parcel_id')
->join('situations', 'situation.id', '=', 'order_parcels.situation_id')
->select([
'orders.id AS order_id',
'users.id AS user_id',
'order.parcels.id AS parcel_id',
'and so on'
])
->where('some row', '=', 'some row or variable')->get();

how to define methods for a model that has a 1: 1 self relationship

I have this relational table on my db:
id, is referenced to: "attivitaSost" (and attivitaSpostata).
The relathionship is "optional" so the foreignkey is nullable.
But since the problem is the same, I will try to solve the first relationship first.
My model "cciActivities" have this 2 methods:
public function attOrig()
{
return $this->hasOne(CcieActivity::class,'id', 'attivitaSost');
}
public function attSpost(){
return $this->belongsTo(CcieActivity::class,'attivitaSost','id');
If I set the inverse:
public function attOrig()
{
return $this->hasOne(CcieActivity::class,'attivitaSost','id');
}
not works, and goes in a infinite loop thats goes in 500.
are well written? who needs to carry the foreign key? the children or the parent? there is a standard or I make work as was thinking:
save the new model,
pick up the id,
save it on the parent model,
The code:
$ccieActPadre= CcieActivity::where('id',$ccieActivityId)->first();
$ccieActivityNew = CcieActivity::create($data);
$ccieActPadre -> attivitaSost = $ccieActivityNew->id;
$ccieActPadre->save();
I am asking this, because when i try to apply methods filters like
$ccieActivities = CcieActivity::doesntHave('attOrig')
->get();
are returned not what i am expected.
When I am trying to render the resource activities, im using an api Resource like:
return [
'id' => $this->id,
'project' =>new ProjectResource($this->project) , //id, nomeEnte, name, email, ruolo
'catAttivita' => $this->catAttivita,
'nomeAttivita' => $this->nomeAttivita,
'descrizione' => $this->descrizione,
'dataInizioPrevista' => $this->dataInizioPrevista,
'dataFinePrevista'=> $this->dataFinePrevista,
'numNegoziAderentiPrevisti'=> $this->numNegoziAderentiPrevisti,
'numAziendeCoinvoltePreviste'=> $this->numAziendeCoinvoltePreviste,
'numInfluencerPartecipantiPrevisti'=> $this->numInfluencerPartecipantiPrevisti,
'numBuyerPrevistiB2B'=> $this->numBuyerPrevistiB2B,
'budgetTotalePrevisto'=> $this->budgetTotalePrevisto,
'modalitaRealizzazionePrevista'=> $this->modalitaRealizzazionePrevista,
'attivitaSpostata' => new CcieActivityResource($this->attOrigSpost),
'attivitaSostituitaaaaa' => new CcieActivityResource($this->attOrig),
];
this part
'attivitaSostituita' => new CcieActivityResource($this->attOrig),
never works! whatever method I apply!
So I need to understand which is the right convention to menage a 1:1 optional self relationship over a laravel model, thanks.
The second parameters for hasOne and belongsTo are not the same.
belongsTo is for the related model and hasOne is for the local model
$this->hasOne(Phone::class, 'foreign_key', 'local_key');
$this->belongsTo(User::class, 'foreign_key', 'owner_key');
In your case, the hasOne has the wrong parameters. change it to
public function attOrig()
{
return $this->hasOne(CcieActivity::class, 'attivitaSost', 'id');
}
EDIT:
Never eager load by default the parent in the child model and the child in the parent model even if they are seperate Classes. It will lead to an infinite loop.

Best way to query model relationship in Eloquent

I am working within a controller in a Laravel application. I am returning a table to the view. The table is based on my PlanSubmission model. I am receiving parameters through a GET request and using those parameters to return a filtered set of rows to my view.
The first part of my controller looks like this and is working fine:
public function index()
{
//Used for filter. The request is received in the URL
if (request()->has('status')) {
$plans = PlanSubmission::where('status', request('status'))->paginate(25)->appends('status', request('status'));
}
elseif (request('employer_name')) {
$plans = PlanSubmission::where('employer_name', request('employer_name'))->paginate(25)->appends('employer_name', request('employer_name'));
}
I have run into a problem because now I need to use a model relationship in the controller. I am receiving 'advisor_name' from the request. The 'advisor_id" column is the foreign key on the PlanSubmission model. The 'advisor_name' column exists in the Advisor model. I have a function on my PlanSubmission model that looks like this:
public function advisor()
{
return $this->belongsTo(Advisor::class);
}
Initially, I thought there was a way I could do this easily with something like:
$plans = PlanSubmission::where(advisor->name, request('advisor_name'))->paginate(25)->appends('advisor_name', request('advisor_name'));
Of course, this will not work because I cannot enter a relationship into the first parameter in the Where Clause.
I do not know where to go from here. My other thought is to return all the advisors first from the Advisor model like this:
$advisors = Advisor::where('name', request('advisor_name'));
Then, I imagine I would have to somehow loop through that and get the id (primary key) for each of the objects in $advisors and somehow get that into the PlanSubmission where clause. I'm totally lost.
Like Victor mentions in his answer you can use whereHas like so:
PlanSubmission::whereHas('advisor', function ($query) {
$query->where('name', request('advisor_name'));
});
You didn't asked this directly, but I noticed that you use conditionals to make different queries. Eloquent provides a few way to make this a bit nicer to deal with.
The first which is kind of obvious is that that whatever method you call a builder (query) is returned that you can just add on to. It could be there were some common restrictions in your two cases:
public function index()
{
$query = PlanSubmission::where('something', 42);
if (request()->has('status')) {
$query = $query->where('status', request('status'));
} elseif (..) {
...
}
return $query->paginate(25);
}
Another way to do conditional queries in Laravel is using when. E.g. for status:
$query = $query->when(request->has('status'), function ($query) {
// note that you don't have to return the query
$query->where('status', request('status'));
});
// or PlanSubmission::>when(..)
In your example you cannot both filter by status AND advisor_name, but lets assume that would be okay, then you can combine everything like so:
public function index()
{
return PlanSubmission::query()
//->where('something', 42)
->when(request->has('status'), function ($query) {
$query->where('status', request('status'));
})
->when(request->has('advisor_name'), function ($query) {
$query->whereHas('advisor', function ($query) {
$query->where('name', request('advisor_name'));
});
})->paginate(25);
}
This approach may seem verbose for simple queries and then it is fine to use if conditions, but for complex queries when can be useful. Also the idea of "building up a query" also works nice in those situation. You can pass the query builder around and continuously build it up.
You can use whereHas for that
docs

Filter many to many relationship based on child existence and column value

I've been searching for a while and couldn't find an answer, here's what I have:
1- ShowCategory (id & title)
class ShowCategory extends Model
{
public function shows()
{
return $this->belongsToMany(Show::class, 'category_show');
}
}
2- Show (id, title & active)
class Show extends Model
{
public function categories()
{
return $this->belongsToMany(ShowCategory::class, 'category_show');
}
}
So there's a many to many relationship, what I need is retrieving all ShowCategory elements that has at least one Show related to it, and to filter each ShowCategory->shows by show.active, only return shows that are active
Here's what I'm trying to do:
$categories = ShowCategory::whereHas('shows', function($query) {
$query->where('shows.active', '=', true);
})->get();
It only filters ShowCategory that includes shows and if only one of those shows are active, it returns the category with all shows inside, even if others are not active, I need to filter those who are not active.
What should I do? Thanks in advance
This requires a combination of whereHas() and with(). First, whereHas() will filter the ShowCategory model to those that have an active Show, while the with() clause will limit the results of the relationship to only return active ones:
$categories = ShowCategory::whereHas("shows", function($subQuery) {
$subQuery->where("shows.active", "=", true); // See note
})->with(["shows" => function($subQuery){
$subQuery->where("shows.active", "=", true);
}])->get();'
Note: You should be able to use active instead of shows.active, but depends on if that column is on multiple tables.
Using this query, you will get a Collection of ShowCategory models, each with their active Show models already loaded and available via ->shows:
foreach($categories AS $category){
dd($category->shows); // List of `active` Shows
}
This is what you need.
$categories = ShowCategory::whereHas('shows', function($query) {
$query->whereActive(true);
})->get();
Try, this can be a possible way to retreive related results.
// This will only return ShowCategory which will have active shows.
/* 1: */ \ShowCategory::has('shows.active')->get();
// So, logically this will only have active shows -__-
$showCategory->shows
Laravel allows to extends foreign relation by using this . notation as a condition for retreival.
Update
You should update the \ShowCategory model as
public function shows(){
return $this->belongsToMany(Show::class, 'category_show')->where('active', true);
}

How to remove fields from Laravel JSON Api response

I have an API returning an object with one-to-one relation to another object. As the models behind the objects, do have timestamps, these are also delivered when asking API.
// Get all transactions
Route::get('transaction', function() {
return Transaction::with('Personone','Persontwo')->get();
});
How do I prevent Laravel from returning the timestamps of the objects in the API?
I googled, but only found some hints to middleware or response macros but found no example pointing me into the right direction. Maybe you can help me.
You can make attributes "hidden" so that they do not show up in json.
docs
class Transaction extends Model
{
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = ['timestamp'];
}
I'm not sure if I get the question correctly but if you want to select from eager loads there are two ways
first one is inline selecting
Route::get('transaction', function () {
return Transaction::with('Personone:id,foo,bar', 'Persontwo:id,foo,bar,foobar')->get();
});
second one is to pass a closure
Route::get('transaction', function () {
return Transaction::with([
'Personone' => function ($query) {
$query->select('id', 'foo', 'bar');
},
'Persontwo' => function ($query) {
$query->select('id', 'foo', 'bar', 'foobar');
}])->get();
});
Eager Loading Specific Columns You may not always need every column
from the relationships you are retrieving. For this reason, Eloquent
allows you to specify which columns of the relationship you would like
to retrieve:
$users = App\Book::with('author:id,name')->get();
Constraining Eager Loads Sometimes you may wish to eager load a
relationship, but also specify additional query constraints for the
eager loading query. Here's an example:
$users = App\User::with(['posts' => function ($query) {
$query->where('title', 'like', '%first%'); }])->get(); In this example, Eloquent will only eager load posts where the post's title
column contains the word first. Of course, you may call other query
builder methods to further customize the eager loading operation:
$users = App\User::with(['posts' => function ($query) {
$query->orderBy('created_at', 'desc'); }])->get();

Resources