Have select() and/or pluck() been broken in Laravel 6? - laravel

The following code does not pluck the name column of the selected user record. Rather, returns the entire row. Before I make a re-creatable example: Is this the expected behaviour here?
I want to explicitly select columns across joins to reduce my JSON payload size, and to return a nested model hierarchy to my clients.
I should add that I'm experiencing the same behaviour when using the pluck() function as well, on the same line. Perhaps I've done something wrong.
There's tons of examples showing this approach with earlier versions of Laravel. Version 6 may have broken this.
$query = Post::whereHas('user.address', function ($query) use ($lat, $lon, $distance) {
$query->distance($lat, $lon, $distance);
})->with([
'user' => function ($query) {
$query->select('name'); // TODO: Report this bug. I've also tried pluck()
},
'user.address' => function ($query) use ($lat, $lon, $distance) {
$query->distance($lat, $lon, $distance);
},
'user.address.city',
'bids' => function ($query) {
$query->orderBy('amount', 'DESC');
},
'bids.user',
'images',
]);

pluck() is a collection method, it executes the query and returns a simple Collection object of the field you specify.
Using pluck() inside your subquery builder executes it (returning nothing, because you are assigning it to nothing) while the $query variable is unmodified and behaves as normal returning all columns.
If you were to dump the value of the pluck() inside this query, you would see it is an array of just names, and because of that, it has no affect on the query itself.
'user' => function ($query) {
dd($query->pluck('name'));
}
select() should work fine in this case. You just need to also provide the relationship key or else it will just return a null object.
'user' => function ($query) {
$query->select(['id', 'name']);
},

Related

Laravel, eloquent, query: problem with retriving right data from DB

I'm trying to get the right data from the database, I'm retriving a model with a media relation via eloquent, but I want to return a photo that contains the 'main' tag stored in JSON, if this tag is missing, then I would like to return the first photo assigned to this model.
how i assign tags to media
I had 3 ideas:
Use orWhere() method, but i want more likely 'xor' than 'or'
$models = Model::with(['media' => function ($query) {
$query->whereJsonContains('custom_properties->tags', 'main')->orWhere();
}]);
return $models->paginate(self::PER_PAGE);
Raw SQL, but i don't really know how to do this i tried something with JSON_EXTRACT and IF/ELSE statement, but it was to hard for me and it was a disaster
Last idea was to make 2 queries and just add media from second query if there is no tag 'main'
$models = Model::with(['media' => function ($query) {
$query->whereJsonContains('custom_properties->tags', 'main');
}]);
$models_all_media = Model:: with(['media']);
return $models->paginate(self::PER_PAGE);
but i tried something like
for($i=0; $i<count($models); $i++) {
$models->media = $models_all_media
}
but i can't do this without get() method, beacuse i don't know how to change this to LengthAwarePaginator class after using get()
try using whereHas https://laravel.com/docs/9.x/eloquent-relationships
Model::with('media')
->whereHas('media',fn($media)=>$media->whereJsonContains('custom_properties->tags', 'main'))
->paginate(self::PER_PAGE);
as per your comment you can use
$models = Model::with(['media' => function ($query) {
$query->whereJsonContains('custom_properties->tags', 'main');
}])
->leftJoin('media', function ($join) {
$join->on('models.id', '=', 'media.model_id')
->whereNull('media.custom_properties->tags->main');
})
->groupBy('models.id')
->paginate(self::PER_PAGE);
return $models;

How to write a query conditionally in laravel?

I am new to the laravel, i am joining three tables based on conditions every thing is working fine but i need to write conditionally if the $field is array it should run with whereIn otherwise it should run where condition,can you please help me to acheive this thing
//$field sometimes it's an array sometimes it's a string.
public function find($field){
}
For conditional constraints, you can use the when() clause:
$query = DB::table(...);
$data = $query
->when(is_array($field), function ($query) use ($field) {
$query->whereIn('my_field', $field);
}, function ($query) use ($field) {
$query->where('my_field', $field);
})
->get();
Now, as a tip, you could do this: Wrap the $fields variables to an array and then use always the whereIn clause. You can achieve this with the Arr::wrap() function:
use Illuminate\Support\Arr;
// ...
$query = DB::table(...);
$data = $query
->whereIn('my_field', Arr::wrap($field))
->get();
PS: I have linked the relevant functions to the docs so you can know how they work and their params.

