Eager Loading with hasManyThrough with Laravel 4.1 - laravel-4

I have the following table structure;
users
user_id | email | name
==================================
1 | tom#example.org | Tom
2 | pete#example.org | Pete
trainings
training_id | user_id | title
=================================================
1 | 1 | First training of Tom
2 | 1 | Second training of Tom
3 | 2 | First training of Pete
4 | 2 | Second training of Pete
calendar
calendar_id | training_id | when_date
=================================================
1 | 1 | 2014-08-01 10:00:00
2 | 1 | 2014-08-08 10:00:00
3 | 1 | 2014-08-15 10:00:00
4 | 2 | 2014-08-01 18:00:00
5 | 2 | 2014-08-03 18:00:00
6 | 2 | 2014-08-05 18:00:00
7 | 3 | 2014-08-12 16:30:00
8 | 4 | 2014-08-09 22:30:00
I have prepared three models for this table structure
class User extends Eloquent
{
public function trainings()
{
return $this->hasMany('Training');
}
public function events()
{
return $this->hasManyThrough('Calendar', 'Training', 'user_id', 'training_id');
}
}
class Training extends Eloquent
{
public function user()
{
return $this->belongsTo('User');
}
public function events()
{
return $this->hasMany('Calendar');
}
}
class Calendar extends Eloquent {
public function training()
{
return $this->belongsTo('Training');
}
}
I want to get latest N calendar events ordered by the nearest event first for a selected user (user_id = 1) with all training details of this calendar event. When I do the following, the eager loading doesn't seem to work.
$events = User::with('events', 'trainings')->find($userId);
foreach ($events as $event) {
echo $event->training->title; // runs a separate query to get the title of the training
}
If there are 100+ events for a given user, it runs 100+ queries.
How can I achive this with eager loading?

Shouldn't you fetch the user first and then query through that instance?
$user = User::find(userId);
$events = $user->with('events', 'trainings');
or in one go:
User::find(userId)->with('events', 'trainings');

You could achieve that by using lazy eager loading. In your case:
$user = User::with('events')->find($userId);
$user->events->load('training');
foreach ($user->events as $event) {
echo $event->training->title;
}

Related

Is there a way to join table B to A in laravel but table B as array without duplicating rows from table A please?

