Complex Laravel collection - php

I'm having some issues with the Laravel Collection class.
What I'm trying to do:
I have a multisite solution in which a site has "facilitators". Sometimes one facilitator appears on multiple sites, and not just one.
I want to list all the facilitators and the website they're on the main page, but I don't want multiple users.
So what I currently do is:
Get the Facilitators.
Use Collection to collect the facilitators and use unique('name').
This gives me unique facilitators, but only picks the first one it detects and then deletes the other ones.
So lets say I have this collection:
Collection {
#items: array:3 [
0 => array:2 [
"name" => "John"
"site" => "Example"
]
1 => array:2 [
"name" => "Martin"
"site" => "Another"
]
2 => array:2 [
"name" => "John"
"site" => "Another"
]
]
}
With unique() I would get:
Collection {
#items: array:3 [
0 => array:2 [
"name" => "John"
"site" => "Example"
]
1 => array:2 [
"name" => "Martin"
"site" => "Another"
]
]
}
And this is what I want to get:
Collection {
#items: array:3 [
0 => array:2 [
"name" => "John"
"site" => ["Example", "Another"]
]
1 => array:2 [
"name" => "Martin"
"site" => "Another"
]
]
}
Does anyone have an idea how I could accomplish this with Laravel's collection class?

When stuck with collections always remember reduce is a powerful tool in your arsenal.
Building on Sam's answer which I couldn't get to work, I think using reduce alongside groupBy should work...
$sites = collect([
["name" => "John", "site" => "Example"],
["name" => "Martin", "site" => "Another"],
["name" => "John", "site" => "Another"],
]);
$sites->groupBy('name')->reduce(function ($result, $item) {
$result[] = [
'name' => $item->first()['name'],
'sites' => $item->pluck('site')->toArray()
];
return $result;
}, collect([]))->toArray();
And from the console...
λ php artisan tinker
Psy Shell v0.8.2 (PHP 7.0.10 ÔÇö cli) by Justin Hileman
>>> $sites = collect([
... ["name" => "John", "site" => "Example"],
... ["name" => "Martin", "site" => "Another"],
... ["name" => "John", "site" => "Another"],
... ]);
=> Illuminate\Support\Collection {#698
all: [
[
"name" => "John",
"site" => "Example",
],
[
"name" => "Martin",
"site" => "Another",
],
[
"name" => "John",
"site" => "Another",
],
],
}
>>> $sites->groupBy('name')->reduce(function ($result, $item) {
... $result[] = ['name' => $item->first()['name'], 'sites' => $item->pluck('site')->toArray()];
...
... return $result;
... }, collect([]))->toArray();
=> [
[
"name" => "John",
"sites" => [
"Example",
"Another",
],
],
[
"name" => "Martin",
"sites" => [
"Another",
],
],
]
One thing to note is that you specified in your question that the sites should return a single string if there's only one site and an array if there's many.The above solution does not provide this! I think this is inconsistent and you should always return an array for the sites key, even if it only has one value as it will make it more difficult to read and manipulate later on.
However, if this is something important, you could instead check if there are many sites when using pluck to set an array and if not you could set it as a single string, like this:
$sites->groupBy('name')->reduce(function ($result, $item) {
$result[] = [
'name' => $item->first()['name'],
'sites' => $item->pluck('site')->count() > 1 ? $item->pluck('site') : $item->first()['site']
];
return $result;
}, collect([]))->toArray();
which would produce...
[
[
"name" => "John",
"sites" => [
"Example",
"Another",
],
],
[
"name" => "Martin",
"sites" => "Another",
],
]

you can do it by chaining to get exactly what you want, assuming $collection is the main collection
$collection->groupBy('name')->map(function($facilitators) {
return ['name' => $facilitators->first()['name'], 'site' => $facilitators->pluck('site')->toArray()];
})->values()->toArray();
first we group by name so it will give 2 dimensional array inside collection, then iterate to that and name will be common so get it from first element, then from all the element pluck site and convert it to array, using flatMap will make it single level nested.

Related

How to get value from a collection base on key name condition?