Limit attributes from joined tables in complex query build

So my question is a follow in from the one I posted here:
Laravel 8 - Limit joined tables
And using the suggestion in the accepted answer, this is what I ended up with
User::with(['profile'])->with('collections.game')
->whereHas('collections', function (Builder $query) {
return $query->whereHas('game', function (Builder $query) {
return $query->where('status', '<>', 'denied');
});
})
->paginate(10)
It works great. I get the information about the user from both the users and profiles table as well as all the collections the user owns of games that do not have a denied status. Sweet! Thanks #matiaslauriti!
The problem I'm seeing is that all of the columns are getting pulled in. This includes information about the user such as their email address (from the user's table) and their physical mailing address (from the profile table) among other things. Information that I would prefer not to disclose.
Under normal circumstances, this wouldn't be a problem because once the blade template was parsed and the markup for the page is generated, all of that data would get trashed and not get returned to the browser. But my circumstance is different. I'm using InertiaJS with React so all of that data is getting returned from the server via an XHR request and passed in as props to my component. This makes it so that all the data is completely visible and I definitely don't want that.
I've played around with ::without() and ::withOnly() but I couldn't get them to work. Not just not work for User but for the nested Profile data as well. So is there a way to do what I'm doing to get all the data I need but without all the sensitive data I'd rather not return to the client?
thnx,
Christoph
It can be done one by passing a closure function in with() as second index of array.
Example:
Post::query()
->with(['user' => function ($query) {
$query->select('id', 'username');
}])
->get()
It will only select id and username from other table.
Remember that the primary key (id in this case) needs to be the first param in the $query->select() to actually retrieve the necessary results.*
IF you are using a JSON response (e.g. you have an API) and IF you want to have a standard way to create "views" in your data for the purposes of e.g. displaying them in specific responses you can do that using JSON resources(if these two don't apply to you then you can ignore this answer)
In your case you could create two resources e.g. UserResource and ProfileResource and implement them as:
UserResource:
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'profile' => new ProfileResource($this->profile),
];
}
ProfileResource:
public function toArray($request)
{
return [
'public_info' => $this->public_info,
];
}
As the docs mention you can send this response as:
$users = User::with(['profile'])->with('collections.game')
->whereHas('collections', function (Builder $query) {
return $query->whereHas('game', function (Builder $query) {
return $query->where('status', '<>', 'denied');
});
})
->paginate(10);
return UserResource::collection($users);
Calling the whereHas method will never select the related records. So if you want to run only the query on relations remove with in query
User::query()
->whereHas('collections', function (Builder $query) {
return $query->whereHas('game', function (Builder $query) {
return $query->where('status', '<>', 'denied');
});
})
->paginate(10);
You can also use dotted relation
User::query()
->whereHas('collections.game', function (Builder $query) {
return $query->where('status', '<>', 'denied');
})
->paginate(10);

Eloquent, Retrieve results from nested relations query

Here is my query:
$profile = \App\ShippingProfile::findOrFail(2)
::with('costs')
->with( ['methods.zone' => function($query) {
$query->where('id', 2);
}])->get();
So I need shipping profile id 2, with related "costs" model and its related "method" with id 2, and the method's related "zone".
But I don't know how to get the result. If I try ->get() , I get all ShippingProfiles, and if try
->first() I get profile id 1.
So how would I retrieve the results? I have only seen "get", "first" and "find"...or is there something wrong with the query?
There are a few problems with your current code. First of all you have a syntax error:
$profile = \App\ShippingProfile::findOrFail(2)
::with('costs')
The second :: should be ->.
Secondly, when you do firstOrFail() you're actually executing the query and returning a Model as a result. So when you're then chaining the rest onto the Model instance you're likely ending up with an error.
There are a couple of ways you can achieve your goal however. First of all, try using whereKey which adds a where clause to your query against the primary key, (just like using findOrFail does - but it doesn't immediately execute the query and return the model) and use first() to grab the model instance with the relations eager loaded:
$profile = \App\ShippingProfile::whereKey(2)
->with(['costs', 'method.zone' => function ($query) {
$query->where('id', 2);
}])
->first();
Alternatively, if you already have an instance of your model, you can use load(...), in the same way as you would use with(...) to load your relations:
$profile = App\ShippingProfile::findOrFail(2);
$profile->load(['costs', 'method.zone' => function ($query) {
$query->where('id', 2);
}]);

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