Laravel eager loading with two Fk to same table - php

I want use Eager loading in Laravel 8 with a relations of two foreign keys to the same table.
Teams table
id
name
120
Germany
245
Italy
Fixtures table
id
timestamp
home_id
away_id
winner_id
home_goals
away_goals
1
1607803200
120
245
120
2
0
Fixture Model
public function home()
{
return $this->belongsTo(Team::class, "home_id");
}
public function away()
{
return $this->belongsTo(Team::class, "away_id");
}
public function winner()
{
return $this->belongsTo(Team::class, "winner_id");
}
In controller, even with $fixtures = Fixture::with(["home", "away", "winner"])->get(); the N+1 Query detector package advise me from that problem.
Are there any way to use eager loading with multiple foreign keys belongs to the same table??
If not, any suggestions to modify the database structure in any better way?
Thank you!
UPDATED:
Add the requested info. Seen in the laravel debugbar now, I see only one duplicate query and I think it´s makes sense, are the two relations loaded in with..
So, it´s correct?
Using the data in the view:
#foreach($fixtures as $fixture)
<div class="flex items-center ">
<div>
<img class="object-scale-down h-12 w-12 border border-indigo-600" src="{{ $fixture->home->logo }}" />
</div>
<div class="flex-auto ml-5">
{{ $fixture->home->name }}
</div>
<div class="flex-auto">
</div>
<div class="flex-auto mr-5 text-right">
{{ $fixture->away->name }}
</div>
<div class="">
<div>
<img class="object-scale-down h-12 w-12 border border-indigo-600" src="{{ $fixture->away->logo }}" />
</div>
</div>
</div>
#endforeach
Data from Laravel DebugBar:

Related

Laravel Eloquent Query within Foreach Loop

Back again with another question I am hoping someone might have an idea for regarding the app that I am working on.
In this educational assessment application, I have assessments which have a one-to-many relationship with Competencies (similar to a Blog Post Category) and a many-to-many relationship with Contexts (similar to a Blog Tag).
I am trying to create a report view which will allow me to show a card for each of the Competency & Context combinations and then count all assessments where that competency & context combination exists (think of this as counting how many blog posts are in each category & tag combination, even if that number is 0).
So far I am able to produce the report which lists a card for each competency and context combination but I can't figure out how to pass that information to the controller for use in the query which will find the relevant assessments.
Here is my Report View
<x-app-layout title="{{ config('app.name', 'Laravel') }}">
<div class="container grid px-6 mx-auto">
<h2 class="my-6 text-2xl font-semibold text-gray-700 dark:text-gray-200">
{{ __('Reports') }}
</h2>
<div class="grid gap-6 mb-8 md:grid-cols-2 xl:grid-cols-4">
#foreach ($competencies as $competency) <br>
#foreach ($contexts as $context)
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200">
{{ $competency->name}}
</p>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
{{ $context->name }}
</p>
{{ $assessments }}
</div>
</div>
#endforeach
#endforeach
</div>
</x-app-layout>
Here is my Report Controller
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Assessment;
use App\Models\Competency;
use App\Models\Context;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
class ReportController extends Controller
{
public function index(Competency $competency, Context $context)
{
return view('dashboard.reports.index', [
'competencies' => Competency::where('team_id', Auth::user()->currentTeam->id)->get(),
'contexts' => Context::where('team_id', Auth::user()->currentTeam->id)->get(),
'assessments' => Assessment::where('competency_id', $competency->id)->whereRelation('contexts', 'context_id', $context->id)->count(),
]);
}
}
Here is an image of what this currently produced in the Report View: https://imgur.com/a/SQSf7UM
Please let me know if there is additional detail which would be helpful
it's better you declare the relation in the model, let's assume:
an assessment has one category,
an assessment has many contexts,
You can create an additional table with its model to store assessment contexts (because it assessent can has many contexts), let's say the table name is assessment_contexts and the model name is AssesmentContext. Or you can just run php artisan make:model AssessmentContext -m.
At least it has 2 columns, assessment_id and context_id,
Then inside AssessmentContext, add this function to create a simple relation,
public function assessment() {
return $this->hasOne(Assessment::class);
}
public function context() {
return $this->hasOne(Context::class);
}
Declare this functions inside the Assessment model,
public function competency() {
return $this->hasOne(Competency::class);
}
public function contexts() {
return $this->hasMany(AssesmentContext::class);
}
And add the this to the Competency model,
public function assessments() {
return $this->hasMany(Assessment::class);
}
And this for the Context model,
public function assessments() {
return $this->hasMany(AssessmentContext::class);
}
You have to add foreign key inside assessments table that refers to category primary key id, (I recommend you the column name is category_id).
Then finally in your controller, you can just declare the competencies list, and inside your view, you can access all the relate data. i.e:
return view('dashboard.reports.index', [
'competencies' => Competency::where('team_id', Auth::user()->currentTeam->id)->get()
]);
Inside view,
#foreach ($competencies as $competency) <br>
#foreach ($competency->assessments as $assessment)
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200">
{{ $competency->name}}
</p>
#foreach ($assessment->contexts as $context)
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
{{ $context->name }}
</p>
#endforeach
</div>
</div>
#endforeach
#endforeach
This is just simple scheme of relation, there are other ways to do it that maybe better and more optimal.

