How to have multiple layers of hasMany() relation for Laravel 5? - php

Let say I have tables like the following:
order
order_id | customer_name | delivery_address
1 | David | ABC Road, DEF district
...........Some other record ..............
order_dish
order_id | dish_id | dish_name
1 | 1 | chicken wing
1 | 2 | meat pie
1 | 3 | onion ring
...........Some other record ..............
dish_ingredient
dish_id | ingredient
1 | chicken wing
1 | salt
1 | oil
2 | pork meat
2 | flour
3 | onion
...........Some other record ..............
In Laravel 5, if I want to get all dishes of an order, I can do:
$order = Order::hasMany('App\OrderDish', 'order_id')->get();
Is there a way I can get all ingredients needed for an order in one line?

Yes. The "has-many-through" relationship provides a convenient shortcut for accessing distant relations via an intermediate relation. You define it using hasManyThrough() method of your model.
First, define the relation in your Order model:
class Order extends Model {
public function ingredients() {
return $this->hasManyThrough('App\DishIngredient', 'App\OrderDish', 'order_id', 'dish_id');
}
}
With such a relation defined, you'll be able to get ingredients for given order with:
$ingredients = $order->ingredients;

Related

Laravel GroupBy with Multiple Joins

I'm having problems with getting correct data in a three-way join.
EDIT: Sample table structure
Jobs Table
id | name | country | country_slug | city
1 | Job1 | Canada | canada | Ontario
2 | Job2 | South Africa | south-africa | Durban
Cheers Table
id | rep | jobs_id
1 | 14 | 2
2 | 9 | 1
3 | 12 | 2
4 | 23 | 1
Categories Table
id | name
1 | PHP
2 | Laravel
3 | Javascript
4 | Go
category_job pivot Table
id | category_id | job_id
1 | 2 | 1
2 | 2 | 2
3 | 1 | 1
4 | 3 | 2
5 | 4 | 1
Here is an example of what my models look like:
Job
public function categories(){
$this->belongsToMany(Category::class)
}
public function cheers(){
$this->hasMany(Cheer::class)
}
Category
public function jobs() {
$this->belongsToMany(Job::class)
}
Cheer
public function job() {
return $this->belongsTo(Job::class);
}
This is what I'm trying to achieve:
Group jobs by country
Get count of how many jobs are in each country (job_count)
Get count how many cities (distinct) in each group (city_count)
Sum (rep) in cheers relation table for each group
Get the most popular category for each country group via Categories relationship
This is my code so far:
$result = Job::
selectRaw(
"jobs.country,
jobs.country_slug,
COUNT('jobs') as job_count,
COUNT(DISTINCT city) as city_count,
SUM(rep) as cheer_rep",
)
->join('cheers', 'cheers.jobs_id', '=', 'jobs.id')
->orderByDesc('cheer_rep')
->groupBy('jobs.country', 'jobs.country_slug')
->get();
Here are my problems;
The job count is wrong because the join clause adds more results due to the hasMany relationship.
I can't seem to wrap my head around grouping the categories and getting the most occurring (popular) category.
Sample result
[
{
"country": "Canada",
"country_slug": "canada",
"job_count": 23,
"cities": 5,
"cheer_rep": "35000"
},
{
"country": "South Africa",
"country_slug": "south-africa",
"job_count": 9,
"cities": 2,
"cheer_rep": "700"
},
]
Any help would be highly appreciated. Also, if there's an eloquent way of achieving this, I'd appreciate that too.
Eloquent way:
Job::with('cheers')
->select(DB::raw('count(country) as job_count, country'),
DB::raw('count(city) as city_count, city')
)
->orderByDesc('cheers.cheer_rep')
->groupBy('country', 'country_slug')
->get();
Note: Not tested, as you did not provided DB schema.

Laravel Relationship - Additional fields in pivot table linking to additional models

