Laravel ->with(") is there a way to use it with attributes? - php

I've been using ->("with) for relationships when querying models like so...
return Model::
where('set_id', $request->set)
->without('set')
->with('user:id,first_name,last_name,profile_photo')
->get();
Which works great for a relationship:
public function user(){
return $this->belongsTo(User::class, 'user_id');
}
But what about doing the same with an attribute?
public function getPermissionsAttribute(){
$permissions = $this->permissions;
$new = [];
foreach($permissions as $p){
$new[$p->permission] = true;
}
return json_encode($new);
}
Is there a way to do this in laravel? As at the moment I'm getting the following error...
App\Models\User::permissions must return a relationship instance. (View: C:\Users\Nick\PhpstormProjects\laravel-vue\resources\views\index.blade.php)
I did have it in:
public $appends = [
'full_name',
'profile_photo_thumb',
'permissions'
......... etc ............
];
But, the list was getting longer and I was sending out data to the front end that largely wasn't used, so was causing longer loading

You can append accessor at Run Time to help you decide what data you want serialized in the response by calling append on your Eloquent Collection:
return $collection->append('attribute');
Or for multiple, you can pass an array:
return $collection->append(['attribute1', 'attribute2']);
Laravel 8.x Docs - Eloquent - Serialization - Append Values to JSON - Appending at Run Time append

Try to get it by select function like this
return Model::where('set_id', $request->set)->with(['user' => function ($query) {
$query->select('id','first_name','last_name','profile_photo');
}])

Related

Filtering Laravel Eloquent call by something in the with clause

I'm building an app based off of the Laravel bootcamp app and I'm trying to add additional filtering based off of the with statement.
Some probably obvious things:
each user has a city
each call has a user referenced by user_id
public function index()
{
$user = auth()->user();
$city = $user->city;
return Inertia::render('Calls/Index', [
'calls' => Call::with('user:id,name,city,state,email')->latest()->get(),
]);
}
I need to only get calls whose user has the same location as them. Is there something obvious I'm missing?
When retrieving model records, you may wish to limit your results based on the existence of a relationship, there is where you can use whereHas
I presume you have a relation in your Interia Model refer to User model:
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
now you can make you code like this:
return Inertia::render('Calls/Index', [
'calls' => Call::with('user:id,name,city,state,email')->whereHas('user',function ($q) use ($city) {
$q->where('city',$city);
})->latest()->get(),
]);

After create - why does it not return relationship?

When creating an entry using create() - Why does it return a relationship of pilot_id table instead of just showing the value of pilot_id?
For example, in the repository class:
public function createFlight()
$flight = App\Flight::create([
'name' => 'Flight 10'
'pilot_id' => 4
]);
return $flight;
}
In the controller:
public function show()
{
$data = $this->flight->createFlight();
return $data
}
It would return json on the screen but it is not showing the relationship table (pilot) of pilot_id.
Try adding the following to your Flight model, like so. By default you need to tell Laravel to include any relationships.
protected $with = ['pilot'];
That will make it so everytime it includes the relationship. If this is not desirable, then you will want to load the relationships when you return the flight, like so.
return $flight->load(['pilot']);
It shows pilot_id is 4 because that's what its value is. Did you create a relationship on the Flight so that Laravel knows how to retrieve the model for Pilot? It should look something like this:
public function pilot()
{
return $this->hasOne('App\Pilot');
}
When you return a model directly from the controller, it invokes the toJson() method to convert the object to a string. If you want to append the contents of a related model you can do so by adding the relationship to the $with variable on the Flight model.
protected $with = ['pilot']

Custom model method to get a relation in Laravel