I am trying to get the value from a collection in PHP.
$todaylog variable contains a collection from a laravel query builder:
$todaylog = [
{
"row_id":55,
"emp_number":"IPPH0004",
"timestamp":"03:30:23",
"attendance_status":"Punch In",
"date_created":"2021-10-01"
},
{
"row_id":56,
"emp_number":"IPPH0004",
"timestamp":"11:32:50",
"attendance_status":"Start Break",
"date_created":"2021-10-01"
},
{
"row_id":57,
"emp_number":"IPPH0004",
"timestamp":"11:33:09",
"attendance_status":"End Break",
"date_created":"2021-10-01"
}
]
What I have done so far: but this approach is so slow:
$timein = DB::table('attendance')->select('timestamp')->where('attendance_status','Punch In')->get();
Now I want to have something like this: (PS this is only a pseudo code)
$timein = (where) $todaylog.attendance_status = "Punch In"
$endbreak = (where) $todaylog.attendance_status = "End Break"
Is this possible? or I have to query them to the database individually? Thanks
What you need in Laravel is something like that:
$rows = collect([
[
"row_id" => 55,
"emp_number" => "IPPH0004",
"timestamp" => "03:30:23",
"attendance_status" => "Punch In",
"date_created" => "2021-10-01"
],
[
"row_id" => 56,
"emp_number" => "IPPH0004",
"timestamp" => "11:32:50",
"attendance_status" => "Start Break",
"date_created" => "2021-10-01"
],
[
"row_id" => 57,
"emp_number" => "IPPH0004",
"timestamp" => "11:33:09",
"attendance_status" => "End Break",
"date_created" => "2021-10-01"
]]);
dd($rows->where('row_id', 57));
Result:
Illuminate\Support\Collection {#446 ▼
#items: array:1 [▼
2 => array:5 [▼
"row_id" => 57
"emp_number" => "IPPH0004"
"timestamp" => "11:33:09"
"attendance_status" => "End Break"
"date_created" => "2021-10-01"
]
]
}
you can use laravel collection where method:
collect($todaylog)->where("attendance_status", "Punch In")->first();

Laravel - restructuring array for easy sync of many-to-many with additional pivot data

I have created what feels like a clunky solution to restructuring a data array in order to pass it to a sync() to update a many-to-many relationship with additional data in the pivot table and wondered if anyone could suggest a simpler approach.
I have an array coming from a request, here's a relevant extract:
"papers" => [
0 => [
"id" => 2
"code" => "123-321-888"
"name" => "Pop out"
"pivot" => [
"job_id" => 46
"paper_id" => 2
"qty_required" => 500
]
]
1 => [
"id" => 1
"code" => "444-666-999"
"name" => "Premium pro"
"pivot" => [
"job_id" => 46
"paper_id" => 1
"qty_required" => 1000
]
]
]
In order to do an easy sync of a many-to-many relationship with extra pivot data one needs to restructure that to:
[
paper[id] => [
'qty_required' => paper[pivot][qty_required]
]
]
Which for the above example would be:
[
2 => [
"qty_required" => "500"
]
1 => [
"qty_required" => "1000"
]
]
I'm currently doing a 2-step process to achieve this as follows:
$paperUpdate = Arr::pluck($request->input('papers'), 'pivot.qty_required', 'id');
//output: [ 2 => 500, 1 => 1000]
foreach ($paperUpdate as $key => $value) {
$paperSync[$key]['qty_required'] = $value;
}
//output: [ 2 => [ "qty_required" => "500" ], 1 => [ "qty_required" => "1000" ]
$job->papers()->sync($paperSync);
Is there an easier approach?
Your approach seems fine to me. If you want to nit pick, you could do one less iteration using:
$sync = array_reduce($request->input('papers'), function ($sync, $paper) {
$id = $paper['id'];
$sync[$id] = [ 'qty_required' => $paper['pivot']['qty_required'] ];
return $sync;
}, []);

Removing an element from a nested array in PHP

Am working on a Laravel application whereby I have an associative array that am to pass to an API endpoint, Before posting to the API, I want to delete the img key together with its value . I have tried to use unset function but it is not removing the img key
Array where I want to remove the image property
$a[] = [
0 => array:4 [
"name" => "Martoo nnn"
"relationship" => "Spouse"
"dob" => "2001-02-03"
"img" => "img.png"
]
1 => array:4 [
"name" => "sdsdsd sdsdsd"
"relationship" => "Child"
"dob" => "2019-04-04"
"img" => "img1.png"
]
2 => array:4 [
"name" => "sdsdsd sddds"
"relationship" => "Child"
"dob" => "2019-04-05"
"img" => "img2.png"
]
3 => array:4 [
"name" => "dssdsd dsdsd"
"relationship" => "Child"
"dob" => "2019-04-02"
"img" => "img3.png"
]
4 => array:4 [
"name" => "dssdsd dssdsd"
"relationship" => "Child"
"dob" => "2019-04-04"
"img" => "img4.png"
]
];
Unset method
$array = $a;
unset($array['img']);
//dd($a);
You can do something like this,
foreach ($array as $key => &$value) { // & defines changes will be made # value itself
unset($value['img']);
}
And Yes, I don't understand why you initialised $a as $a[]?
$newarray = array_filter($a, function($k) {
return $k != 'img';
}, ARRAY_FILTER_USE_KEY);
and pass this new array

