Manual paginator on merged collection - laravel-5

I have a merged collection I want to paginate and I can't seem to figure this out.
First off, the reason I need to create the paginator manually is because I fetch 2 collections first and merge them, like so:
$a = ModelA::all();
$b = ModelB::all();
$c = $a->merge($b);
Next step is to paginate this collection, I tried the Paginator and the LengthAwarePaginator
PAGINATOR
$page = 1;
$results = new Paginator($c, 2, $page);
Here I get the 2 first results in a paginator
$page = 2;
$results = new Paginator($c, 2, $page);
I still get the 2 first results, while i'd expect the third and fourth result (the collection is more then 2 elements long!)
LENGTHAWAREPAGINATOR
$page = 1;
$results = new LengthAwarePaginator($c, count($c), 2, $page);
Here I get a Paginator but the items contain all elements of the collection, no matter what page number i ask for (instead of the 2 i'm asking for)
Any ideas on what might be the problem?
Thank you in advance

According to the Laravel community this is intentional.
You should slice your items before passing it to the Paginator.

Related

Unable to merge Laravel collections

I am not sure why this isn't working. I am having to make a series of paginated calls to an external API and want to combine all the pages into one master collection. Right now each call (page) returns its own collection. I know about the merge() method but for some unknown reason it isn't working.
Here is what I got:
$master_collection = collect();
for ($x = 1; $x <= $total_pages; $x++) {
$page = ApiService::getPage($x); //Returns collection of elements from the requested page
$master_collection->merge($page);
}
dd($master_collection);
However the dump just returns the empty collection the was initialized but doesn't have any of the page collections merged in. I do know the page collections work fine because I can save $page to a new index in an array and dd() the array and can see an array of the individual collections.
You will have to save the merged collection into the $master_collection variable.
$master_collection = collect();
for ($x = 1; $x <= $total_pages; $x++) {
$page = ApiService::getPage($x); //Returns collection of elements from the requested page
$master_collection = $master_collection->merge($page);
}

Laravel Model::find() auto sort the results by id, how to stop this?

$projects = Project::find(collect(request()->get('projects'))->pluck('id')); // collect(...)->pluck('id') is [2, 1]
$projects->pluck('id'); // [1, 2]
I want the result to be in the original order. How do I achieve this?
Try $projects->order_by("updated_at")->pluck("id"); or "created_at" if that's the column you need them ordered by.
Referencing MySQL order by field in Eloquent and MySQL - SELECT ... WHERE id IN (..) - correct order You can pretty much get the result and have it order using the following:
$projects_ids = request()->get('projects'); //assuming this is an array
$projects = Project::orderByRaw("FIELD(id, ".implode(',', projects_ids).")")
->find(projects_ids)
->pluck('id'));
#Jonas raised my awareness to a potential sql injection vulnerability, so I suggest an alternative:
$projects_ids = request()->get('projects');
$items = collect($projects_ids);
$fields = $items->map(function ($ids){
return '?';
})->implode(',');
$projects = Project::orderbyRaw("FIELD (id, ".$fields.")", $items->prepend('id'))
->find($projects_ids);
The explanation to the above is this:
Create a comma separated placeholder '?', for the number of items in the array to serve as named binding (including the column 'id').
I solve this by querying the data one by one instead mass query.
$ids = collect(request()->get('projects'))->pluck('id');
foreach($ids as $id){
$projects[] = Project::find($id);
}
$projects = collect($projects);
$projects->pluck('id');
I have to do this manually because laravel collection maps all the element sorted by using ids.

Laravel 4 Paginantor::make, returning same data on every page