I have simplified the problem to get to the point. I have three tables, users, roles, account.
Normally I would set up the User model to have a many to many relationships with roles but I want those roles to be specific to each account. So I have added an additional field to the pivot table. Here are the tables and fields that I have;
‘users’ table
|—————————
| id | name |
|—————————
| 1 | Bob |
| 2 | Jim |
| 3 | Fred |
|—————————
‘roles’ table
|—————————
| id | title |
|—————————
| 1 | Administrator |
| 2 | Manager |
| 3 | Approver |
|—————————
‘accounts’ table
|—————————
| id | name |
|—————————
| 1 | ABC Company |
| 2 | XYZ Shipping |
| 3 | KLM Transport |
|—————————
I then have the pivot table role_user with an additional pivot field for the account;
|—————————
| role_id | user_id | account_id
|—————————
| 1 | 3 | 1
| 2 | 2 | 1
| 3 | 2 | 3
| 3 | 1 | 2
|—————————
I have used the withPivot function on the belongsToMany function when setting up the many to many relationships. This allows me to get the information using $user->roles->pivot->account_id but what I need is to be able to get the name of that company. All it’s passing to the blade template is the id from the pivot table and not linking that to an actual Account model.
Is there a way with Eloquent to get this entire model in the same way as the original relationship?
Create a Custom Pivot Model
use Illuminate\Database\Eloquent\Relations\Pivot;
class RoleUserAccountPivot extends Pivot
{
public function user()
{
return $this->belongsTo(User::class);
}
public function role()
{
return $this->belongsTo(Role::class);
}
public function account()
{
return $this->belongsTo(Account::class);
}
}
Update your belongsToMany relationships
Bellow is an example with the User::roles relationship
class User //extends...
{
public function roles()
{
return $this->belongsToMany(Role::class, /*other parameters*/)->using(RoleUserAccountPivot::class)->withPivot('account_id');
}
}
Usage
$user->roles->first()->pivot->account // returns Account model
Hope it helps.
Reference links:
Laravel doc on custom pivots

Laravel 5.6 - How to fetch last X guest names for room deal?

I can't figure out how to structure an efficient Eloquent query for the following scenario.
Users can stay in many locations like rooms, apartments, homes, so we have a polymorphic stayable_locations table, but we're only focusing on the room stayable_type of this table. When the staff clicks a room, we want to display all available room deals (if any are available from the room_deals table) and also the last 3 guests for each room deal (if any).
Trying to get this output from the following tables via eloquent:
Room 111 (desired output for room deals and guests below)
- Room Deal #1 -> Able, Kane, Eve
- Room Deal #2 -> Eve, Adam
------------------------------------------
$room = Room::where('id',111)->first(); // room 111
// Eloquent query, not sure how to setup model relations correctly
// To get last 3 guest names per room deal [if any] in an efficient query
$room->roomDeals()->withSpecificRoomDealLast3RoomGuestNamesIfAny()->get();
Here is the table structure:
stayable_locations table [polymorphic]:
id | stayable_id | stayable_type | room_deal_id | room_guest_id
----------------------------------------------------------------
1 | 111 | room | 0 | 3 (Steve no room deal)
2 | 111 | room | 1 | 1 (Adam room deal)
3 | 111 | room | 1 | 2 (Eve room deal)
4 | 111 | room | 1 | 4 (Kane room deal)
5 | 111 | room | 1 | 5 (Able room deal)
6 | 111 | room | 2 | 1 (Adam room deal)
7 | 111 | room | 2 | 2 (Eve room deal)
room_deals table:
id | room_id | room_deal
-----------------------
1 | 111 | Deal A
2 | 111 | Deal B
users table:
id | name
------------
1 | Adam
2 | Eve
3 | Steve
4 | Kane
5 | Able
UPDATE: Showing respective models
User Model:
class User extends Authenticatable {
public function stayableLocations() {
return $this->morphMany('App\StayableLocation', 'stayable');
}
}
RoomDeal Model:
class RoomDeal extends Model {
public function room() {
return $this->belongsTo('App\Room');
}
public function guests() {
return $this->belongsToMany('App\User', 'stayable_locations', 'room_deal_id', 'room_guest_id');
}
}
StayableLocation Model:
class StayableLocation extends Model {
public function stayable() {
return $this->morphTo();
}
public function room() {
return $this->belongsTo('App\Room', 'stayable_id');
}
}
Room Model:
class Room extends Model {
public function stayableLocations() {
return $this->morphMany('App\StayableLocation', 'stayable');
}
public function roomDeals() {
return $this->hasMany('App\RoomDeal');
}
}
Any idea how to get the desired output via an efficient eloquent query?
I figured it out from the helping comments in my question. Here we go:
Laravel does not have this out of the box (see here) so we'll have to use a third party package.
Install the staudenmeir/eloquent-eager-limit package per link directions and follow usage example.
This is what needed to change above [still used same defined relationship above for below ...], just added the use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;:
class User extends Authenticatable {
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
...
}
class RoomDeal extends Model {
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
...
}
Working query with nested limit via eloquent, thanks to the commenters and package help:
$room = Room::find(111);
$deals3Guests = $room->roomDeals() // query deals
->with(['guests' => function($query) { // eager load guests
$query->orderBy('stayable_locations.id', 'desc') // get latest guests
->limit(3); // limit to 3
}])
->get();