I try to define a custom Model method in Laravel. I have a n:m relation between Subscription and Notification over SubscriptionNotification.
I already defined the default relations:
public function subscription_notifications() {
return $this->hasMany('App\SubscriptionNotification');
}
public function notifications() {
return $this->belongsToMany('App\Notification', 'subscription_notifications');
}
Now I want to define a method, which returns a collection of notifications. I collect the IDs of the notifications I want in an array and write the following method:
public function notifications_due() {
// Collect $notification_ids
return $this->belongsToMany('App\Notification', 'subscription_notifications')->whereIn('notifications.id', $notification_ids)->get();
}
But when I want to use the mothod by $subscription->notifications_due, I get the following error:
[LogicException]
Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation
I'm new to Laravel (I come from Rails). I don't know if this is in Laravel even possible. Maybe someone can help me. Thanks!
Remove the ->get() part in the method notifications_due. get() will return a Collection, but when calling the method as a property (or magic method), Laravel expects the method to return an instance of Relation. Laravel will then execute the query and transform it to a Collection automatically.
Also, you can use your already defined notifications() method:
public function notifications_due() {
// Collect $notification_ids
return $this->notifications()->whereIn('id', $notification_ids);
}
Remove the get call from your relationship method, for example:
public function notifications_due() {
return $this->belongsToMany(
'App\Notification',
'subscription_notifications
')->whereIn('notifications.id', $notification_ids);
}
Use it just same:
// It'll return a collection
$dues = $subscription->notifications_due;
To get all the ids from the collection you may try this:
$ids = $dues->pluck('id');
Also, you may add more constraints if you want if you use it like:the
$dues = $subscription->notifications_due()->where('some', 'thing')->get();
Or paginate:
$dues = $subscription->notifications_due()->where('some', 'thing')->paginate(10);

Eloquent $appends attributes returning null

I am loading some data into an Eloquent model via an appended attribute and the returned model's attribute is always null. I have protected $appends = array('survey_status); defined in my model (named Survey), and have the accessor defined as such:
public function getSurveyStatusAttribute($value){
return $value;
}
I have tried setting the attribute both as a property and in bracket notation($this->survey_status = ... & $this->attributes['survey_status'] = ..) and also tried using the setAppends() method prior to returning the model, all to no avail.
This was reported on the Laravel forums back at the end of Sept. 2013 and reported as fixed that Oct. (see: https://github.com/laravel/framework/issues/2336 and http://laravel.io/forum/02-26-2014-model-attribute-accessor-not-working-with-object-get-helper-function)
I am on the most current version of Laravel 4 (v4.2.17) which was released in Feb of this year. And from what I read in the docs and elsewhere it seems as though I'm doing everything correctly. Can any see something I'm not doing or confirm this is still an issue?
UPDATE
So I think I figured out 75% of my issue. I didn't realize you could pass an array to $model->load() to make complex queries using where/orWhere/etc. So this basic example works:
$survey = Survey::find(168);
$survey->load(array('surveyStatus' => function ($q){
$q->where('organization_id', '=', 7485);
}));
return Response::json($survey);
In the response my SurveyStatus model is supplied. My issue now is I am trying to iterate of a collection of Survey models to add a SurveyStatus relation just like the working one above but the attribute isn't there on the response. This is what I'm using to iterate the collection:
$org->surveySets->each(function ($ss) use ($id){
$fye = $ss->fiscal_year_end;
$ss->surveys->each(function ($survey) use ($id, $fye){
$sid = $survey->id;
$survey->load(array('surveyStatus' => function ($q) use($id, $fye){
$q->where('organization_id', '=', $id)->where('fiscal_year_end', '=', $fye);
}));
$survey->content_groups->each(function ($cg) use ($id, $sid, $fye){
$cg->content_rows->each(function ($cr) use ($id, $sid, $fye){
$cr->questions->each(function ($q) use ($id, $sid, $fye){
// do the same thing as surveyStatus but load surveyData relation into question
});
});
});
});
});
Is there some reason the loading doesn't 'stick' when iterating over a collection?
Correct me if I'm wrong, but appends doesn't get passed a $value because it's not mapped to a table column. I always thought of it as a computed property of sorts.
Given we have fillable columns 'first' and 'last' we might create an appends called 'fullname'.
protected $appends = [
'fullname'
];
public function getFullnameAttribute()
{
return $this->attributes['fullname'] = $this->first . ' ' . $this->last;
}
Essentially what I think your confusing is that appends is extra data that your appending to your model attributes. You are expected to return a value from the accessor. Returning $value will be null because $value doesn't exist, which is why your manually appending it. Try returning 'foo' in your accessor then you'll see what I mean.
Hello if you want to append some extra data related to another model you could do something like this.
protected $appends = [
'catName'
];
// relation
public function category()
{
return $this->hasOne(PostCat::class, 'id', 'id_cat');
}
//getters
public function getCatNameAttribute()
{
return $this->category()->getEager()->first()->name;
}
If your related model hold many db row considere this
protected $with = [
'category'
];
protected $appends = [
'catName'
];
// relation
public function category()
{
return $this->hasOne(PostCat::class, 'id', 'id_cat');
}
//getters
public function getCatNameAttribute()
{
return $this->category->name;
}
best regards

Laravel belongsTo returning null when using 'with'

I'm just getting started with Laravel so please forgive any noobness.
I have a User and Order model, a user has many orders:
# Inside User model
public function orders()
{
$this->hasMany('Order');
}
# Inside Order
public function user()
{
return $this->belongsTo('User');
}
// Not sure if this is upsetting anything (also in Order)
public function products()
{
return $this->belongsToMany('Product');
}
So I think I have the above right.
But when I do this:
$users = User::with('orders')->find(1);
return $users;
I get Call to a member function addEagerConstraints() on null.
However, if I do it the other way around, it works great:
$orders = Order::with('User')->get();
return $orders;
What am I doing wrong / what don't I understand?! Or is my problem bigger than I think?
Database:
The problem is you don't have return for your orders relationship. It should be:
public function orders(){
return $this->hasMany('Order');
}
You should also use your relationships case sensitive. you showed:
$orders = Order::with('User')->get();
is working, but you should rather use
$orders = Order::with('user')->get();
to avoid extra queries to your database in future
For anyone else that runs across this, I was having the same issue, but my problem was that I had the foreign/local keys swapped. Example:
// This is correct for hasX relationships
public function user() {
return $this->hasOne('App\Models\User', 'user_id', 'local_key_user_id');
}
// This is correct for belongsTo relationships
public function user() {
return $this->belongsTo('App\Models\User', 'local_key_user_id', 'user_id');
}
Notice that for hasX relationships, the foreign key is the second parameter, and the local key is the third. However, for belongsTo relationships, these two are swapped.
Probably doesn't answer this particular question but it relates to the title. I had the same issue here is the wrong query
$offer = Offer::with([
'images:name,id,offer_id',
'offer_options:offer_option,value,id,offer_id',
'user:id,name,avatar'])
->select(['id', 'views', 'type', 'status'])
->where('id', $id)->get();
the model look like this
class Offer extends Model {
function user(): BelongsTo {
return $this->belongsTo(User::class);
}
}
The User
class User extends ..... {
function offer(): HasMany {
return $this->hasMany(Offer::class);
}
}
The issue with the query is I was not selecting user_id, i.e in my select function user_id column was not included and that is why I was getting null for user
according to Laravel docs
When using this feature, you should always include the id column and
any relevant foreign key columns in the list of columns you wish to
retrieve.
So the correct query is
$offer = Offer::with([
'images:name,id,offer_id',
'offer_options:offer_option,value,id,offer_id',
'user:id,name,avatar'])
->select(['id', 'views', 'type', 'status','user_id'])
->where('id', $id)->get();

Categories