Nested Relation Laravel - laravel

class Airport extends Model{
public function flights(){
return $this->hasMany('App\Flights', 'flight_id', 'id');
}
}
class FlightInfo extends Model{
public function status(){
return $this->hasMany('App\FlightStatus', 'flight_info_id', 'id');
}
}
class FlightStatus extends Model{
public function flight_info(){
return $this->belongsTo(FlightInfo::class);
}
}
I'm trying to get all flights and its last status from one airport
example:
Airport::where('country_id', 10)->with(['flights.status' => function($q){$q->orderBy('last_update')->limit(1);}])
this give empty array for status collection
i've also tried
Airport::where('country_id', 10)->whereHas('flights.status', function ($q){$q->orderBy('last_update')->limit(1);})->with(['flights.status' => function($q){$q->orderBy('last_update')->limit(1);}])->get()
but same, empty array on status collection
I would like to retrieve this kind of result:
[
Airport_id: 2,
flights:[
id: 10,
airport_id: 2,
status: [ status_id: 1]
],
[ id: 15,
airport_id: 2,
status: [ status_id: 3]
],
any idea ?

If you want to do it via SQL you should use Joins. You have three different tables, so you must join them together and then you can apply the where conditions to reduce the data.
See related documentation: https://laravel.com/docs/9.x/queries#joins
If you have a small amount of data you could do it programmatically. (It's a dirty solution but may be enough for the given task).
You can get your Airport as a collection and the apply filter to remove unnecessary data.
See: https://laravel.com/docs/9.x/collections#method-filter
Use eager loading in this case to reduce the amount of db queries:
See: https://laravel.com/docs/9.x/eloquent-relationships#eager-loading

Related

Laravel 8 - I have an error in when using method in Model when using paratheses with the method

I have two models [The relation is one-many / Category-Trip]:
Category Model Code:
class Category extends Model
{
use HasFactory;
protected $fillable = [
'id',
'category_name',
'category_desc',
'category_img',
];
public function trip() {
return $this->hasMany(Trip::class);
}
}
Trip Model Code:
class Trip extends Model
{
use HasFactory;
protected $fillable = [
'name',
'description',
'max_visitors',
'price',
'date',
'image',
'guide_id',
'category_id',
];
public function category()
{
return $this->belongsTo(Category::class);
}
}
when I use this code, I will get this error:
TypeError Symfony\Component\HttpFoundation\Response::setContent():
Argument #1 ($content) must be of type ?string,
Illuminate\Database\Eloquent\Relations\BelongsTo given, called in
C:\Users\Orange\Desktop\ON
GITHUB\Tours-Booking\vendor\laravel\framework\src\Illuminate\Http\Response.php
on line 72
$trip = Trip::findOrFail(1);
return $trip->category();
But when write this without paratheses, i will not get this error, what is the problem?
$trip = Trip::findOrFail(1);
return $trip->category;
As a user explained (but not fully):
You are returning a relationship in your controller, but that is not valid (based on the error). Because BelongsTo cannot be serialized to a string, it tries to return the BelongsTo object, hence giving you that error (look, it is saying it is wanting null or a string, but you are returning BelongsTo).
When you do $model->relation (without ()), that means it will try to get all the data that satisfies the relation and store it as a Category in $model->relation (because it is a BelongsTo), but when you use $model->relation() you creating a query so you can query the relation with whatever you want/need, like $model->relation()->where('status', 'Active')->get()...
You have 2 solutions: either return $trip->category (it is going to be a Category object/model) or $trip->category()->get() or any query but use ->get() (will return a Collection) or ->first() (will return a Category model) at the end...
Read the documentation again so you understand better now: Relationships and First vs Get and BelongsTo documentation.
When you call the method its instantiating a eloquent instance in which you can extend it to a query which will give you a Category model
example (this might not be the most appropriate example)
$trip = Trip::findOrFail(1);
return $trip->category()->where('category_name', 'something')->first();
And the latter returns a collection.

Eloquent Api Resource: show one column from pivot table

I have a vehicle database with a many to many relation with my variant and country table. I only want to show the title that's in my pivot (countries_variants) table. But when I set the relation in my api resource file, it shows all the columns from variants, countries_variants and countries table. Is there a way to only get the title from the pivot table?
Here is my Variant Model:
public function countries(): BelongsToMany
{
return $this->belongsToMany(Country::class, 'countries_variants')->withPivot('title');
}
VariantResource.php
public function toArray($request)
{
return [
'title' => $this->countries->first->title,
];
}
VariantController.php
public function index()
{
return new VariantCollection(Variant::paginate(50));
}
The output I'm getting is:
{
"data": [
{
"title": {
"id": 1,
"title": "Nederland",
"country_code": "NL",
"language_code": "NL_nl",
"pivot": {
"variant_id": 1,
"country_id": 1,
"title": "3/5 deurs"
}
}
}
]
}
I just want to show "title": "3/5 deurs" and not the other data.
I thought that if I set withPivot('title') in my model, it will only show that title and not the foreign keys (variant_id and country_id). Apparently thought wrong..
I tried adding this as well:
'variant_title' => $this->whenPivotLoaded('countries_variants', function () {
return $this->pivot->title;
}),
But the data then returns empty.
Any help would be very much appreciated :)
Try changing
$this->countries->first->title
To
$this->countries->first->title->pivot->title
When you do withPivot('title') you are telling Eloquent to also get title column, if you do not do that, you will only get the keys (variant_id and country_id in your case).
More info here.
Okay so I finally figured it out. I looked through the official laravel docs again and I found this.
So I changed my CountryVariant model class to extend Pivot instead of Model.
Like so:
use Illuminate\Database\Eloquent\Relations\Pivot;
class CountryVariant extends Pivot {
public function variant(): BelongsTo
{
return $this->belongsTo(Variant::class);
}
}
Now in my VariantResource.php I added this:
public function toArray($request): array
{
$items = [];
foreach ($this->countries()->get() as $country) {
$items[$country->language_code] = $country->pivot->only('title');
}
return $items;
}
I also changed the pivot table names to be singular like: country_variant (I had it plural).
Apparently the problem was that in my model it was looking for a primary key which I didn't have in my pivot table. I only had foreign keys and an additional column: 'title'. It took one of my foreign keys and used it as a primary one. So when I extended with extends pivot it would ignore the primary key and I could collect my alternate column 'title' by using country->pivot->only('title') in my resource file.
I hope this helps some one else.

Attach to all possible relations from many-to-many method on create?

I have two Model named Position and Event, they have many-to-many relationship with each other.
Event:
<?php
class Event extends Model
{
protected $fillable = [
'name', 'opta_id'
];
public function positions() {
return $this->belongsToMany(Position::class);
}
}
Position:
class Position extends Model
{
protected $fillable = [
'name', 'short_name'
];
public function events() {
return $this->belongsToMany(Event::class);
}
}
Each event should have a pivot entry for each position currently in the database, there are no exceptions. So every time a user creates a new event, I want to create a pivot entry for each existing position.
I am struggling to figure this out using the documentation and SO. I could use sync() or attach() to make the connections by explicitly naming the IDs of all the positions in the db. In the EventController's store method:
$data = $request->all();
$event = Event::create($data);
$event->positions()->sync([1, 22, 34, 167]);
But for this to work, I would first have to get all the entries from the positions table, format them into an array of IDs, and pass them to this method. Is there any built-in or canonical way to do this?
There is no built-in way, but the manual solution is quite short:
$event->positions()->attach(Position::pluck('id'));
attach() is more efficient than sync() because it inserts all pivot records in a single query.
I've gotten the other solution, So to may achieve your purpose you have
// the 1st sync will remove all the relationship to the position table
$event->positions()->sync([1, 22, 34, 167]);
// the 2nd sync is to extend the relationship from the 1st
$event->positions()->syncWithoutDetaching([1, 22, 34, 167]);

Paginate with Eloquent but without instantiation Models

We have two Models:
SimpleModel (id, country, code)
ComplexRelatedModel (id, name, address)
SimpleModel has many ComplexRelatedModel, then
class Product extends Model
{
protected $fillable = [
'name'
];
/* hasOne */
public function complexRelatedChild()
{
return $this->hasOne(self::class, 'parent_id', 'id');
}
}
If we do
$simples = SimpleModel
->with('complexRelatedChild')
->simplePaginate(100000 /* a lot! */);
And we need only do
foreach ($simples as $simple) {
echo $simple->complexRelatedChild->name;
}
Any ComplexChild has hydratated and ready. This takes a lot of memory in my case. And we need just one field without any funciton or feature of Model.
It's possible use some data field from related object or with eloquent this isn't possible?
Not sure I completely understand your question. You want to only load one field from the complexRelatedChild relation to keep memory limit down?
You could do:
$simples = SimpleModel::with(['complexRelatedChild' => function($query){
return $query->select(['id', 'name']);
})
->simplePaginate(100000);
Which can be simplified to:
$simples = SimpleModel::with('complexRelatedChild:id,name')
->simplePaginate(100000);
However if I were you, I would try to paginate less items than 100000.
Update:
You could use chunk or cursor functions to process small batches of SimpleModel and keep memory limit down.
SimpleModel::chunk(200, function ($simples) {
foreach ($simples as $simple) {
}
});
or
foreach (SimpleModel::cursor() as $simple) {
}
See the documentation for more information

Getting specific columns from Laravel Eloquent

I've been beating myself up with this and can't seem to be able to get my JSON to only display the title and description of the rooms should they be live. I have tried using return $this->hasMany('Room')->select(array('id', 'title', 'description')); but it returns nothing at all for rooms.
Can anyone help? I'm sure this must be very simple to achieve as I understood Laravel Eloquent to be simplistic for this kind of task.
JSON
[
{
id: 1,
building_title: "Drum Castle"
room: [
{
id: 1,
building_id: 7,
title: "Snooker Room",
description: "Full Description",
live: 1
}
]
}
]
Building Model
public function room()
{
return $this->hasMany('Room');
}
Room Model
public function building()
{
return $this->belongsTo('Building', 'building_id');
}
Controller
$buildings = Building::with('room')->get();
Your attempt at restricting the fields on the relationship failed because you didn't select the building_id field. If you don't get this field, Laravel has no idea on which rooms belong to which buildings (think about eager loaded relationships).
One method to do what you want is to override the toArray() method (which is called by toJson()), on either the Building or the Room.
Another option is to set the hidden/visible attributes on your Room model. The attributes will govern which fields are hidden/visible when converting to an array.
class Room extends Eloquent {
protected $hidden = array('building_id', 'live');
// or
protected $visible = array('id', 'title', 'description');
// rest of class
}

Resources