Multi JOIN Table in Laravel

I have 4 tables below :
track
+----+-----+----------------+-------+
| ID | TID | TITLE | ALBUM |
+----+-----+----------------+-------+
| 1 | AAA | Yesterday | 1 |
| 2 | BBB | Happy | 2 |
| 3 | CCC | Gangname Style | 3 |
+----+-----+----------------+-------+
album
+----+-----+---------+-------+
| ID | AID | TITLE | COVER |
+----+-----+---------+-------+
| 1 | AAA | Album A | 1.jpg |
| 2 | BBB | Album B | 2.jpg |
| 3 | CCC | Album C | 3.jpg |
+----+-----+---------+-------+
track_artist
+----+-----+-----------+
| ID | TID | ARTIST_ID |
+----+-----+-----------+
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 3 | 3 |
+----+-----+-----------+
artist
+----+--------+--------+
| ID | NAME | AVATAR |
+----+--------+--------+
| 1 | Taylor | 1.JPG |
| 2 | T-ara | 2.JPG |
| 3 | M2M | 3.JPG |
+----+--------+--------+
I want to get track.TITLE, album.TITLE and artist.NAME base on track.TID (where TID = $XXX) in 4 table above. It's easy when I use raw INNER JOIN MySQL code, but I want to use Eloquent ORM in Laravel. How I can do this ? Thank you.
I have analyzed :
One track belongsTo one Album
One track hasMany track_artist
One track_artist hasOne artist
One track hasMany artist
You can try this
The table should be like this, you need 4 tables
albums(id, title, cover)
artist(id, name, avatar)
tracks(id, album_id, title)
track_artists (id, artist_id, track_id) ManyToMany with tracks
Models
class Album extends Model{
function tracks(){
$this->hasMany(Track::class);
}
}
class Artist extends Model{
function tracks(){
$this->belongsToMany(Track::class, 'track_artists'); //here track_artists table is as pivot table
}
}
class Track extends Model{
function album(){
$this->belongsTo(Album::class);
}
function artist(){
$this->belongsToMany(Artist::class, 'track_artists'); //here track_artists table is as pivot table
}
}
Fetch Data
$track = Track::with('album','artists')->find($trackId);
echo $track->title." - ". $track->album->title;
//now print all artist of this track
foreach($track->artists as $artist){
echo $artist->name;
}
Note: You don't need to create model for track_artists because it is a pivot table for tracks and artists tables. You can insert and update in pivot table using Track or Artist laravel eloquent model. Though if you want you can create it extending Pivot.
Save Data in pivot table
$track = Track::find(1);
$artist = Artist::create(['name' => 'Katy Perry', 'avatar' => 'Katy_Perry.jpg']);
$track->artists()->save($artist); //this save track and artist relation in track_artists pivot table
For details https://laravel.com/docs/5.6/eloquent-relationships#many-to-many
First, your relations ( in my opinion ) need some changes.
One track belongs to one album and one album has many tracks (one to many)
One track has one artist and one artist has multiple tracks (one to many)
One album has multiple artist and one artist has multiple albums(many to many)
You will need a table for the many to many relation with the album id and the artist id
This is an example considering the above points
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class track extends Model{
function album(){
$this->belongsTo(album::class);
}
function artist(){
$this->belongsTo(artist::class);
}
}
class album extends Model{
function track(){
$this->hasMany(track::class);
}
function artist(){
$this->belongsToMany(artist::class, 'my_many_to_many_table');
}
}
class artist extends Model{
function track(){
$this->hasMany(track::class);
}
function album(){
$this->belongsToMany(album::class, 'my_many_to_many_table');
}
}