How to display a list of elements from a different table in a blade view?

I have a recipe information view on a meal plan application, where users can see the specific details on how to prepare a recipe pulled from it's Recipe ID. The Recipe's ingredient information is stored in a different table, and I'm trying to pull all the ingredient names and corresponding ingredient amount with the same Recipe_ID as the current recipe that's being inspected. Recipe and Ingredients has a one-to-many relationship.
Controller:
public function recipeinformation($Recipe_ID)
{
$datatest = Ingredients::join('mealplan_main', 'mealplan_main.Recipe_ID', '=', 'recipeingredientsmain.Recipe_ID')
->where('mealplan_main.recipe_id', '=', $Recipe_ID)
->get([ 'recipeingredientsmain.ingredientname', 'recipeingredientsmain.amount']);
$dat = Recipe::find($Recipe_ID);
return view('MealPlanDisplay.recipeinformation', compact('dat', 'Recipe_ID', 'datatest'));
}
The View:
//this works
<div class="flex flex-col">
<h3> {{$dat->recipe_name}} </h3>
</div>
//the attempt to display the ingredientname and ingredient amount does not work
<div class="flex flex-col">
#foreach ($datatest as $var)
<p> {{$var->recipeingredientsmain.amount}} </p>
<p> {{$var->recipeingredientsmain.ingredientname}} </p>
#endforeach
</div>
The code runs but the information does not display on the view. When I remove the foreachloop, I get the error:
Property [recipeingredientsmain] does not exist on this collection instance.
How get this to work properly?
In your blade use below code by removing table name recipeingredientsmain
<div class="flex flex-col">
#foreach ($datatest as $var)
<p> {{$var->amount}} </p>
<p> {{$var->ingredientname}} </p>
#endforeach
</div>

How to show only one image in the index page in Laravel 5.6?

