Retrieving table via relational table in Laravel models - many-to-many

I have a database called "recipe-book" that looks like this:
+-----------------------+
| Tables_in_recipe-book |
+-----------------------+
| ingredients |
| migrations |
| recipe_categories |
| recipe_ingredients |
| recipes |
| users |
+-----------------------+
I have set up models in Laravel for a users recipes so I can return them like this:
$user = User::findOrFail($id);
return $user->recipes;
This works perfectly, however, now I naturally want to list the ingredients for a given recipe right? I want to do something similar like this
$recipe = Recipe::findOrFail($id);
return $recipe->ingredients;
The problem for me now is how to understand how to achieve this. In my database I have a relational table called "recipe_ingredients" for each ingredient like so:
+---------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+------------------+------+-----+---------+-------+
| recipe_id | int(10) unsigned | NO | MUL | NULL | |
| ingredient_id | int(10) unsigned | NO | MUL | NULL | |
| quantity | varchar(255) | NO | | NULL | |
+---------------+------------------+------+-----+---------+-------+
The idea is that I can then more easily retrieve all the recipes that contain a specific ingredient. What I want though when I run:
return $recipe->ingredients;
Is both the "quantity" column from the relational table, as well as the information about that specific recipe, i.e. the table "ingredients".
How would I go about this?
Thanks in advance!