Laravel Eloquent multiple relationship in a football game schedule

I am new to Laravel and Eloquent and i am trying to create a football game plan.
For now i have 4 tables (with some example entries):
teams (all teams)
+---------+-----------+
| team_id | team_name |
+---------+-----------+
| 1 | Arsenal |
| 2 | Chelsea |
+---------+-----------+
competition (all competitions)
+----------------+------------------+
| competition_id | competition_name |
+----------------+------------------+
| 1 | Premier League |
+----------------+------------------+
schedule (schedule to the competitions)
+----+----------------+----------+--------------+--------------+-----------+-----------+
| id | competition_id | matchday | home_team_id | away_team_id | home_goal | away_goal |
+----+----------------+----------+--------------+--------------+-----------+-----------+
| 1 | 1 | 1 | 1 | 2 | 3 | 2 |
| 2 | 1 | 2 | 2 | 1 | 0 | 3 |
+----+----------------+----------+--------------+--------------+-----------+-----------+
schedule_teams (matches the schedule teamid with the teams id over the competition_id and the schedule_team_id)
+----+------------------+----------------+----------+
| id | schedule_team_id | competition_id | teams_id |
+----+------------------+----------------+----------+
| 1 | 1 | 1 | 1 |
| 2 | 2 | 1 | 2 |
+----+------------------+----------------+----------+
And here are my current classes:
Schedule.php
public function competition()
{
return $this->belongsTo(Competition::class, 'competition_id', 'competition_id');
}
Competition.php
public function schedule()
{
return $this->hasMany(Schedule::class, 'competition_id', 'competition_id');
}
With
$id = \request('competition_id');
$schedule = Schedule::where('competition_id', $id)->with('competition')->get();
i get the schedule with the home and away id's from schedule.
The question now is, how can i get the entries from the teams table over the schedule_teams table to a specifiy home and away id, also for example home_team_id = 1:
home_team_id (=1) -> schedule_team_id (=1) and competition_id (=1) -> teams (Arsenal)
I want the data from schedule and the associated teams in a collection to output in a blade.
can anyone help or give me improvement tips for a football database?
You should make use of the hasManyThrough relationship.
If you create say Schedule\Team, and then have that like the following.
public function schedule() {
$this->belongsTo(Schedule::class, 'schedule_id');
}
public function team() {
$this->belongsTo(Team::class, 'team_id');
}
Now in your Schedule class, you can have the following.
public function teams() {
$this->hasManyThrough(Team::class, Schedule\Team::class, 'schedule_id');
}
It should also be noted, that you don't need competition_id in your schedule team. Since a team belongs to a schedule, which belongs to competition, you can get it like that.
If you also want your Team to know about its schedules, you can add this to Team.
public function schedules() {
return $this->hasManyThrough(Schedule::class, Schedule\Team::class);
}
You Schedule\Team class becomes essentially, a glorified representation of a pivot table, but having it as a model, allows you to expand upon it in the future. It also helps keep everything neat.
Hope that makes sense.

Categories