Laravel 5.1 eloquent query orderby external data and paginated - laravel

I'm trying to get data from an Eloquent query (that works), then order the data, then paginate the data.
I had the code ordering by date, then paginating, but now I want to order by facebook API obtained data (I get the data correctly).
The thing is I don't know what I should do first (paginate or ordering):
If I paginate first, I don't know how to order the data since the object is LengthAwarePaginator and doesn't have any orderBy method.
If I order first, I get a collection object and can't use ->paginate($perPage) to do the pagination.
This is the code:
$posts = Post::with('User')->
where('created_at', '<', new \DateTime)->
paginate($perPage);
$counter = 0;
foreach ($posts as $post) {
$url = 'http://myweb.com/' . $post['id'];
$fb_stuff = json_decode(file_get_contents('https://api.facebook.com/method/links.getStats?urls=' . $url . '&format=json'), true);
$posts[$counter]['share_count'] = $fb_stuff[0]['share_count'];
$counter++;
}

If you need to sort by some value that is not stored in the database then the only option is to fetch all records from the database, fetch the sorting data from external source (Facebook API), sort it by user defined function (see http://php.net/manual/en/function.usort.php) and then paginate.
Once you have your data sorted you can easily get the paginated version by creating a Paginator object:
$paginator = new \Illuminate\Pagination\Paginator($posts, $perPage, $currentPage);
Anyway, this solution will be quite heavy as you'll need to fetch data from Facebook API every time you want a sorted list of posts. I doubt you need real time data so I suggest to store share_count in your posts table and refresh it on regular basis, e.g. by running a scheduled laravel command.

Related

How can i sort a laravel's elequent list by another ids

I have a challenge that if I want to sort the records when getting using Laravel's ORM based on a list of IDs, how should I do it?!!!!!!!
I mean :
Suppose we have a table called users, which contains 100 records and each record has a unique ID.
We also have an array of IDs.
$ids = [4,1,2,3]
Now I want to get the list of users, but only the users who are first in the ids array and secondly according to the same order as they are listed in this array.
User::whereIn('id' , $Ids)->sortBy('id',$ids)->get();
Can you think of a solution to do this?
User::whereIn('id' , $Ids)->sortBy('id',$ids)->get();
The collections sortBy() function can take a custom call back this way:
$users = User::whereIn('id', $Ids)->get()
->sortBy(function($user, $key) use($ids) {
return array_search($user->id, $ids);
});
This will sort your collection according to the given array.
You can also reference the docs for more information.
Note that the sortBy() function must act upon a collection, which means that the get() function must come before it.

Is Laravel sortBy slower or heavier to run than orderBy?

My concern is that while orderBy is applied to the query, I'm not sure how the sortBy is applied?
The reason for using sortBy in my case is because I get the collection via the model (i.e. $user->houses->sortBy('created_at')).
I'm just concerned about the performance: is sortBy simply looping each object and sorting them?, or is Laravel smart enough to simply transform the sortBy into an orderBy executed within the original query?
You need orderBy in order to perform a SQL order.
$user->houses()->orderBy('created_at')->get()
You can also eager load the houses in the right order to avoid N+1 queries.
$users = User::with(['houses' => function ($query) {
return $query->orderBy('created_at');
}])->get();
$orderedHouses = $users->first()->houses;
The sortBy method is applied to the Collection so indeed, it will looping each objects.
The orderBy() method is much more efficient than the sortBy() method when querying databases of a non-trivial size / at least 1000+ rows. This is because the orderBy() method is essentially planning out an SQL query that has not yet run whereas the sortBy() method will sort the result of a query.
For reference, it is important to understand the difference between a Collection object and a Builder object in Laravel.
A builder object is, essentially, an SQL query that has not been run. In contrast, a collection is essentially an array with some extra functionality/methods added. Sorting an array is much less efficient than pulling the data from the DB in the correct format on the actual query.
example code :
<?php
// Plan out a query to retrieve the posts alphabetized Z-A
// This is still a query and has not actually run
$posts = Posts::select('id', 'created_at', 'title')->orderBy('title', 'desc');
// Now the query has actually run. $posts is now a collection.
$posts = $posts->get();
// If you want to then sort this collection object to be ordered by the created_at
timestamp, you *could* do this.
// This will run quickly with a small number or rows in the result,
// but will be essentially unusable/so slow that your server will throw 500 errors
// if the collection contains hundreds or thousands or objects.
$posts = $posts->sortBy('created_at');

Laravel append overall total value on pagination result

I have a Laravel query using pagination.
I want to be able to return the result based on the pagination but also get the overall total of the query and append this to the return. So for example the pagination is set to 5 but the overall total might be 20.
$query = Model::paginate(5);
$queryTotal = $query->total();
$query->append($queryTotal);
return $query;
The Laravel Paginator does this already.
You can see that when serializing results to JSON there is a total key which represents all rows matching that query.
You can also see there is a total method available from the paginator:
$results->total()
Along side other methods that can be found in the Pagination Docs
$query = Model::paginate(5);
return $query;
You can access overall total using
{{ $query->total() }}
For more Info read Paginator instance
The paginate function returns a LengthAwarePaginator object. It simply not possible to add another field to this object.
Your best option is to manually create a new collection in which you merge the LengthAwarePaginator with your newly added data.
An example would be:
$query = Model::paginate(5);
$addition = collect(['totalResult' => $query->total()]);
$queryData = $addition->merge($query);
return $queryData;
Naturally, if you just return the LengthAwarePaginator object, you can simply call the total() function, if you use it in your blade files for example.
Hope this helps!

Join query in laravel with data manipulation send to view

I am having 2 tables, users and profiledetails
I am able to run Join query and access the data and send to view.
But when I am manipulation the field 'dob' (date format) in profiledetails table. No Success, Please check the code below ,
webpagesConroller:
$users = DB::table('users')
->join('profiledetails', 'users.id', '=', 'profiledetails.user_id')
->select('users.*', 'profiledetails.dob')
->get();
$age = $users->dob->diffInYears(Carbon::now());
return view('webpages.index',compact('users'))->with($age);
View:
<li class="cate_head">Age : {{ $age}}</li>
Error:
Trying to get property of non-object
I have model Profiledetails added the mutators as below,
public function getAge(){
return $this->dob->diffInYears(Carbon::now());
}
public function getDOB(){
return $this->dob->format('d-m-Y');
}
Can I not use this method on another controller for Ex- webpagesController, If yes How.
Since you are using a raw query, the data returned is not an object but an array with the data.
Also you did not limit the query, meaning it could return multiple rows.
You'll probably need to get the data from the $users as:
//with the possibily of multiple rows. Index is the row which has to be used
$users[index]['dob']
//when you are sure it only returns one row
$users[0]['dob'] // or $users['dob'] not sure as of the moment
You want to check the differences with Carbon.
So you will probably need to make a Carbon object of the dob result and check it against the Carbon::now.
$age = Carbon::createFromFormat('Y-m-d', $users[0]['dob'])->diffForHumans();
// diffForHumans() uses the local time
Note that this only gets the age for one row (the index or the first because of [0]).
If you want to do it for every row use a for or foreach where you set the $users->age for every $user. But as these are not object/models but raw data, this will not work.
Hope this helps your question a bit

Doctrine toarray does not convert relations

I followed doctrine documnetation to get started. Here is the documentation.
My code is
$User = Doctrine_Core::getTable("User")->find(1);
when I access relations by $User->Phonenumbers, it works. When I convert User object to array by using toArray() method, it does not convert relations to array. It simply display $User data.
Am I missing something?
By using the find method you've only retrieved the User data which is why the return of toArray is limited to that data. You need to specify the additional data to load, and the best place to do this is usually in the original query. From the example you linked to, add the select portion:
$q = Doctrine_Query::create()
->select('u.*, e.*, p.*') // Example only, select what you need, not *
->from('User u')
->leftJoin('u.Email e')
->leftJoin('u.Phonenumbers p')
->where('u.id = ?', 1);
Then when toArray'ing the results from that, you should see the associated email and phonenumber data as well.
I also noticed an anomaly with this where if you call the relationship first then call the ToArray, the relationship somehow gets included. what i mean is that, taking your own eg,
$User = Doctrine_Core::getTable("User")->find(1);
$num= $User->Phonenumbers->office; // assumed a field 'office' in your phone num table
$userArray = $user->toArray(true);
In the above case, $userArray somehow contains the whole relationship. if we remove the $num assignment it doesn't.
am guessing this is due to doctrine only fetching the one record first, and it's only when you try to access foreign key values that it fetches the other related tables

Resources