Paginate related models - php

I would like to paginate 2 models: Trip which hasMany PartialTrip.
The Trip model:
public function PartialTrips()
{
return $this->hasMany('PartialTrip', 'main_trip_id');
}
The tricky part is that, in the object that contains the queried information, I want to have each PartialTrip under the Trip that owns it. So, f.e.: query Trip -> query PartialTrip with whereMainTripId = the id of the current Trip -> paginate(5).
This way, when I list them, the partial trips will not be at the end of the array object, but each of them will be directly after its parent trip.
Update
I am doing this (just as a test to see whether the ::with('PartialTrips') works:
$trips = Trip::with('PartialTrips')
->select('id', 'driver_user_id', 'route_from', 'route_to', 'start_date')
->get();
var_dump(count($trips));
and it returns me the just the number of the Trips that I have (I counted how many trips and partial trips I have in the DB).
->toSql() on that query yields me the following:
"select 'id', 'driver_user_id', 'route_from', 'route_to', 'start_date' from 'trips'"
so I guess the ::with('PartialTrips') does not really work in this case...

This is the answer where trip_id come from
Take note that Eloquent assumes the foreign key of the relationship
based on the model name.
more detail: http://laravel.com/docs/4.2/eloquent#one-to-one
When you are using hasMany relationship without specific foreign key, the model is assume to use trip_id as a foreign key (table name + id). Anyway you can overwrite it. And you model might look like
//Trip model
public function PartialTrips()
{
return $this->hasMany('PartialTrip', 'main_trip_id'); // second parameter is your foreign key
}
more details: http://laravel.com/docs/4.2/eloquent#one-to-many
And you can get all the trips with partial trips by passing $trips variable to your view and render them like this
#foreach ($trips as $trip)
<div> Your trip id: {{ $trip->id }}</div>
<div> -- Partial Trips </div>
#foreach ($trip->partialTrips as $partialTrip)
<div> Partial trip id: {{ $partialTrip->id }}</div>
#endforeach
#endforeach
Note: when you are using with() (eager loading), $partialTrips will be nested inside $trips. That why you get only number of $trips when using count($trips). But you can get partialTrips by using code above.
source: http://laravel.com/docs/4.2/eloquent#eager-loading

Related

Laravel retrieves too many model on a simple paginate query with loaded relations

I have a simple database with couple of tables. orders - the main table, about 16_000 records. Each order has payments. The order_payments table is about 17_000 records. Also each could have zero or more travellers in a order_tourists table - 33_000 rows.
The Order model looks like this
namespace App;
class Order extends \Illuminate\Database\Eloquent\Model
{
public function payments()
{
return $this->hasMany('App\OrderPayment');
}
public function tourists()
{
return $this->hasMany('App\OrderTourist');
}
}
I have a simple task to display a paginated list of all orders. Each item in this list should display some order's information (from the orders table), the sum of all payments and names of all travellers.
The first that came to my mind was something like this.
Get all orders with payments and travellers and pass this data to a view
Route::get('search_offers', static function () {
$orders = \App\Order::query()->with(['payments', 'tourists']);
return view('orders', [
'orders' => $orders->paginate(),
]);
});
And my orders.blade.php is pretty straightforward
<ol>
#foreach($orders as $order)
<li>
{{$order->request_id}}; {{ $order->payments->sum('amount') }}; {{ $order->tourists->implode('fullName', ', ') }}
</li>
#endforeach
</ol>
However, even the number of queries is very small I have a lot of manipulations with models. I add a custom hook to track all eloquent.* events and in first case there are more than 23_000 of such events.
Then I try to remove the loading of relations so for each order Laravel have to run a separate query to get all order's payments and travellers. I simply did like this in my controller
return view('orders', [
'orders' => \App\Order::query()->paginate(),
]);
So, I get more queries but page speed improves and a number of affected models significantly decreased
To track affected models I add a listner in a AppServiceProvider::boot method and output a AppServiceProvider::$hydratedModels value in a debugbar panel
Event::listen('eloquent.*', static function ($event) {
if (strpos($event, 'eloquent.retrieved') !== false) {
AppServiceProvider::$hydratedModels++;
}
});
So, my question how it possible that so many models are retrieved even if I display only 15 items. Looks like Laravel somehow get all of them, processed but retruns only those for a required page.
Right now it's not a big deal to run extra queries to fetch some data but I'm wondering maybe I'm doing something wrong and it's possible to load relations but do not cause Laravel to retrieve all models.
I found the issue. Thanks, #dparoli for a suggestion.
My models have a primary key as UUID. But I did not change a Model::$keyType so internally Laravel cast it into an integer and queries look like this.
Instead of a UUID string, I got integers as an order_id for related records.
So, I add protected $keyType = 'string'; and everything starts work as expected.
PS. Although, I'm wondering how it works before.

Laravel 5 get relation data

I've made application with Laravel 5.5 and I use MySQL database. I have 2 tables
people: id, name, homeplanet_id
planets: id, name
And foreign key people.homeplanet_id references planets id
I also have 2 models: Person and Planet, and PersonController.
I can get Person's data in controller by using
Person::find($id)->getAttributes()
but it is like
[
id => 1
name => Name
homeplanet_id => 1
]
How can I get data look like next
[
id => 1
name => Name
homeplanet_id => Planet_name
]
You may need a transformation layer that sits between your Eloquent models and the JSON responses that are actually returned to your application's users.
https://laravel.com/docs/5.5/eloquent-resources
So, if you want to get JSON, use resources. If you want this data format for Blade views, it's a bad idea to transform it. Just work with the data in Blade template:
{{ $person->name }} lives on the planet {{ $person->planet->name }}
Where planet is name of relationship defined in Person model:
public function planet()
{
return $this->belongsTo(Planet::class, 'homeplanet_id')
}
Use with() method,
Something like
$person = Person::where(array())->with('planet')->get(); //You can fetch all related data by passing multiple values for method with()
To print
{{$person->planet->name}} //where planet indicated the relation on person model as follows
Where planet indicated the relation on Person model.
In our case
Getting data
$person = Person::where(array('id'=>$id))->with('planet')->get();
And in Person model do something like following to fetch planet using with() method:
Person extends model{
public function panet(){
//your relation one to one, one to many etc.....whatever
}
}
Try exploring eager loading in Laravel (eloquent) and scoped query too.
Visit the link for Eager loading and everything will be done by the ORM itself.Link

laravel eloquent - match data stored within array

I have a multiselect form to store multiple client_id in contact table:
{!! Form::select('client_id[]', $clients, null, ['multiple'=>true]) !!}
When I show an individual client, I wish to show the related contacts.
My client Model has as a 1:many relationship defined as:
public function contact()
{
return $this->hasMany('App\Models\contact');
}
usually, for non array items, I would use:
$contacts = $client->contact()->get();
to fetch related contacts, but as the client_id is stored as an array in my contact table, how to I fetch this data?
I really think you just want this:
$contacts = $client->contact()->lists('id', 'name')->toArray();
Note that you didn't specify your version of Laravel, but in 5.1+ lists returns a Illuminate\Support\Collection instance, but in previous versions it returns an Array.
Sidenote
Your hasMany relationship should be defined as plural and not singular:
public function contacts()
This is by no means a requisite, but it better defines your relationship type.

Laravel 5 | For Loop w/Multiple Relations

I've setup a few model relationships and I'm trying to feed that data to a page that's displays a table with different columns to show the information. I've seeded 3 Tables (customers, jobs, steps) with Faker dummy data just for testing. How can I pull in the "Steps" table column data? My controller defines both (2) variables for the Job & Step model but I'm unsure how to pull in Step to the display. Do I have to do an array for the the variables called in the controller?
Here's my controller setup
public function index()
{
$jobs = Job::all();
$steps = Step::all();
return view('jobs.home', compact ('jobs', 'steps'));
}
Here's my blade setup
#foreach ($jobs as $job)
<tr>
<td>{{ $job->number }}</td>
<td>{{ $job->customer->name }}</td>
<td>NEED TO ADD STEPS COLUMN Here</td>
</tr>
#endforeach
There are several ways that this could be done, depending on what kind of data your "Step" model is providing for you.
If you just need 1 item from the "Step" model, you could first find just the specific data you need from the "Step" model instead of pulling in everything with Step::all(); for instance, you could use one of the more specific methods under "Collection Methods" that just return one item: https://github.com/susanBuck/notes/blob/master/05_Laravel/11_Databases_Eloquent_Collections.md
If you need more than 1 item, then you can use a more specific Collection method as well; however, you are going to have to loop through it to extract out each individual item. You will have to use "if" statements to determine which item is the one you want and then pull in its column data the same way you did with the "jobs" item like $job->number.
For your second question, you don't have to use an array to return the items from the controller. Just get the value you want there and return that if you like. For instance, to get the first "step" (if you just wanted the first one) then in the Controller do this to return a variable instead of an array:
public function index()
{
$firstStep = Step::first();
return view('jobs.home')->with('firstStep', $firstStep);
}
and then in your Blade file, you can access the data column rows just like you did with your $job variable:
{{ $firstStep->someColumn}}

Filtering Eloquent ORM query by relation

I need to filter my Eloquent query by relation. I have following relations:
User **has many** achievements
Game **has many** achievements
Now I need to filter achievements of user A to those gained in game B. This can be done this way:
$user->achievements()->whereGameId($game->id)
This is fine, but can I use $game object directly instead of filtering by ugly ID column?
Injecting $game object into a closure method and doing querying while eager loading is a way better approach in my opinion.
whereHas and orWhereHas is for limiting the result by querying relations.
$game = Game::find($gameID);
//magic happens here
$users = User::whereHas('achievements', function($query) use($game){
$query->where('gameId', $game->id);
})
->with('achievments') //if you want to have them inside collection
->get();
This limits both User (main collection result) along with achievments (relation).
Here's a detailed information (Querying Relations).
If you just want to filter achievments, but not User collection, you just need a closure function on eager loading:
$game = Game::find($gameID);
//magic happens here
$users = User::with(
array('achievements' => function($query) use($game){
$query->where('gameId', $game->id);
}))
->get();
This just limits achievments relation, but does not affect User (main collection)
Here's a detailed description (Eager Loading Constains)
edit:
So here's the PR https://github.com/laravel/framework/pull/4267 that lets you do this:
// 'game' being relation on the Achievement model
$user->achievements()->hasIn('game', $game);
// or equivalent dynamic call
$user->achievements()->hasInGame($game);
// of course you may use it like any other Builder method, for example:
User::hasInAchievements($achievement)->get();
No straightforward way to do this, unless the Game is related somehow to the User.
To be clear, say User and Game is related, then you can access achievements on the games Collection injecting Game model instead of 'ugly' $game->id:
$user->games->find($game)->achievements;

Categories