Push a set of values to an array based on another unique value in the same array

Question background
Hello, I have the following array of movie crew members:
array:7 [▼
0 => array:6 [▼
"credit_id" => "52fe49dd9251416c750d5e9d"
"department" => "Directing"
"id" => 139098
"job" => "Director"
"name" => "Derek Cianfrance"
"profile_path" => "/zGhozVaRDCU5Tpu026X0al2lQN3.jpg"
]
1 => array:6 [▼
"credit_id" => "52fe49dd9251416c750d5ed7"
"department" => "Writing"
"id" => 139098
"job" => "Story"
"name" => "Derek Cianfrance"
"profile_path" => "/zGhozVaRDCU5Tpu026X0al2lQN3.jpg"
]
2 => array:6 [▼
"credit_id" => "52fe49dd9251416c750d5edd"
"department" => "Writing"
"id" => 132973
"job" => "Story"
"name" => "Ben Coccio"
"profile_path" => null
]
3 => array:6 [▼
"credit_id" => "52fe49dd9251416c750d5ee3"
"department" => "Writing"
"id" => 139098
"job" => "Screenplay"
"name" => "Derek Cianfrance"
"profile_path" => "/zGhozVaRDCU5Tpu026X0al2lQN3.jpg"
]
4 => array:6 [▼
"credit_id" => "52fe49dd9251416c750d5ee9"
"department" => "Writing"
"id" => 132973
"job" => "Screenplay"
"name" => "Ben Coccio"
"profile_path" => null
]
5 => array:6 [▼
"credit_id" => "52fe49dd9251416c750d5eef"
"department" => "Writing"
"id" => 1076793
"job" => "Screenplay"
"name" => "Darius Marder"
"profile_path" => null
]
11 => array:6 [▼
"credit_id" => "52fe49de9251416c750d5f13"
"department" => "Camera"
"id" => 54926
"job" => "Director of Photography"
"name" => "Sean Bobbitt"
"profile_path" => null
]
]
As you can see this is a list of credits I'm getting via the TMDb API. The first step of building the above array was to filter out all jobs that I don't want to display, here's how I did that:
$jobs = [ 'Director', 'Director of Photography', 'Cinematography', 'Cinematographer', 'Story', 'Short Story', 'Screenplay', 'Writer' ];
$crew = array_filter($tmdbApi, function ($crew) use ($jobs) {
return array_intersect($jobs, $crew);
});
My question
I'd like to figure out how to take the above result one step further and combine jobs where the id is the same, so as to end up with something like this, for example:
array:7 [▼
0 => array:6 [▼
"credit_id" => "52fe49dd9251416c750d5e9d"
"department" => "Directing"
"id" => 139098
"job" => "Director, Story, Screenplay"
"name" => "Derek Cianfrance"
"profile_path" => "/zGhozVaRDCU5Tpu026X0al2lQN3.jpg"
]
I have also considered ditching doing this in my logic and instead doing it in my blade template, but I'm not sure how to achieve that.
How would you accomplish this?
You could nicely use Laravel's Collection in such a situation, which has a great number of methods which will help you in this case.
First, turn this array (the one you already filtered on jobs) to a Collection:
$collection = collect($crew);
Second, group this Collection by it's ids:
$collectionById = $collection->groupBy('id');
Now, the results are grouped by the id and transformed to a Collection in which the keys correspond to the id, and the value an array of 'matching' results. More info about it here.
Finally, just a easy script that iterates through all the results for each id and combines the job field:
$combinedJobCollection = $collectionById->map(function($item) {
// get the default object, in which all fields match
// all the other fields with same ID, except for 'job'
$transformedItem = $item->first();
// set the 'job' field according all the (unique) job
// values of this item, and implode with ', '
$transformedItem['job'] = $item->unique('job')->implode('job', ', ');
/* or, keep the jobs as an array, so blade can figure out how to output these
$transformedItem['job'] = $item->unique('job')->pluck('job');
*/
return $transformedItem;
})->values();
// values() makes sure keys are reordered (as groupBy sets the id
// as the key)
At this point, this Collection is returned:
Collection {#151 ▼
#items: array:4 [▼
0 => array:6 [▼
"credit_id" => "52fe49dd9251416c750d5e9d"
"department" => "Directing"
"id" => 139098
"job" => "Director, Story, Screenplay"
"name" => "Derek Cianfrance"
"profile_path" => "/zGhozVaRDCU5Tpu026X0al2lQN3.jpg"
]
1 => array:6 [▼
"credit_id" => "52fe49dd9251416c750d5edd"
"department" => "Writing"
"id" => 132973
"job" => "Story, Screenplay"
"name" => "Ben Coccio"
"profile_path" => null
]
2 => array:6 [▼
"credit_id" => "52fe49dd9251416c750d5eef"
"department" => "Writing"
"id" => 1076793
"job" => "Screenplay"
"name" => "Darius Marder"
"profile_path" => null
]
3 => array:6 [▼
"credit_id" => "52fe49de9251416c750d5f13"
"department" => "Camera"
"id" => 54926
"job" => "Director of Photography"
"name" => "Sean Bobbitt"
"profile_path" => null
]
]
}
Note: to use this Collection as an array, use:
$crew = $combinedJobCollection->toArray();
There are multiple ways to achieve this, for example: search the array for overlapping id's, but I think this is the easiest way to achieve this.
Goodluck!
Since you are trying to edit the array elements and its size, I believe array_map() or array_filter() won't be a solution to this.
This is what I could come up with...
$jobs = [
'Director', 'Director of Photography', 'Cinematography',
'Cinematographer', 'Story', 'Short Story', 'Screenplay', 'Writer'
];
$crew = [];
foreach($tmdbApi as $key => $member) {
if($member['id'] == $id && in_array($member['job'], $jobs)) {
if(!isset($crew[$key])) {
$crew[$key] = $member;
} else {
$crew_jobs = explode(', ', $crew[$key]['job']);
if(!in_array($member['job'], $crew_jobs)) {
$crew_jobs[] = $member['job'];
}
$crew[$key]['job'] = implode(', ', $crew_jobs);
}
}
}
Hope this answers your question :)

