Getting specific columns from Laravel Eloquent - laravel

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
}

Related

Nested Relation 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

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.

Laravel - How to change response format for specific fields

I've "Product" model.
And need to change some value formats for only responses.
For example;
I've "price" on database as decimal (11,2).
I want this as "1.000.000,00" format on response.
Or created_at field to "Carbon::parse($this->created_at)->toDayDatetimeString()"
Or I want to add 3 specific columns with my user attribute, on response. (is_allowed etc.)
How can this be possible on model?
How can I response like that?
You can use Mutator and Accessor to set format :
https://laravel.com/docs/8.x/eloquent-mutators#accessors-and-mutators
public function setDateAttribute($date) {
$this->attributes['date'] = Carbon::createFromFormat('Y-m-d', $date);
}
public function getFirstNameAttribute($value)
{
return ucfirst($value);
}
As a best practice in Laravel you can use Eloquent Resources: Eloquent Resources
It's basically a "transformer" between models data and API/Responses Output.
The only one thing to notice is that in the Resource files yout must specify all fields and relations (if needed) of the Model manually.
In the toArray() function you can modify the type of all data of your model as you prefer.
If not, you can access the new field by $model->my_custom_field (Laravel can resolve the name of the getter function automatically).
public function toArray($request)
{
$editedFieldValue = doSomething();
return [
'my_field' => $editedFieldValue,
'other_field' => '',
];
}
If you want to do that in Model, you can create customs fields:
class MuModel extends Model
{
protected $appends = ['my_custom_field'];
public function getMyCustomFiledAttribute(){
$newData = doSomething($this->existent_field);
return $newData;
}
}
The $appends variable add the new fields to all responses generated from the Model, as a normal database field.
P.S.: You can create a getAttribute() function for existent database attribute and return the value as you want!
For example: getCreatedAtAttribute()

How to retrieve full relationships from an item that has a relationship with itself?

I have comments and replies to those comments and I want to bring the full relationships. I want to bring all the comment relationships when calling the replies. It's kinda confusing so let me show that I mean with actual data.
This is the model
class ForumReply extends Model
{
protected $fillable = [
'user_id',
'bulletin_id',
'comment',
'anonymous'
];
public $timestamps = true;
public function discussionForum()
{
return $this->belongsTo(DiscussionForum::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
public function replies()
{
return $this->hasMany(ForumReply::class, 'parent_id');
}
}
This is the function in the controller
public function show($id)
{
$forum = DiscussionForum::findOrFail($id)
->with('comments', 'user')->first();
$comment = ForumReply::with('discussionForum', 'user', 'replies')
->where('discussion_forum_id', $forum->id)
->orderByDesc('comment_time')->get();
return Inertia::render('Forum/View.vue', [
'forum' => $forum,
'comments' => $comment
]);
}
This is the data I get in the vue for the $comment query, as you can see the replies part has very little information, in order to display it as I want I need more information, like the user info for the person who replied not just the person with the parent comment.
{
"id":4,
"user_id":41,
"discussion_forum_id":1,
"parent_id":null,
"comment":"adsfasdf asdf asdfa sdf ",
"comment_time":"2019-12-03 11:25:00",
"created_at":null,
"updated_at":null,
"discussion_forum":{
"id":1,
"theme":"asdfa",
"description":"asdffghjluui",
"user_id":1,
"anonymous":0,
"start_date":"2019-11-24 06:00:00",
"end_date":"2019-12-30 06:00:00",
"created_at":"2019-11-27 13:21:03",
"updated_at":"2019-11-27 13:21:03"
},
"user":{
"id":41,
"name":"Isadora Felix",
"card":"12346",
"scard":"97531",
"user_type_id":4,
"email":"i#email.com",
"created_at":"2019-12-03 10:09:26",
"updated_at":"2019-12-03 10:09:26"
},
"replies":[
{
"id":6,
"user_id":39,
"discussion_forum_id":1,
"parent_id":4,
"comment":"not agreeing",
"comment_time":"2019-12-03 11:26:01",
"created_at":null,
"updated_at":null
}
]
}
Is there another way to bring this information in a way where I can get the information on the reply just as if it was a parent comment? Or is there a better way to approach this?
You can load nested relationships with the dot notation.
For example:
$forum = DiscussionForum::with(['user', 'comment' => function ($query) {
return $query->orderByDesc('comment_time');
}, 'comment.user', 'comments.replies.user'])->findOrFail($id);
This code will load the discussion forum with the user and comments with their authors. Then for each comment it also retrives its replies with their respective authors.
Side note: Keep in mind that findOrFail($id) actually executes a query. If you then call ->with(...)->first() you are performing a second query on the database.
You can instead start with ::with(...) method and then call ->findOrFail($id).
If you know you will never want to access the ForumReply without it's user you can add the user to the protected "with" attribute so it will always be appended with every model of this type
class ForumReply extends Model
{
protected $with = ['user'];
}
This will save you the code to including the user and will also help with the next part
Unless you limit ForumReplies to only have a single reply to them, you will have to consider nesting level on the relationship. However there is an easy fix for this, make your relationship recursive.
public function replies()
{
return $this->hasMany(ForumReply::class, 'parent_id')->with('replies');
}
The combination of those two changes will load all replies to a reply, even if they are replying to another reply, and attach the user to each one.

Resources