Related $query->with but "pick properties as" - laravel

I am using Laravel 5.5.13.
I am trying to fetch all Extensions but with certain extra properties based on their relations.
My goal is to get data like this, I am trying to get the latest_comment_date, thumbs_count, and thumbs_yes_count:
[
{
"id": 3,
"name": "Pull Refresh",
"created_at": "2017-11-10 06:04:44",
"updated_at": "2017-11-10 06:04:44",
"latest_comment_date": "2017-11-10 05:46:25",
"thumbs_count": 10,
"thumbs_yes_count": 2
}
]
I tried this:
return Extension::with([
'comments' => function($query) { // rename to 'latest_comment_date'
$query->orderBy('created_at', 'desc')->take(1);
},
'thumbs' => function($query) { // rename to 'thumbs_count'
$query->count();
},
'thumbs' => function($query) { // rename to 'thumbs_yes_count'
$query->where('like', '=', true)->count();
}
])->get();
This gives me data like this:
[
{
"id": 3,
"name": "Pull Refresh",
"created_at": "2017-11-10 06:04:44",
"updated_at": "2017-11-10 06:04:44",
"comments": [
{
"id": 10,
"body": "heck ya baby",
"displayname_id": 2,
"extension_id": 3,
"created_at": "2017-11-10 18:31:31",
"updated_at": "2017-11-10 18:31:31"
}
],
"thumbs": []
}
]
May you please help me to "pick as" stuff as I showed at top most?

For counting you should use withCount like so:
return Extension::withCount(
'thumbs',
'thumbs as thumbs_yes_count' => function($query) {
$query->where('like', '=', true);
})->get();
Reference: Counting related models
And for taking latest comment, you should create extra relationship like so:
public function latestComment()
{
return $this->hasOne(Comment::class)->orderBy('created_at', 'desc');
}
so the whole code would look like this:
return Extension::with('latestComment')->withCount(
'thumbs',
'thumbs as thumbs_yes_count' => function($query) {
$query->where('like', '=', true);
})->get();

Related

How to select not null datas using whereHas in LARAVEL

Let me make it simple, In my models, I have countries and cities. My goal is, I want to select and display only the cities that has shops and it should also have datas and the employees should not be NULL
In the below given array, my city has two shops where id - 13 that doesn't have any datas and id - 19 which is having data.
[
{
"id": 1,
"created_at": "2022-06-02T06:07:31.000000Z",
"updated_at": "2022-06-02T06:07:31.000000Z",
"name": "Niue",
"cities": [
{
"id": 13,
"created_at": "2022-06-02T06:07:44.000000Z",
"updated_at": "2022-06-02T06:07:44.000000Z",
"country_id": 1,
"name": "North Michealbury",
"shops": [] //empty
},
{
"id": 19,
"created_at": "2022-06-02T06:07:44.000000Z",
"updated_at": "2022-06-02T06:07:44.000000Z",
"country_id": 1,
"name": "Millsmouth",
"shops": [
{
"id": 1,
"created_at": "2022-06-02T06:14:37.000000Z",
"updated_at": "2022-06-02T06:14:37.000000Z",
"city_id": 19,
"name": "Roberts-Okuneva",
"employees_count": 146
}
]
}
]
}
]
This is the code that, I have written in my controller. The goal is, I want to select and show only those cities that has shops with employees.
return Country::with(['cities','cities.shops'=>function($employee){
$employee->withCount('employees')->where('city_id',19);
}])->whereHas('cities',function($city){
$city->where('cities.id',19);
})->get();
These are my models, each of them has a hasMany relationship
Country model
public function Cities(){
return $this->hasMany(City::class);
}
City model
public function shops(){
return $this->hasMany(Shop::class);
}
Shop model
public function employees(){
return $this->belongsToMany(User::class,'shop_employees','shop_id','employee_id');
}
You can try
//Get all cities which have shops having at least one employee
return Country::with([
'cities',
'cities.shops' => function($query) {
$query->withCount('employees');
}
])
->whereHas('cities', function($query) {
$query->whereHas('shops', function($query) {
$query->has('employees');
});
})
->get();
i found another solution querying with clause.
return Country::whereHas('cities',function($cities){
$cities->where('country_id',1);
})->with(['cities'=>function($shops){
$shops->has('shops')->where('cities.country_id',1);
},'cities.shops'=>function($employee){
$employee->withCount('employees');
}])->get();