How to Count the number of objects in elequont Object inside a group by in a already queried collection?

This is my current output
Collection {#794 ▼
#items: array:8 [▼
"IN" => Collection {#795 ▶}
"NZ" => Collection {#787 ▶}
]}
I want the items to be hold the no of count for each codes like
"IN" => 4,
"NZ" => 3,
I know that I can directly write in a query like this
$query->groupBy('country_code')->orderBy('country_code', 'ASC');
return $query->get([
DB::raw('country_code as country_code'),
DB::raw('COUNT(*) as "count"')
]);
But I want the output from a already queried collection to reduce multiple queries which is a collection.
Right now I am only able to group by on the collection like this
$collection->groupBy('country_code');
$b = $a->groupBy('country_code');
You've done most of the job by proper grouping the data based on the country_code. Now it's just to iterate through the collection with a foreach, key, value and use the collections's count() method to count the number of elements stored under a given country_code
foreach ($b as $countryCode => $items) {
echo $items->count()."\n";
}
Reproduce:
php artisan ti
Psy Shell v0.7.2 (PHP 7.0.8-0ubuntu0.16.04.3 — cli) by Justin Hileman
>>> $cities = collect([['country_code' => 'pl', 'name' => 'Warszawa'], ['country_code' => 'pl', 'name' => 'Wrocław'], ['country_code' => 'de', 'name' => 'Berlin']]);
=> Illuminate\Support\Collection {#846
all: [
[
"country_code" => "pl",
"name" => "Warszawa",
],
[
"country_code" => "pl",
"name" => "Wrocław",
],
[
"country_code" => "de",
"name" => "Berlin",
],
],
}
>>> $grouped = $cities->groupBy('country_code');
=> Illuminate\Support\Collection {#836
all: [
"pl" => Illuminate\Support\Collection {#838
all: [
[
"country_code" => "pl",
"name" => "Warszawa",
],
[
"country_code" => "pl",
"name" => "Wrocław",
],
],
},
"de" => Illuminate\Support\Collection {#837
all: [
[
"country_code" => "de",
"name" => "Berlin",
],
],
},
],
}
>>> foreach ($grouped as $cCode => $cities) {
... echo $cCode . ' has '.$cities->count()."\n";
... }
pl has 2
de has 1

Categories