I have a set of documents, which I have stored in an array $resultset. I want to apply laravel Paginator::make method on it.
In Controller:
$total_resultset = count($resultset);
$perPage = 10;
$currentPage = Input::get('pageno',1);
$offSet = ($currentPage * $perPage) - $perPage;
$pagedData = array_slice($resultset, $offSet,$perPage, true);
$resultset = Paginator::make($pagedData, $total_resultset, $perPage);
and in view:
#foreach($resultset as $r)
$r->title
#endforeach
{{ $resultset->links() }}
The problem is, when I use $PagedData in Paginator::make it returns 10 docs per page. But all of the pages have same set of documents, rather than different docs, the page=2 does not show new set of docs. And when I use $resultset in Paginator::make it returns me all of the documents rather than only 10.
I can't use Eloquent or DB query as the $resultset has docs, based on different queries. I'm confused where I'm wrong.
P.S I've been trying to solve the issue since last two days, but didn't get successful. Please help me out, any kind of help would be appreciated.
Thanks in advance
$total_resultset = count($resultset);
$perPage = 10;
$currentPage = Input::get('pageno',0); //if you use default 1 you will skip 10 results
$offSet = ($currentPage * $perPage);// Ex: (1*10)= 10 - 10 = 0 you are not moving
$pagedData = array_slice($resultset, $offSet,$perPage, false);//maybe you are using Eloquent? you can use take() and skip() if is the case; and you was preserving the array keys in slice so never move really
$resultset = Paginator::make($pagedData, $total_resultset, $perPage);
hope helps :)

Take first 5 from previous query

If I have a query like:
$posts = Post::all();
How can I select the first 5 results from that previous query? I tried the following, but it didn't work:
$first_five = $posts->take(5)->get();
Or in other words, how do I get the first 5 elements in the returned object?
I know you can use $first_five = Post::all()->take(5)->get();
I am guessing you may be able to do this also:
$posts = Post::all();
$first_five = $posts->take(5);
Also, have a look into pagination.

Merging multiple objects which uses same id

I'm trying to merge multiple objects (like Receipts, Reports, etc) with Collection->merge().
This is the code I used:
$receipts = Receipt::all();
$reports = Report::all();
$collection = $receipts->merge($reports);
This is the result:
The above screenshot shows two elements, but the third element is missing because it has the same id (id: "1") as the first one. What I'm trying to achieve is to display all three of them as a collection.
EDIT:
I need the result to be objects (collection) because I also use the code on my view, where I check the class to determine what to display. Also, I use this function to sort the objects in the collection.
$collection->sort(function($a, $b)
{
$a = $a->created_at;
$b = $b->created_at;
if ($a === $b) {
return 0;
}
return ($a > $b) ? 1 : -1;
});
I know that this is an old question, but I will still provide the answer just in case someone comes here from the search like I did.
If you try to merge two different eloquent collections into one and some objects happen to have the same id, one will overwrite the other. I dunno why it does that and if that's a bug or a feature - more research needed. To fix this just use push() method instead or rethink your approach to the problem to avoid that.
Example of a problem:
$cars = Car::all();
$bikes = Bike::all();
$vehicles = $cars->merge($bikes);
// if there is a car and a bike with the same id, one will overwrite the other
A possible solution:
$collection = collect();
$cars = Car::all();
$bikes = Bike::all();
foreach ($cars as $car)
$collection->push($car);
foreach ($bikes as $bike)
$collection->push($bike);
Source: https://medium.com/#tadaspaplauskas/quick-tip-laravel-eloquent-collections-merge-gotcha-moment-e2a56fc95889
I know i'm bumping a 4 years old thread but i came across this and none of the answers were what i was looking for; so, like #Tadas, i'll leave my answer for people who will come across this. After Looking at the laravel 5.5 documentation thoroughly i found that concat was the go-to method.
So, in the OP's case the correct solution would be:
$receipts = Receipt::all();
$reports = Report::all();
$collection = $receipts->concat($reports);
This way every element in the Report collection will be appended to every element in the Receipts collection, event if some fields are identical.
Eventually you could shuffle it to get a more visual appealing result for e.g. a view:
$collection->shuffle();
Another way to go about it is to convert one of your collections to a base collection with toBase() method. You can find it in Illuminate\Support\Collection
Method definition:
/**
* Get a base Support collection instance from this collection.
*
* #return \Illuminate\Support\Collection
*/
public function toBase()
{
return new self($this);
}
Usage:
$receipts = Receipt::all();
$reports = Report::all();
$collection = $receipts->toBase()->merge($reports);
You could put all collections in an array and use this. Depends on what you want to do with the collection.
$list = array();
$list = array_merge($list, Receipt::all()->toArray());
$list = array_merge($list, Report::all()->toArray());

Resources