i want to change my API response to become array of array object in laravel

i have a problem for the response, i want to change the response API because i need for my mobile APP, the feature have filter object based on date. So i hope you all can help me to solve the problem
i wanna change the response for my API
before:
{
"tasks": [
{
"id": 7,
"user_id": 1,
"title": "gh",
"date": "2022-02-10 13:05:00",
"deskripsi": "gfh",
"created_at": "2022-02-09T06:05:56.000000Z",
"updated_at": "2022-02-09T06:05:56.000000Z"
},
{
"id": 5,
"user_id": 1,
"title": "ghf",
"date": "2022-02-17 16:05:00",
"deskripsi": "fghf",
"created_at": "2022-02-09T06:05:12.000000Z",
"updated_at": "2022-02-09T06:05:12.000000Z"
},
{
"id": 6,
"user_id": 1,
"title": "fgh",
"date": "2022-02-17 18:05:00",
"deskripsi": "gh",
"created_at": "2022-02-09T06:05:40.000000Z",
"updated_at": "2022-02-09T06:05:40.000000Z"
}
]
}
here is the code for the response API above
return response([
'tasks' => Task::where('user_id', auth()->user()->id)->where('date','>',NOW())->orderBy('date','asc')->get(),
],200);
and i want to change it my response API into this response
{
"tasks": [
{
"date": "2022-02-10",
"task": [
{
"id": 7,
"user_id": 1,
"title": "gh",
"date": "2022-02-10 13:05:00",
"deskripsi": "gfh",
"created_at": "2022-02-09T06:05:56.000000Z",
"updated_at": "2022-02-09T06:05:56.000000Z"
},
{
"id": 7,
"user_id": 1,
"title": "gh",
"date": "2022-02-10 15:05:00",
"deskripsi": "gfh",
"created_at": "2022-02-09T06:05:56.000000Z",
"updated_at": "2022-02-09T06:05:56.000000Z"
}
]
},
{
"date": "2022-02-12",
"task": [
{
"id": 7,
"user_id": 1,
"title": "gh",
"date": "2022-02-12 13:05:00",
"deskripsi": "gfh",
"created_at": "2022-02-09T06:05:56.000000Z",
"updated_at": "2022-02-09T06:05:56.000000Z"
}
]
},
]
}
Do groupBy on the resulting Collection from the query (see docs: https://laravel.com/docs/9.x/collections#method-groupby)
For example, you could do:
$tasksGroupedByDate = Task::where(.......)
->get()
->groupBy(fn (Task $task) => $task->date->format('Y-m-d'));
(Note: above uses PHP 7.4 arrow functions. Also, add a date cast on the date column in your Task model to be able to use ->format( directly on the date field)
The above code results to:
{
'2022-01-01' => [
{ Task object },
{ Task object },
{ Task object },
],
'2022-01-02' => [
{ Task object },
{ Task object },
{ Task object },
],
}
(used Task object for brevity, but that will be ['id' => 1, 'title' => 'Task name', .....])
To morph that to the structure you want, you can use map and then values to remove the keys and turn it back to an ordered array:
$tasksGroupedByDate->map(fn ($tasksInGroup, $date) => [
'date' => $date,
'task' => $tasksInGroup
])->values();
If you want to combine everything into one method chain:
return [
'tasks' => Task::where(......)
->get()
->groupBy(fn (Task $task) => $task->date->format('Y-m-d'))
->map(fn ($tasksInGroup, $date) => [
'date' => $date,
'task' => $tasksInGroup
])
->values(),
];
It sounds like you want to create a human friendly date field based on the date column, then group by it.
While solutions do exists to accomplish this at the database level, I believe you'd still need to loop around it again afterwards to get the hierarchy structure you're looking for. I don't think it's too complicated for PHP to loop through it.
My suggestion is as follows:
Before:
return response([
'tasks' => Task::where('user_id', auth()->user()->id)
->where('date','>',NOW())->orderBy('date','asc')->get(),
],200);
After:
$out = [];
$tasks = Task::where('user_id', auth()->user()->id)
->where('date','>',NOW())->orderBy('date','asc')->get();
foreach($tasks as $task) {
$date = strtok((string)$task->date, ' ');
if (empty($out[$date])) {
$out[$date] = (object)['date' => $date, 'task' => []];
}
$out[$date]->task[] = $task;
}
$out = array_values($out);
return response(['tasks' => $out], 200);
Note in the above I'm using the function strtok. This function might look new even to the most senior of php developers.... It's a lot like explode, except it can be used to grab only the first part before the token you're splitting on. While I could have used explode, since the latter part after the token isn't needed, strtok is better suited for the job here.
$task = Task::where('user_id', auth()->user()->id)->where('date','>',NOW())->orderBy('date','asc')->get();
foreach($task as $item){
$date[] = item->date;
$result = Task::where('user_id', auth()->user()->id)->where('date','=', $date)->get();
}
return response([
'tasks' =>
['date' => $date,
'task' => $task]
],200);
maybe something like this

Laravel How to orderby()->first() in eager loading

I have this query which is working just fine except when i call ->first() function on it, the data from certain relations disappear.
What I am trying to achieve:
I am trying to get the sp_services where price is lowest. The first object (id: 1) in data array has two sp_services, the second object inside data array (id: 13) has only one sp_services. When i do $query->orderBy('sp_services.price', 'DESC')->first(); The object from first one is there but the second one where (id: 6, name: car wash) disappears and there is only empty array.
In the below mentioned JSON I'm only showing the relevant data here. Can anyone please tell me what am i doing wrong here ? and why is it not working. Also is there a better way to achieve desired result.
Query:
$branchesQuery = SpBranch::query();
$branchesQuery->where('status', true);
$branchesQuery->when($request->filled('price'), function($query) use($request) {
return $query->with(['service_provider' => function($query) use($request) {
$query->whereHas('sp_services')
->with(['sp_services' => function($query) use($request) {
$query->orderBy('sp_services.price', 'DESC'); //->first();
}]);
}]);
});
$branches = $branchesQuery->paginate($perPageLimit);
return $branches;
Models:
class SpBranch extends Model
{
public function service_provider()
{
return $this->belongsTo(ServiceProvider::class, 'sp_id');
}
}
class ServiceProvider extends Model
{
public function sp_services()
{
return $this->hasMany(SpService::class, 'sp_id', 'id');
}
}
JSON:
{
"data": [
{
"id": 1,
"sp_id": 1,
"name": "Branch 01",
"status": 1,
"service_provider": {
"id": 1,
"name": "Service Provider 01",
"sp_services": [
{
"id": 3,
"sp_id": 1,
"branch_id": null,
"name": "Service 03",
"price": 50
},
{
"id": 4,
"sp_id": 1,
"branch_id": null,
"name": "Service 04",
"price": 70
}
]
}
},
{
"id": 13,
"sp_id": 5,
"name": "Branch 13",
"status": 1,
"service_provider": {
"id": 5,
"name": "Service Provider 05",
"sp_services": [
{
"id": 6,
"sp_id": 5,
"branch_id": null,
"name": "car wash",
"price": 300
}
]
}
}
]
}
You cannot order or group by a eager loaded table on database, because what WITH method does is that it fires a new query meaning that is not connected to the first one at all, what you can do is you can order or group them in collections meaning after the data is fetch from db you can order them, I think function is called ->sortBy() which will order by the field you want, but it will cost you on performance. If performance is important in your case, you can always use joins to join both tables which will enable you order results directly from database.
class ServiceProvider extends Model
{
public function sp_services()
{
return $this->hasMany(SpService::class, 'sp_id', 'id')->orderBy('price', 'asc');
}
}

Is it possible to apply condition inside the map function of Laravel?

I wanted to apply some condition in foodOrders. Is it possible to apply condition inside map function that acts as where $foodOrder->foodOrders->where('invoice_id',null)->get();
public function getTableList(Request $request){
$skip =$request->skip;
$limit=$request->limit;
$totaltable = Table::get()->count();
$table = Table::skip($skip)->take($limit)->orderBy('id', 'DESC')->get();
$table->map(function($foodOrder){
$foodOrder->foodOrders;
});
}
Below is the output that this query returns. But I only want the data with null invoice_id
{
"success": true,
"message": "Lists of Table.",
"data": [
{
"id": 2,
"table_number": "TN02",
"food_orders": [
{
"id": 16,
"food_items_id": 1,
"table_id": 2,
"invoice_id": null,
"quantity": 2,
"price": "2000.00"
},
{
"id": 17,
"food_items_id": 2,
"table_id": 2,
"invoice_id": null,
"quantity": 3,
"price": "150.00"
}
]
},
{
"id": 1,
"table_number": "TN01",
"created_at": "2020-10-25 10:44:31",
"updated_at": "2020-10-25 10:44:31",
"food_orders": [
{
"id": 14,
"food_items_id": 1,
"table_id": 1,
"invoice_id": 39,
"quantity": 1,
"price": "2000.00"
}
]
}
]
}
Not the best approach, bu this should do the trick
$table->map(function($foodOrder){
$foodOrder->foodOrders=$foodOrder->foodOrders()->where('invoice_id',null)->get();
});
Note the scopes, you need them to apply where condition. And we say = so the further changes are propagated.
I think what you need to use is a filter and not a map. Or am I wrong ?
You can filter out the whitelisted records for the food_orders.
$table->map(function($record){
$record->food_orders = collect($record->food_orders)
->filter(fn($foodOrder) => is_null($foodOrder->invoice_id));
return $record;
});
From the output data you have shown above it seems that the food_orders is a hasMany relation on the model, so it would be easy to filter out the relation when eager loading
Assuming that Table model has a relation defined for food_orders
class Table extends Model
{
public function food_orders()
{
return $this->hasMany(FoodOrder::class);
}
//... rest of the class code
}
You can constrain the related models when eager loading
$table = Table::with([
'food_orders' => fn($query) => $query->whereNull('invoice_id')
])
->skip($skip)
->take($limit)
->orderBy('id', 'desc')
->get();

Transform Laravel Eloquent result

When I execute this in my Controller
$data = User::with('teams')->find(2);
return response(['data' => $data]);
I get this as result
{
"id": 2,
"country_id": 1,
"first_name": "John",
"last_name": "Doe",
"created_at": "-0001-11-30 00:00:00",
"updated_at": "2015-02-02 23:08:21",
"full_name": "John Doe",
"teams": [
{
"id": 1,
"name": "Helpdesk medewerker",
"description": ""
},
{
"id": 2,
"name": "Retentie",
"description": ""
}
]
}
Im not interested in the full teams data, but only interested in the teamNames of the user.
I've done this by adding this
$data->each(function($user) {
$user->team_names = $user->teams->lists('name');
unset($user->teams);
});
I was wondering if this is the correct way of modifying the Eloquent result.
You can use an attribute accessor for that. Add this to your model:
protected $hidden = ['teams']; // hide teams relation in JSON output
protected $appends = ['team_names']; // add custom attribute to JSON output
public function getTeamNamesAttribute(){
return $this->teams->lists('name');
}
To change hidden and appends dynamically you can use the setter methods:
$data = User::with('teams')->find(2);
$data->setHidden(['teams']);
Filter out the columns you want in your first build.
$data = User::with(['teams' => function($query){
return $query->select('name');
}])->find(2);
Will output:
{
...
"teams": [
{
"name": "Helpdesk medewerker"
},
{
"name": "Retentie"
}
]
}
You can also use the lists method.
$data = User::with(['teams' => function($query){
return $query->lists('name');
}])->find(2);
Will output:
{
...
"teams": ["Helpdesk medewerker", "Retentie"]
}

Resources