If you have something like this as models:
<?php
class User extends Eloquent {
public function recipes()
{
return $this->hasMany('Recipe');
}
}
class Recipe extends Eloquent {
public function ingredients()
{
return $this->belongsToMany('Ingredient', 'recipe_ingredients')->withPivot('quantity');
}
}
class Ingredient extends Eloquent {
public function recipes()
{
return $this->belongsToMany('Recipe', 'recipe_ingredients')->withPivot('quantity');
}
}
And now you can do
Route::get('/test', function()
{
$user = User::find(1);
var_dump($user->email);
foreach($user->recipes as $recipe)
{
var_dump($recipe->name);
foreach($recipe->ingredients as $ingredient)
{
var_dump($ingredient->name);
var_dump($ingredient->pivot->quantity);
}
foreach($ingredient->recipes as $recipe)
{
var_dump($recipe->name);
var_dump($recipe->pivot->quantity);
}
}
});
This is the migration I used here:
use Illuminate\Database\Migrations\Migration;
class CreateIngredientsTable extends Migration {
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('recipes', function($table)
{
$table->increments('id');
$table->integer('user_id');
$table->string('name');
});
Schema::create('ingredients', function($table)
{
$table->increments('id');
$table->string('name');
});
Schema::create('recipe_ingredients', function($table)
{
$table->integer('recipe_id');
$table->integer('ingredient_id');
$table->string('quantity');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('recipes');
Schema::dropIfExists('ingredients');
Schema::dropIfExists('recipe_ingredients');
}
}

Related

Laravel nested relations eager load respect ancestor id

Let's say I have a model called Research. Each research belongsToMany Location models. And each Location model BelongsToMany Contact models. BUT, each Contact is also related to Research.
class Research extends Model {
protected $table = 'researches';
public function locations()
{
return BelongsToMany( Location::class, 'research_locations_list', 'research_id', 'location_id' );
}
}
class Location extends Model {
protected $table = 'locations';
public function researches()
{
return BelongsToMany( Research::class, 'research_locations_list', 'research_id', 'location_id' );
}
public function contacts()
{
return BelongsToMany( Contact::class, 'location_contacts_list', 'location_id', 'contact_id' );
}
}
class Contact extends Model {
protected $table = 'contacts';
public function locations()
{
return BelongsToMany( Location::class, 'location_contacts_list', 'location_id', 'contact_id' );
}
}
researches table:
+----+------------+
| id | research |
+----+------------+
| 1 | Research 1 |
| 2 | Research 2 |
+----+------------+
locations table:
+----+---------------+
| id | location |
+----+---------------+
| 1 | United States |
| 2 | Great Britain |
| 3 | Germany |
+----+---------------+
contacts table:
+----+---------+
| id | contact |
+----+---------+
| 1 | Jack |
| 2 | John |
| 3 | Hanz |
+----+---------+
research_locations_list table:
+----+-------------+-------------+
| id | research_id | location_id |
+----+-------------+-------------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 2 |
| 4 | 2 | 3 |
+----+-------------+-------------+
So Research 1 is being conducted in United States and Great Britain, Research 2 in Great Britain and Germany
location_contacts_list table:
+----+-------------+------------+-------------+
| id | location_id | contact_id | research_id |
+----+-------------+------------+-------------+
| 1 | 1 | 1 | 1 |
| 2 | 1 | 2 | 1 |
| 3 | 2 | 1 | 2 |
| 4 | 3 | 3 | 2 |
+----+-------------+------------+-------------+
Research 1 should have Jack and John as contacts in United States and no contacts elsewhere;
Research 2 should have John as contact in Great Britain and Hanz in Germany;
Now, with lazy load I can achieve that:
$researches = Research::all();
foreach( $researches as $research )
{
foreach( $research->locations as $location )
{
$contacts = $location->contacts()->wherePivot( 'research_id', $research->id )->get();
// Will return John and Jack in United States for Research 1 and John in Great Britain and Hanz in Germany for Research 2
}
}
Now, the question is: how do I achieve this with eager loading?
$researches = Research::with( 'locations.contacts' )->all();
foreach( $researches as $research )
{
foreach( $research->locations as $location )
{
$contacts = $location->contacts;
// Will return John and Jack in United States, John in Great Britain ( which is not supposed to happen ) for Research 1 and John in Great Britain and Hanz in Germany for Research 2
}
}
Perhaps I can instruct somehow for contacts to respect ancestor id? Like:
$research = Research::with( 'locations.contacts' )->where( 'researches.id = location_contacts_list.research_id' )->all();
UPDATE
The closest I came up to solving this is modifying the Location model like this:
class Location extends Model {
protected $table = 'locations';
public function researches()
{
return BelongsToMany( Research::class, 'research_locations_list', 'research_id', 'location_id' );
}
public function contacts()
{
return BelongsToMany( Contact::class, 'location_contacts_list', 'location_id', 'contact_id' );
}
// Modify contacts attribute getter
public function getContactsAttribute()
{
$contacts = $this->contacts();
if( !empty( $this->pivot->research_id ) )
{
$contacts = $contacts->wherePivot( 'research_id', $this->pivot->research_id );
}
return $contacts->get();
}
}
But it looks kind of dirty...
In your solution you get N+1 query problem. I can suggest the following solution:
class Research extends Model
{
protected $table = 'researches';
public function locations(): BelongsToMany
{
return $this->belongsToMany(Location::class, 'research_locations_list');
}
public function contacts(): BelongsToMany
{
return $this->belongsToMany(Contact::class, 'location_contacts_list')
->withPivot('location_id');
}
public function contactsByLocationAttribute(int $locationId): Collection
{
return $this->contacts
->filter(static function ($contact) use ($locationId) {
return $contact->pivot->location_id === $locationId;
});
}
}
$researches = Research::with(['locations', 'contacts'])->get();
foreach ($researches as $research) {
foreach ($research->locations as $location) {
$contacts = $research->contactsByLocation($location->id);
}
}
here there will always be only 3 queries to the database. And only necessary models will be loaded
If I got it right, you want to add some conditions inside your with statement. If you want to use eloquent syntax, you can do it like this:
$research = Research::with(['YOUR RELATION' => function ($query) {
$query->where('YOUR COLUMN', 'EQUALS TO SOMETHING');
}])->get();
Keep in mind that since inside with you use nested relationships, like locations.contacts, the where function inside the query, will filter only the last model (in this case that would be contacts). If you want to filter both locations and contacts based on some conditions, you have to write something similar to this (just an example):
$research = Research::with(['locations' => function ($query) {
$query->where('id', 1)->with(['contacts' => function ($query) {
$query->where('name', 'Tim');
}]);
})->get();
In order to do that though, you need to create a relationship also with your pivot table (if you want to use it also inside the conditions). Otherwise, you have to use a different syntax, using joins. Check this page from docs for query builders https://laravel.com/docs/9.x/queries#main-content
Perhaps, this https://laravel.com/docs/9.x/eloquent-relationships#has-many-through helps you. You should try to this

Laravel 7 select from table with two columns having ids from a Single Table

My requirement?
If i am the logged in user with Id = 1, then through the Messages Table i want to select users from Users Table to whom i sent the message or from whome i received the message.
Table 1: Users
+----+------+-------+
| id | name | email |
+----+------+-------+
| 1 | a | ??? |
| 2 | b | ??? |
| 3 | c | ??? |
| 4 | d | ??? |
| 5 | e | ??? |
| 6 | f | ??? |
| 7 | g | ??? |
| 8 | h | ??? |
| 9 | i | ??? |
| 10 | j | ??? |
+----+------+-------+
Table 2: Messages
+----+---------+-------------+
| id | user_id | receiver_id |
+----+---------+-------------+
| 1 | 1 | 2 |
| 2 | 3 | 1 |
| 3 | 4 | 1 |
| 4 | 1 | 5 |
| 5 | 1 | 3 |
+----+---------+-------------+
User Model
public function messages()
{
return $this->belongsToMany(User::class, 'messages', 'user_id', 'receiver_id');
}
Message Model
public function user()
{
return $this->belongsTo(User::class);
}
So what i have tried so far?
$id = Auth::id();
$users = User::with(['messages' => function($query) use($id){
$query->where('user_id', $id)
->orWhere('received_id', $id)
->orderBy('created_at', 'DESC');
}])->get();
dd($users);
What is the expected result?
Using this query, i am getting all of my 10 users. Although i should only get 4 users(those with id's 2,3,4,5).
If the above query is wrong, or i should follow another method or i should created some sort of relationships Please help.
Hopefully you have understood the question, i am new to Laravel but i am learning.
Probably what you need is three relations(one to many) in the User model. One for sent messages, one for received messages and one for both, like this:
public function messagesSent()
{
return $this->hasMany(Message::class, 'user_id');
}
public function messagesReceived()
{
return $this->hasMany(Message::class, 'receiver_id');
}
public function messages()
{
return $this->messagesSent()->union($this->messagesReceived()->toBase());
}
Then you should be able to get user messages like this: User::with('messages')->get();
I think you should use a join statement or "whereHas" to select users who have any messages.
$id = Auth::id();
$users = User::whereHas('messages', function ($query) use($id){
$query->where('user_id', $id)
->orWhere('received_id', $id);
})
->get();
To have access to "messages" you should add "with" statement too.
Adding my own solution(i.e working) to this question.
User Model
public function sent()
{
return $this->hasMany(Message::class, 'user_id');
}
public function received()
{
return $this->hasMany(Message::class, 'receiver_id');
}
Query
$users = User::whereHas('sent', function ($query) use($id) {
$query->where('receiver_id', $id);
})->orWhereHas('received', function ($query) use($id) {
$query->where('user_id', $id);
})->get();
dd($users);

Laravel5: Eloquent and JOIN

Items Table
| id | item_id | item_title |
|-------|---------|------------|
| 1 | 1002 | A |
| 2 | 1003 | B |
| 3 | 1004 | C |
Sells Table
| id | item_id |
|----|-----------|
| 1 | 1002 1003 |
| 2 | 1003 1004 |
| 3 | 1004 1002 |
I want result : Sells Table 1. item title is A B
I want to combine the sells table with the item table and then match the item_id of the sells table to the item_title of the item table.
The table definitions look incorrect, you should have a pivot table linking items with sells, so a sell_item table:
item_id | sell_id
-----------------
1 | 1
1 | 3
2 | 1
2 | 2
3 | 2
3 | 3
Then using eloquent, you'd create models to represent your tables and define the relationships using BelongsToMany:
class Item extends Model {
public function sells() {
return $this->belongsToMany(Sell::class);
}
}
class Sell extends Model {
public function items() {
return $this->belongsToMany(Item::class);
}
}
Each instance of either model will then have access to it's related models via $item->sells and $sell->items.
The query builder can perform a join if not going the Eloquent route:
DB::table('sells')->join('items', 'sells.item_id', '=', 'items.item_id')
->select('sells.*', 'items.title')
->get();
The table definitions look incorrect, If you corrected already then your model replationship should be like
class Item extends Model {
public function sells() {
return $this->belongsToMany(Sell::class);
}
}
class Sell extends Model {
public function items() {
return $this->belongsToMany(Item::class);
}
}
Each instance of either model will then have access to it's related models via $item->sells and $sell->items.
The query builder can perform a join if not going the Eloquent route:
DB::table('sells')->join('items', 'sells.item_id', '=', 'items.item_id')
->select('sells.*', 'items.title')
->get();
Or if your model name is Sell then
$response=Sell::with('items')->get();

Laravel how to join one to many relationship tables

I am using Laravel 5.5 and I want to display list of data by joining tables that have one to many relationship.
Currently, I do this by going through the loop and make queries to retrieve data. This way, I think, is very inefficient, because if I were to display 1000 rows of data record, I will have to go 1000 loops to append other data with one-to-many relationship.
I am thinking to get around this problem using cache but it does not seem to solve fundamental problem.
For more understanding I have shared tables that I want do join as below.
Post Table
| id | comment_id | status |
|----|------------|--------|
| 1 | 1 | 0 |
| 2 | 2 | 0 |
| 3 | 3 | 1 |
| 4 | 4 | 0 |
| 5 | 5 | 1 |
| 6 | 6 | 1 |
Comment Table
| id | order_id | content |
|----|----------|----------|
| 1 | 1 | hi |
| 2 | 1 | hellow |
| 3 | 1 | yes |
| 4 | 1 | okay |
| 5 | 2 | bye |
| 6 | 2 | good bye |
If I were to join Table Post with Table Comment, because they have one to many relationship, rows would not match. How would I join these two tables to show the list of post with comments?
Sample List Controller
/**
* #param Request $request
* #return \Illuminate\Http\JsonResponse
*/
public function list(Request $request)
{
$vaildData = $request->validate([
'comment_id' => 'numeric',
]);
$posts = new PostModel;
$posts->find(1);
$displayPosts = [];
foreach ( $posts->find(1)->get() as $post ) {
$displayPosts->comments = $post->comment()->get();
}
return $displayPosts;
}
Post Model
namespace App\Model\Post;
use SoftDeletes;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
public function comments()
{
return $this->hasMany('App\Model\Post\Comment’, ‘post_id', 'id');
}
}
Use with() for eager loading your comments.
$posts = PostModel::with('comments')->find($id);
So your function will be like-
public function list(Request $request)
{
$vaildData = $request->validate([
'comment_id' => 'numeric',
]);
$posts = PostModel::with('comments')->find(1);
return $displayPosts;
}
You can filter your comments with comment_id using whereHas() like the following-
$comment_id = $request->input('comment_id');
$posts = PostModel::with('comments')->whereHas('comments', function ($query) use($comment_id)
{
$query->where('id', '=', $comment_id);
})->find(1);
https://laravel.com/docs/5.1/eloquent-relationships
Firstly, you may refer to this documentation.
To setup one-to-many relationship for Post and Comment table:
A Post has Many Comments
So in you Comment table there should be a column named post_id
Inside your Post.php
public function comments()
{
return $this->hasMany('App\Comment’);
}
Inside your controller
public function list(Request $request){
$posts = Post::where('id', 1)
->with('comments')
->get()
return $posts;
}
public function list(Request $request, $id)
{
$vaildData = $request->validate([
'comment_id' => 'numeric',
]);
$posts = PostModel::find($id);
return $posts->comments;
}
try this... Hope this can help you
or you can try like this
public function list(Request $request, $id)
{
$vaildData = $request->validate([
'comment_id' => 'numeric',
]);
$posts = PostModel::find($id)->comments()->get();
return $posts;
}
public function list(Request $request)
{
$vaildData = $request->validate([
'comment_id' => 'numeric',
]);
$posts = new PostModel;
$results = $posts->find(1)->with('comments')//comments is the function name you defined in the App\Model\Post
return resurts;
}
collection results contain the infomation of the post and another extra comment collection that belong to the post

Laravel, Eloquent Many to Many with Many To Many in Pivot table

How is the best way to represent this in Eloquent Models:
jobs -- ManyToMany -- (providers -- ManyToMany-- services)
Tables:
| providers | services |services_providers | jobs | jobs_services_providers
--------------------------------------------------------------------------------------
| id | id | id | id | id |
| | | providers_id | | services_providers_id |
| | | services_id | | job_id |
| | | price | | price |
Thanks!
Using Laravel official documentation which is available here your models should look like
Model Jobs
class Jobs extends Model {
public function providers(){
return $this->belongsToMany('App\Providers');
}
}
Model Providers
class Providers extends Model {
public function jobs(){
return $this->belongsToMany('App\Jobs');
}
public function services(){
return $this->belongsToMany('App\Services');
}
}
Model Services
class Services extends Model {
public function providers(){
return $this->belongsToMany('App\Providers');
}
}
If you using pivot tables then you can define pivot table in model
return $this->belongsToMany('App\Providers', 'services_providers');
If you want to customize your fields in pivot relationship in Eloquent then use something like
return $this->belongsToMany('App\Providers', 'services_providers', 'service_id', 'provider_id');
For all other help you should be fine with using LARAVEL OFFICIAL DOCUMENTATION
Hope it helps