I have multiple images in my table witch related to vehicle_id, like this,
image table
id fileName vehicle_id
1 1.jpg 1
2 2.jpg 1
3 3.jpg 1
4 4.jpg 1
5 28.png 2
5 28.png 2
6 29.png 2
7 30.png 3
8 31.png 3
9 56.png 3
The vehicle table have way to many relationship with the image table and data grap using eager loader in VehicleController
$vehicles = Vehicle::with('image')->get();
return view('vechicles.index')->withVehicles($vehicles);
Now these images are showing in the vehicles/index.blade.php file
#foreach($vehicle->images as $image)
<tr>
<td><img src="/images/{{ $image->resized_name }}"></td>
</tr>
#endforeach
My problem is occurred now, in this way, I can show all images in the table which related to proper vehicle_id but, I need only show one image (to matching vehicle_id) like thumbnail to above line.
Then how can I configure this?
updated Controller
public function index()
{
$vehicles = Vehicle::with('images')->get();
return view('vechicles.index')->withVehicles($vehicles);
}
updated full blade
#extends('layouts.app')
#section('content')
<div class="container">
<div class="row">
<div class="col-md-10 col-md-offset-1">
#if($vehicles)
#foreach($vehicles as $vehicle)
{{$vehicle->district}}
{{$vehicle->town}}
{{$vehicle->brand}}
{{$vehicle->model}}
<hr>
#foreach($vehicle->images as $image)
<tr>
<td><img src="/images/{{ $image->resized_name }}"></td>
</tr>
#endforeach
#endforeach
#endif
</div>
</div>
</div>
#endsection
You are looping through every images right now. You can simply retrieve an image using code below:
$vehicle->images()->first()->resized_name
So your code to display image will be:
#extends('layouts.app')
#section('content')
<div class="container">
<div class="row">
<div class="col-md-10 col-md-offset-1">
#if($vehicles)
#foreach($vehicles as $vehicle)
{{$vehicle->district}}
{{$vehicle->town}}
{{$vehicle->brand}}
{{$vehicle->model}}
<hr>
<tr>
<td>
<a href="{{route('vechicles.show',$vehicle->id)}}"><img
src="/images/{{ $vehicle->images()->first()->resized_name
}}"></a>
</td>
</tr>
#endforeach
#endif
</div>
</div>
</div>
#endsection
You can simply take only the first image. But be aware that you must verify if there are any images for that vehicle otherwise an exception will be thrown.
#if ($vehicle->images->isNotEmpty())
<a href="{{route('vechicles.show',$vehicle->id)}}">
<img src="/images/{{ $vehicle->images->first()->resized_name }}">
</a>
#endif
You can use limit along with with function like:
Vehicle::with(['image' => function($query) {
return $query->limit(1);
}])->get();
return view('vechicles.index')->withVehicles($vehicles);
And write code to show image as:
<tr>
<td><img src = "/images/{{ $vehicle->images[0]->resized_name }}"></td>
</tr>
You can use laravel first() function.
$vehicles = Vehicle::->where('vehicle_id', 1)->first();
You just have to dispaly the first array image in the blade as shown below:
#foreach ($vehicles as $v)
<img src = "/images/{{ $v->images[0]->resized_name }}"
#endforeach
(or) we can also use the laravel first() function to display the first record image from the collection
#foreach ($vehicles as $v)
<img src = "/images/{{ $v->images->first()->resized_name }}"
#endforeach

Laravel view links and web.php routes messed up

I'm making a forum with themes and topics. If a user clicks on a theme, he/she gets to see all the topics within that theme. Here we encounter the first problem. In the theme.blade.php I have a title: <span class="card-title">{{ $theme->theme_title }} - Topics</span>. This title is supposed to show the title of the theme that the user clicked on. But it shows (just a wild guess) some random theme title from the database that is not even connected to this topic.
Now I made an extra view for the user. If the user clicks on a topic from the selected theme. He/she is supposed to redirect to the topic that he/she clicked on but instead its shows (again) some random topic from the database that is not connected to the topic/theme at all. Instead of the topic that the user clicked on. In this GIF http://imgur.com/a/vOQFT you can see the problem If u look at the profile picture and username. Maybe the problem is in the Web.phpor somewhere else, I don't know. Sorry for the long story but I couldn't figure out how say this in a better way. I think I switched some things up in the code.
Here is the every file of code where this problem may occur
Web.php
Route::get('/', 'ThemesController#index')->name('home');
Route::get('/theme/{theme_id}/topics', 'ThemesController#show')->name('showtheme');
Route::get('/theme/{theme_id}/topics/{topic_id}', 'TopicsController#show')->name('showtopic');
Route::group(['middleware' => 'App\Http\Middleware\AdminMiddleware'], function() {
//THEMES
Route::get('/theme/{theme_id}/edit', 'ThemesController#edit')->name('edittheme');
Route::patch('/theme/{theme_id}/edit', 'ThemesController#update')->name('updatetheme');
Route::get('/theme/create', 'ThemesController#create')->name('createtheme');
Route::post('/theme/create', 'ThemesController#save')->name('savetheme');
Route::delete('/theme/{theme_id}/delete', 'ThemesController#destroy')->name('deletetheme');
//TOPICS
Route::get('/theme/{theme_id}/topics/{topic_id}/edit', 'TopicsController#edit')->name('edittopic');
Route::patch('/theme/{theme_id}/topics/{topic_id}/edit', 'TopicsController#update')->name('updatetopic');
Route::get('/theme/{theme_id}/topics/create', 'TopicsController#create')->name('createtopic');
Route::post('/theme/{theme_id}/topics/create', 'TopicsController#save')->name('savetopic');
Route::delete('/theme/{theme_id}/topics/{topic_id}/delete', 'TopicsController#destroy')->name('deletetopic');
});
Route::get('user/profile', 'UserController#profile')->name('showprofile');
Route::post('user/profile', 'UserController#update_avatar');
Theme.blade.php (The list of every topic within the theme)
<div class="col s12">
<div class="card">
<div class="card-content"><span class="card-title">{{ $theme->theme_title }} - Topics</span>
<div class="collection">
#foreach($topics as $topic)
<a href="{{ route('showtopic', ['theme_id' => $theme->id, 'topic_id' => $topic->id ]) }}" class="collection-item avatar collection-link"><img src="/uploads/avatars/{{ $topic->user->avatar }}" alt="" class="circle">
<div class="row">
<div class="col s6">
<div class="row last-row">
<div class="col s12"><span class="card-title">{{ $topic->topic_title }}</span>
<p>{!! str_limit($topic->topic_text, $limit = 125, $end = '...') !!}</p>
</div>
</div>
<div class="row last-row">
<div class="col s12 post-timestamp">Posted by: {{ $topic->user->username }} op: {{ $topic->created_at }}</div>
</div>
</div>
<div class="col s2">
<h6 class="title center-align">Replies</h6>
<p class="center replies">{{ $topic->replies->count() }}</p>
</div>
<div class="col s2">
<h6 class="title center-align">Status</h6>
<div class="status-wrapper center-align"><span class="status-badge status-open">open</span></div>
</div>
<div class="col s2">
<h6 class="title center-align">Last reply</h6>
<p class="center-align"></p>
<p class="center-align">Tijd</p>
</div>
</div>
</a>
#endforeach
</div>
</div>
</div>
</div>
ThemesController.php (Only show method)
public function show($id)
{
$theme = Topic::find($id)->theme;
$topics = Theme::find($id)->topics;
return view('themes.theme')->with('topics', $topics)->with('theme', $theme);
}
TopicsController.php(Only show method)
public function show($id)
{
$theme = Theme::find($id);
$topic = Topic::find($id);
return view('topics.topic')->with('theme', $theme)->with('topic', $topic);
}
Thanks for looking at my code. This problem has been sitting here for quite a while and I want to move on. Thanks for your help!
Your controller code simply finds the theme with ID $id, and the topic (singular!) with ID $id. That particular topic may not appear in that particular theme at all. They likely have nothing to do with each other.
To find the topics belonging to the theme with ID $id, you would do this:
$theme = Theme::find($id)->with('topics');
(this assumes your model relationships are set up correctly, you have not show us those). See the docs on eager loading.
To access the topics in your view, do something like this:
#foreach ($theme->topics as $topic)
...
{{ $topic->user->username }}
...
While developing, you can simply
return $theme;
in your controller to see the structure of the data, so you can work out how to handle and iterate over it.