I have two tables:
Table A:
Users
id | Name
1 | Sam
2 | Tom
Table B:
Phones
id | Name | user_id
1 | Nokia | 1
2 | Samsung | 1
3 | Motorola | 1
4 | OnePlus | 2
When I joinLeft I have duplicate Users rows as:
Users.id | Users.Name | Phones.id | Phones.Name | Phones.user_id
1 | Sam | 1 | Nokia | 1 |
2 | Sam | 2 | Samsung | 1 |
3 | Sam | 3 | Motorola | 1 |
4 | Tom | 4 | OnePlus | 2 |
How could I build the query, to have array instead duplicated Users rows please? Data is send as JSON.
I would expect data:
[
{
{
Name: "Sam",
Phones: {
Name: "Nokia",
},{
Name: "Samsung",
},{
Name: "Motorola",
}
},{
Name: "Tom",
Phones: {
Name: "OnePlus",
}
}
]
Any help would be much appreciated.
Thank you.
Laravel Eloquent way
Run php artisan make:model Phones for phone model. you may have user model already if not do same for user php artisan make:model User
now define relation insede User.php file
use App/Model/Phone //use Phone model
public function phones(){
return $this->hasMany(Phone::class);
}
now you can eggerload the relation like below:
$user = User::with('phone')->get();
and you can get: user_name = $user->name
user_phones = $user->phone // this will return array of phones
you can loop the phone insode foreach loop. Hope this will help.
If you have Phone and User models, these are the things you should do.
User.php (Model)
public function phones()
{
return $this->hasMany(Phone::class, 'user_id', 'id');
}
-> The first parameter means the model to which the database we want to reach is connected.
-> The second parameter refers to the column that we will match in the database to which the model we specified in the first parameter is connected.
-> The third parameter specifies which column in the table our current model depends on will match with the column in our second parameter.
Controller:
public function list()
{
$users = User::with('phones')->get();
}
Doing this will suffice.

How to use groupBy() on relation

Currently having trouble of using groupBy in nested relation on laravel. I have 3 Tables and I want to group the result base on the CountryTbl value. Here are the tables.
UserTbl
----------------------------------
id | name | branch_id |
----------------------------------
1 | Joseph | 1 |
2 | Manuel | 1 |
3 | Margaret | 3 |
----------------------------------
BranchTbl
----------------------------------
id | branch_name | country_id |
----------------------------------
1 | Pampanga | 1 |
2 | Manila | 1 |
3 | California | 2 |
----------------------------------
CountryTbl
------------------------
id | country_name |
------------------------
1 | Philippines |
2 | United States |
------------------------
This is my Model
UserModel
public function branch()
{
return $this->belongsTo('App\Branch');
}
BranchModel
public function country()
{
return $this->belongsTo('App\Country');
}
CountryModel
Now, In the table shown above, I want to get all the users and group them by country.
Here is what I've tried.
public function getAllUsers(){
$users = User::with('branch')
->with(['branch.country' => function($q){
return $q->groupBy('country_name');
}])
->get();
return $users;
}
My code doesn't work. It always returns me an error saying:
Syntax Error or Access Violation
Try this:
User::with(['branch', 'branch.country' => function($q) {
return $q->groupBy('CountryTbl.country_name');
}])
or
User::with(['branch', 'branch.country'])
->get()
->groupBy('branch.country.country_name');
You may try the below code
$data = DB::table('country')
->join('branch', 'country_id', '=', 'country.id')
->join('user', 'branch_id', '=', 'branch.id')
->select('country.country_name','user.name','branch.branch_name')
->groupBy('country.country_name')
->orderBy('country.country_name','ASC')
->get();

Laravel. How to get relationships where foreign key is an array

I am trying to retrieve database rows with their relationships. However, the local key is an array. Let me explain using an example.
Lets say I have a table of countries and a table of pages. Each country can have many pages. Each page can belong to multiple countries. I do not have the flexibility to change this schema.
pages
+-------------+-----------+
| id | name | countries |
+-------------+-----------+
| 1 | Page 1 | 1 |
+-------------+-----------+
| 2 | Page 2 | 1,2,3 |
+-------------+-----------+
| 3 | Page 3 | 4,5,6 |
+-------------+-----------+
countries
+----+----------------+
| id | name |
+----+----------------+
| 1 | United States |
+----+----------------+
| 2 | United Kingdom |
+----+----------------+
| 3 | Germany |
+----+----------------+
| 4 | France |
+----+----------------+
| 5 | Hong Kong |
+----+----------------+
| 6 | Thailand |
+----+----------------+
| 7 | Belgium |
+----+----------------+
| 8 | Singapore |
+----+----------------+
My model and controller look something like:
country
public function pages()
{
return $this->hasMany(Page::class, 'id', 'countries');
}
MemberController.php
$countries = Country::with('pages')->get();
This is returning all countries, but only Page 1 contains any relationships.
Is there a way to retrieve relationships using a whereIn approach so all three countries will return appropriate pages?
Thanks in advance
Since Page can belong to many Countries, you need to create a pivot table called country_page and remove the countries column.
Then define two belongsToMany() relationships in both models:
public function pages()
{
return $this->belongsToMany(Page::class);
}
If you're not following Laravel naming conventions listed in my repo and you gave the pivot name a custom name, define it too:
public function pages()
{
return $this->belongsToMany(Page::class, 'custom_pivot_table');
}
Something like this ?
$datas = Pages::where( ##your conditions### )->get()->inArray();
$countries = Countries::pluck('name','id'); // return array of id=>name
foreach($datas as $key=>$data) {
$c = [];
foreach(explode(',',$data['countries']) as $country_id) {
$c[]=$countries[$country_id];
//or
$c[]= ['id'=>$country_id,'name'=>$countries[$country_id]];
}
$datas[$key]['countries']= $c;
}

How to whereHas when use belongsToMany relation

I got a problem, When try to use whereHas in this case
Table users.
----------------
| id | name |
----------------
| 1 | AAA |
| 2 | BBB |
| 3 | CCC |
----------------
Table subjects.
------------------
| id | title |
------------------
| 1 | Subject 1 |
| 2 | Subject 2 |
------------------
Table subject_user.
------------------------
| user_id | subject_id |
------------------------
| 1 | 1 |
| 2 | 1 |
| 1 | 2 |
| 3 | 2 |
------------------------
in Subject Model
...
public function User()
{
return $this->belongsToMany(User::class, 'subject_user');
}
...
When I want to find subject by user_id with this query.
In this case Auth::id() == 1 and $request->user_id == 3
$subject = Subject::whereHas('User', function ($query) use ($request) {
$query->whereIn('user_id', [Auth::id(), $request->user_id]);
})->get();
With this query, I got subjects 1 and 2. That was a wrong result. That must got only subject 2.
Then I try this.
$subject = Subject::whereHas('User', function ($query) use ($request) {
$query->where('user_id', Auth::id())->where('user_id', $request->user_id);
})->get();
It would not get any subjects.
What query do I use in this case to get only subject 2.
#Lloople from your answer, I got an idea.
$subject = Auth::user()->Subject()->whereHas('User', function ($query) use ($request) {
$query->where('user_id', $request->id);
})->first();
Why not doing it backwards? You already have the logged in user, so if you define the relationship in the User model you can do
$subjects = auth()->user()->subjects;
Anyway, you don't need to check double the Auth::id()and $request->user_id. In fact, I'm not sure you can do this last one.
Edit after comments
$subjects = Subject::whereBetween(auth()->user()->id, $request->user_id)->get();
You will need to change the order from the inputs, in case $request->user_id is less than auth()->user()->idor it wouldn't work.

Laravel 5 - Eloquent JOIN confusion

I have two simple tables, cats and breeds:
mysql> select * from cats;
+----+--------+---------------+----------+---------------------+---------------------+
| id | name | date_of_birth | breed_id | created_at | updated_at |
+----+--------+---------------+----------+---------------------+---------------------+
| 1 | Rita | 2008-07-06 | 1 | 2015-05-09 20:40:49 | 2015-05-09 20:50:20 |
| 2 | Muni | 1992-05-15 | 3 | 2015-05-09 20:50:54 | 2015-05-09 20:50:54 |
| 3 | Hector | 2005-01-23 | 4 | 2015-05-09 21:08:23 | 2015-05-09 21:08:23 |
+----+--------+---------------+----------+---------------------+---------------------+
3 rows in set (0.00 sec)
mysql> select * from breeds;
+----+------------+
| id | name |
+----+------------+
| 1 | Domestic |
| 2 | Persian |
| 3 | Siamese |
| 4 | Abyssinian |
+----+------------+
4 rows in set (0.00 sec)
All I want to do is get a list of the cats that are of a certain breed. In the cats table, breed_id is a FK that points to the PK of the breeds table. The example code in my book which is supposed to return a view with all cats of a specific breed returns no results:
The url I am trying is /cats/breeds/Domestic
The view that is generated uses this logic in the routes.php file:
Route::get('cats/breeds/{name}', function($name)
{
$cats = Furbook\Cat::with('breeds')
->whereName($name)
->get();
dd($cats);
});
When I dd($cats) I get zero results even though I have a cat with a domestic breed_id of 1. What have I done wrong here?
EDIT:
I can get it to work with the following hacky code, which first gets the id from the breeds table by querying against the name field, then queries the cats table with that id:
$breed = Furbook\Breed::whereName($name)->get();
$id = $breed[0]['attributes']['id'];
$cats = Furbook\Cat::whereId($id)->get();
How do I make this into one Eloquent query? I see no examples for this kind of query on the Laravel site.
For more information this is how the models look:
class Breed extends Model {
public $timestamps = false;
public function cats()
{
return $this->hasMany('Furbook\Breed');
}
}
class Cat extends Model {
protected $fillable = [
'name',
'date_of_birth',
'breed_id',
];
public function breed()
{
return $this->belongsTo('Furbook\Breed');
}
}
You're asking the system to give you all cats with a name of Domestic - not all cats of the breed name Domestic.
Assuming your model relationships are in order, you could do e.g.
$cats = Furbook\Breed::whereName($name)->first()->cats;
Also, my Burmese cat just hit the monitor; I think she's upset that she's not in the breeds list.

Resources