I'm working on an application where users have different types of events. Each event type has its own database table/Laravel model. Right now I'm performing multiple database queries to fetch the events from the tables. After that I merge them manually by using for-each loops and creating a uniform structure.
Because the code is really long, I give you guys an example code here:
$output = [];
$events1 = EventType1::where('user',$user_id)->get();
foreach ($events1 as $ev1) {
$output[] = [
"id" => $ev1->id,
"date" => $ev1->id,
"attribute3" => $ev1->attributeA
];
}
$events2 = EventType2::where('user',$user_id)->get();
foreach ($events2 as $ev2) {
$output[] = [
"id" => $ev2->id,
"date" => $ev2->id,
"attribute3" => $ev2->someOtherAttribute
];
}
// More fetches here....
// ...
// ...
usort($output, function ($a, $b) {
return strcmp($a["date"], $b["date"]);
});
return $output;
So right now I want to improve the performance by using Pagination. But the way I fetch the data, I don't think it will work?!
Can someone help me how to fetch, merge and paginate all the events the proper way?
Is there a way to union all the data using Eloquent?
Thanks
If you want with pagination you can follow this. I have used one of my projects for merging different model data as well as including pagination. If you don't need pagination just ignore pagination method calling part.
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Collection;
public function events(){
$events1 = EventType1::where('user',$user_id)->get();
$events2 = EventType2::where('user',$user_id)->get();
$collection = new Collection();
$collection = $collection->merge($events1);
$collection = $collection->merge($events2);
$merge = $this->paginate($collection, $limit);
return $merge;
}
public function paginate($items, $perPage = 10, $page = null, $options = [])
{
$page = $page ?: (Paginator::resolveCurrentPage() ?: 1);
$items = $items instanceof Collection ? $items : Collection::make($items);
return new LengthAwarePaginator($items->forPage($page, $perPage), $items->count(), $perPage, $page, $options);
}
Related
I have an array of 'items' which has properties like 'supplier' and 'price'. It looks this(I expanded the last object so you can see the entire structure):
I have the results as I want them, but I feel that I am using an inefficient method.
private function generateSupplierDate(Collection $collection, array $suppliers = []): Collection
{
$supplierGroups = $collection->pluck('items')->flatten()->whereIn('supplier', $suppliers)->groupBy('supplier')->values();
$results = [];
foreach ($supplierGroups as $suppliers) {
$cost = 0;
$name = '';
foreach ($suppliers as $supplier) {
$name = $supplier->supplier;
$cost += $supplier->quantity;
}
array_push($results, ['name' => $name, 'cost' => $cost]);
}
return $results;
}
My goal is to get this but more efficiently:
[
{
supplier: 'Walmart',
totalCost: 1000
},
{
supplier: 'Bestbuy'
totalCost: 100
}
]
I tried to use reduce but that didn't seem to give me the right solution. And honestly, I'm a Javascript developer and I could do this in the Frontend, but that seems inefficient.
Any help would be appreciated (very new to Laravel/PHP).
I cant orderBy points. Points is accessor.
Controller:
$volunteers = $this->volunteerFilter();
$volunteers = $volunteers->orderBy('points')->paginate(10);
Volunteers Model:
public function siteActivities()
{
return $this->belongsToMany(VolunteerEvent::class, 'volunteer_event_user', 'volunteer_id', 'volunteer_event_id')
->withPivot('data', 'point', 'point_reason');
}
public function getPointsAttribute(){
$totalPoint = 0;
$volunteerPoints = $this->siteActivities->pluck('pivot.point', 'id')->toArray() ?? [];
foreach ($volunteerPoints as $item) {
$totalPoint += $item;
}
return $totalPoint;
}
But I try to sortyByDesc('points') in view it works but doesn't work true. Because paginate(10) is limit(10). So it doesn't sort for all data, sort only 10 data.
Then I try to use datatable/yajra. It works very well but I have much data. so the problem came out
Error code: Out of Memory
You could aggregate the column directly in the query
$volunteers = $this->volunteerFilter();
$volunteers = $volunteers->selectRaw('SUM(pivot.points) AS points)')->orderByDesc('points')->paginate(10);
I have this code in controller, so I need to paginate and sort by using distance, I dont know how to do this, Im new to laravel , thanks in advance
$stores = [];
foreach (session('storeinfo') as $storeInfo) {
$store = Storeinfo::find($storeInfo['id']);
if ($store) {
$store->distance = $storeInfo['distance'];
$stores[] = $store;
$stores = collect([]);
if (!Collection::hasMacro('paginate')) {
Collection::macro('paginate', function ($perPage = 25, $page = null, $options = []) {
$options['path'] = $options['path'] ?? request()->path();
$page = $page ?: (Paginator::resolveCurrentPage() ?: 1);
return new LengthAwarePaginator(
$this->forPage($page, $perPage)->values(),
$this->count(),
$perPage,
$page,
$options
);
});
}
}
}
return view('stores.archive',compact('stores'));
Im placing id into session using this:
$allstores= Storeinfo::all();
foreach ($allstores as $allstore) {
Session::push('storeinfo', [
'id' => $allstore->id,
'distance' => $miles
]);
}}
where $mile comes from calculation of distance
enter code here
First, I would create a collection instance of your $stores array:
$stores = collect([]);
I prefer using the push() api of the collection to add items:
$stores->push($store);
Second, the collection instance doesn't provide a native way to paginate so you need to add a macro:
use Illuminate\Support\Collection;
use Illuminate\Pagination\Paginator;
use Illuminate\Pagination\LengthAwarePaginator;
...
if (!Collection::hasMacro('paginate')) {
Collection::macro('paginate', function ($perPage = 25, $page = null, $options = []) {
$options['path'] = $options['path'] ?? request()->path();
$page = $page ?: (Paginator::resolveCurrentPage() ?: 1);
return new LengthAwarePaginator(
$this->forPage($page, $perPage)->values(),
$this->count(),
$perPage,
$page,
$options
);
});
}
I personally added the above macro in AppServiceProvider so that I can use it anywhere in my project. This should be located in the app/Providers directory.
To paginate you simply need to call the paginate() macro you just created:
$stores->paginate(15);
If you wish to set the current page or path, you may do so:
$stores->paginate(15, 1, ['path' => 'your/custom/path']);
To sort, all you need to do is used the desired sortBy method to achieve your results.
Per the docs, sortBy takes a string:
$sorted = $collection->sortBy('price');
Or a callback:
$sorted = $collection->sortBy(function ($product, $key) {
return count($product['colors']);
});
The method, sortByDesc() works the same way as sortBy().
I have a variable that is a pagination object
$pagination
I am changing things inside of it using transform
$pagination->getCollection()->transform(function ($item, $key) use(&$data, &$pagination) {
$item->foo = 'bar';
}
I want to remove an item from the pagination if it meets a certain condition. I don't want it removed until after I've been able to use the data. Following is an example.
$pagination->getCollection()->transform(function ($item, $key) use(&$data, &$pagination) {
$data[] = $item->foo;
if ($item->foo === 'bar') {
$item->remove();
}
}
I've also tried using $pagination->getCollection()->forget($key); inside of the transform
$pagination->getCollection()->transform(function ($item, $key) use(&$data, &$pagination) {
$data[] = $item->foo;
if ($item->foo === 'bar') {
$pagination->getCollection()->forget($key);
}
}
That's from this question.
How to unset (remove) a collection element after fetching it?
I'm guessing the fact I'm dealing with a pagination may be making these answers not apply to my situation.
Doing a separate collection of pagination with filter() allows removing items from the pagination based on complex conditions. I return false in the transform and then simply target it in the filter.
$pagination = $pagination->getCollection()->filter(function ($item) {
return $item;
});
Edit: This actually removed the pagination properties, so I've recreated the pagination afterwards like so
// Remove already merged rows
$itemsTransformed = $pagination->getCollection()->filter(function ($item) {
return $item;
});
// Recreate because filter removed pagination properties
$itemsTransformedAndPaginated = new \Illuminate\Pagination\LengthAwarePaginator(
$itemsTransformed,
$pagination->total(),
$pagination->perPage(),
$pagination->currentPage(), [
'path' => \Request::url(),
'query' => [
'page' => $pagination->currentPage()
]
]
);
I wonder if Laravel have any helper to modify a collection.
What I need to do is to make a query with paginate() then check if the logged in users ID match the sender or receiver and based on that add a new value to the output:
$userId = Auth::guard('api')->user()->user_id;
$allMessages = Conversation::join('users as sender', 'conversations.sender_id', '=', 'sender.user_id')
->join('users as reciver', 'conversations.recipient_id', '=', 'reciver.user_id')
->where('sender_id',$userId)->orWhere('recipient_id',$userId)
->orderBy('last_updated', 'desc')
->select('subject','sender_id','recipient_id', 'sender_unread', 'recipient_unread', 'last_updated', 'reciver.username as receivername', 'sender.username as sendername')
->paginate(20);
Now I want to do something like:
if ($allMessages->sender_id == $userId) {
// add new value to output
newField = $allMessages->sendername
} else {
// add new value to output
newField = $allMessages->receivername
}
Then send the data with the new value added
return response()->json(['messages' => $allMessages], 200);
Is this possible?
You're better off using the Collection class's built-in functions for this. For example, the map function would be perfect.
https://laravel.com/docs/5.3/collections#method-map
$allMessages = $allMessages->map(function ($message, $key) use($userId) {
if ($message->sender_id == $userId) {
$message->display_name = $message->receivername;
} else {
$message->display_name = $message->sendername;
}
return $message;
});
Solved by adding:
foreach ($allMessages as $message) {
if ($message->sender_id == $userId) {
$message->display_name = $message->receivername;
} else {
$message->display_name = $message->sendername;
}
}
You can surely use the laravel's LengthAwarePaginator.
Along with total count of collection you also need to pass the slice of collection's data that needs to be displayed on each page.
$total_count = $allMessages->count();
$per_page = 2;
$current_page = request()->get('page') ?? 1;
$options = [
'path' => request()->url(),
'query' => request()->query(),
];
Suppose you want 2 results per page then calculate the offset first
$offset = ($current_page - 1) * $per_page;
Now slice the collection to get per page data
$per_page_data = $collection->slice($offset, $per_page);
$paginated_data = new LengthAwarePaginator($per_page_data, $total_count, $per_page, $current_page, $options);
$paginated_data will have only limited number of items declared by $per_page variable.
If you want next two slice of data then pass api_request?page="2" as your url.
As I don't know which Laravel version you're using, taking Laravel 5.2 let me give you a smarter way to deal with this (if I get your problem correctly).
You can use Laravel's LengthAwarePaginatior(API Docs).
Don't use paginate method when you are bulding your query, instead of that use simple get method to get simple collection.
$userId = Auth::guard('api')->user()->user_id;
$allMessages = Conversation::join('users as sender', 'conversations.sender_id', '=', 'sender.user_id')
->join('users as reciver', 'conversations.recipient_id', '=', 'reciver.user_id')
->where('sender_id',$userId)->orWhere('recipient_id',$userId)
->orderBy('last_updated', 'desc')
->select('subject','sender_id','recipient_id','sender_unread','recipient_unread','last_updated','reciver.username as receivername','sender.username as sendername')
->get();
Now you can populate extra items into that collection based on your certain conditions like this.
if ($allMessages->sender_id == $userId ) {
// add new value to collection
} else {
// add new value to collection
}
Now use LengthAwarePaginator, to convert that populated collection into a paginated collection.
$total_count = $allMessages->count();
$limit = 20;
$current_page = request()->get('page');
$options = [
'path' => request()->url(),
'query' => request()->query(),
];
$paginated_collection = new LengthAwarePaginator($allMessages, $total_count, $limit, $current_page, $options);
The variable $paginated_collection now can be used to be sent in response. Hope this helps you to deal with your problem.