Laravel: Need to solve foreach loop

What i'm trying to do is basically have the "latest" episodes show for a series that is has a status of "ongoing" below is the code i have so far.
The problem i a facing is that I can't seem to make the foreach loop for episodes work for the series. Wit hthe current code what it does is shows the same variables. Rather what i think is happening is that it loops the same query for each series so that the same variable pops up for each series.
Can anyone help me out here?
Also the way the episodes are linked is by using the title_id for the titles so in the table for episodes, they are liked by 'title_id', I wouldn't know what to do with that in this sequence though.
<?php $titles = DB::table('titles')->whereNotNull('poster')->where('status', '=', 'ongoing')->orderBy('updated_at', 'desc')->limit(12)->get(); ?>
#foreach ($titles as $title)
<?php $episodes = DB::table('episodes')->orderBy('created_at', 'desc')->limit(1)->get(); ?>
#foreach ($episodes as $episode)
<figure class="col-lg-2 col-md-3 col-sm-4 pretty-figure">
<div class="home-episode-number">
{{ $episode->episode_number }}
</div>
<div class="flip-containerw">
<div class="flipper">
<img src="{{ $episode->poster ? $episode->poster : '/assets/images/noimageepisode.png' }}" alt="" class="img-responsive">
</div>
</div>
<div class="home-anime-name">
{{ str_limit($title->title, 23, '...') }}
</div>
</figure>
#endforeach
#endforeach
You are not following some basic design patterns, like, for instance, the Model-View-Controller structure.
MVC
It's not good practice to have DB calls inside your view, wich you are doing. You should do it inside your model, or in a repository. And pass it trought the controller.
You would avoid a lot of headache if you start using eloquent properly.
Eloquent
Now, answering your question:
If you want to get the episode for the title in the loop, try using a where:
$episodes = DB::table('episodes')->where('title_id,'=',$title->id)->orderBy('created_at', 'desc')->limit(1)->get();
That query will retrieve just one episode (limit(1)).

Categories