What I am trying to do, is to fetch a row from a table, using a foreign key inside it, that links to another table, and then display a specific column of that row inside a view.
For some context, this is a website about sport events. Inside the view, the user should be able to see the details of the clicked event. Inside those details should be the sport category and the sport. However, I can't find how to do so, in Laravel 6.5.
What I have:
Database tables:
sport_categories (id, name)
sports (id, sport_category_id, name)
events (id, title, sport_category_id, sport_id)
EventsController
public function show(Event $event)
{
return view('events.show', ['event' => $event]);
}
View
<div id="event-sport-category">
<span>Sport Category:</span>
<span id="show-event-sport-category-label">{{$event->sport_category_id}}</span>
</div>
<div id="event-sport">
<span>Sport:</span>
<span id="show-event-sport-label">{{$event->sport_id}}</span>
</div>
SportCategory Model (empty)
class SportCategory extends Model
{
//
}
Sport Model
class Sport extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
Event Model
class Event extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
Of course at this point what is returned to the view are just the id columns of the events table. How can I return the corresponding name of each id?
Acoording to your database setup:
sport_categories (id, name)
sports (id, sport_category_id, name)
events (id, title, sport_category_id, sport_id)
So.. In this response I'm assuming that:
A Category has many Sports
A Sport has many Events
Notice: Given the fact that a Sport already belongs to a Category, you shouldn't need to specify a category_sport_id key in the events table: the related sport row should already have it.
Now, to your question..
How can I return the corresponding name of each id ?
Defining Relationships.
In your Category.php model:
class SportCategory extends Model
{
protected $guarded = []; // <---
// ...
public function sports() // <---
{
return $this->hasMany(Sport::class);
}
}
Your Sport.php model:
class Sport extends Model
{
protected $guarded = []; // <---
// ...
public function events() // <---
{
return $this->hasMany(Event::class);
}
public function category() // <---
{
return $this->belongsTo(SportCategory::class);
}
}
In your Event.php model:
class Event extends Model
{
protected $guarded = []; // <---
// ...
public function sport() // <---
{
return $this->belongsTo(Sport::class);
}
}
Notice: I added a protected $guarded = []; in each model, this is tell Laravel to include all the fields when returning it to the view. Read this.
Now that your relationships are been defined, you need to load the relationships before returning the variable to the view. Of course you could load the relationship in the view itself but to optimize your query you should eager load this objects like so (in this case, lazy eager loading because you are already resolving the object using Model Binding):
public function show(Event $event)
{
$event->load('sport.category'); // <---
return view('events.show', ['event' => $event]);
}
Now you should have this records in your $event variable: $event->sport and the nested $event->sport->category so in order to output them in your view just:
<p> {{ $event->sport->name }} </p>
<!-- ... -->
<p> {{ $event->sport->category->name }} </p>
Related
Background: The application in question allows users to apply tags from a list of available tags. One article can have many tags and each tag may belong to many articles. The relationship between those is fine, but the complication comes in that a user should only see the tags which they have applied to the article. For instance, if Alice applies ['Apple', 'Banana', 'Cherry'] to article #1, Alice should not see Bob's article #1 tags of ['Grape', 'Orange', 'Kiwi'].
Ideal: An attach would work where the Auth'd user accesses the tags and applies it to an article by creating records in the intermediate pivot table. Additionally, if a user has applied a tag that does not exist yet, they should be able to insert new tags in the same action.
This action would be similar to how tags are applied to a StackOverflow post, actually.
The code I currently works, but just barely, so I wanted to see how others might organize the relationships between these. I'm also open to using a package if one exists that can handle this logic.
Relationships:
class User extends Authenticatable
{
public function articles()
{
return $this->hasMany('\App\Article');
}
public function articles_tags()
{
return $this->belongsToMany('\App\Article_Tag', 'article_tag_user', 'article_tag_id','user_id');
}
}
class Article extends Model
{
public function tags()
{
return $this->belongsToMany('\App\Tag', 'article_tag');
}
public function user()
{
return $this->belongsTo('\App\User', 'user_id');
}
public function article_tag_user()
{
return $this->hasManyThrough('\App\Tag', '\App\Article_Tag_User', 'article_id', 'id', 'article_id', 'tag_id');
}
}
class Tag extends Model
{
protected $fillable = [
'name'
];
public function user()
{
return $this->belongsToMany('\App\User', 'article_tag_user', 'id', 'article_tag_id');
}
public function articles()
{
return $this->belongsToMany('\App\Article', 'article_tag');
}
}
class Article_Tag extends Model
{
protected $table = 'article_tag';
public function user()
{
return $this->belongsToMany('\App\User', 'article_tag_user', 'user_id', 'article_tag_id');
}
public function tags()
{
return $this->belongsTo('\App\Tag');
}
}
class Article_Tag_User extends Model
{
protected $table = 'article_tag_user';
public function tags()
{
return $this->hasManyThrough('\App\Tag', '\App\Article_Tag');
}
}
Table Schema
Tag Table
|id|name|
Article_Tag Table
|id|article_id|tag_id|
Article_Tag_User
|id|user_id|article_tag_id|
You only need one pivot table (it also doesn't need an id):
article_tag_user: article_id | tag_id | user_id
Then you have BelongsToMany relationships between all combinations of Article, Tag, User.
I have the following tables:
flights(id, title, number)
stations(id, title)
flight_price(id, price, flight_id, stationA_id, stationB_id)
flight_station(flight_id, station_id)
flight_station is a pivot table.
My models:
class Flight extends Model
{
public function stations()
{
return $this->belongsToMany('App\Model\Station', 'flight_station', 'flight_id', 'station_id')
}
// To attach data
public function prices()
{
return $this->belongsToMany('App\Model\FlightPrice', 'flight_price', 'flight_id', 'stationA_id')
->withPivot('price');
}
public function price()
{
return $this->hasMany('App\Model\FlightPrice', 'flight_id');
}
}
// Station
class Station extends Model
{
public function flights()
{
return $this->belongsToMany('App\Model\Flight', 'flight_station', 'station_id', 'flight_id');
}
}
class FlightPrice extends Model
{
protected $table = 'flight_price';
public function flights()
{
return $this->belongsToMany('App\Model\Flight', 'flight_price');
}
}
I need the next result (find by id flight):
|stationA_id|stationA_title||stationB_id|stationB_title|price|
Let's say you are trying to retrieve a flight like this:
$flight = Flight::with(['price', 'stations'])->find($id);
This returns a Flight model with the Price and Station models because you eager loaded the relationships.
Now $flight->price will return the Price model associated with the Flight model. If it is not a collection - which I believe is true - the foreach loop has little meaning (or at least not the one you expect). In fact it will loop through the public attributes of the Model class (incrementing, exists, wasRecentlyCreated and timestamps).
So the following piece of code will return 4 booleans.
foreach ($flight->price as $value) {
dump($value);
}
If you want to return the station identifiers, the station titles and the price for each flight then maybe try something like this:
foreach ($flight->stations as $station) {
dump($station->id);
dump($station->title);
}
// I assume the Price model has an attribute named value
dump($flight->price->value)
I mix a little bit with the query in laravel.
I have a list of articles. I would like to get the datas from the author of this article.
Relation model Article
public function author()
{
return $this->belongsTo('App\Models\Author');
}
Relation model Author
public function articles()
{
return $this->hasMany('App\Models\Article');
}
I try this $author = Author::with('articles')->first();
this :
$author = Author::whereHas('articles', function ($query){
$query->where('id', '1');
});
And many others tests, but I doesn't understand all.
My method in my controller :
protected function index()
{
$articles = Article::published()->paginate(8);
return view('pages.blog', [
'articles' => $articles,
]);
}
And above all, how do I display the correct information in my view in my foreach?
Thank you !
To solve the problem you have to define the relationships in the following way.
Article class define, based on the following table structure:
articles {
id : integer [primary key],
...,
author_id : integer [foreign key]
}
class Article extends Illuminate\Database\Eloquent\Model {
protected $table = "articles";
// HERE YOUR CLASS CODE
public function author() {
return $this->belongsTo("Author", "author_id", "id");
}
}
Author class define, based on the following table structure:
authors {
id : integer [primary key],
...
}
class Author extends Illuminate\Database\Eloquent\Model {
protected $table = "authors";
// HERE YOUR CLASS CODE
public function articles() {
return $this->hasMany("Article", "author_id", "id");
}
}
When you use the method belongsTo and hasMany, it is better indicate the label of the external key and the local key.
To display information in your view you have to follow the example:
#foreach ($articles as $article)
<p>Id {{ $article->id }}</p>
...
#endforeach
For more information :
https://laravel.com/docs/5.4/eloquent-relationships
https://laravel.com/docs/5.4/blade
I am trying to grasp the concept of Eloquent ORM by creating a ticketing system at the moment. What I am trying to achieve is:
The tickets with the user who posted the ticket
The feedback belonging to the ticket and the user who entered the
feedback
This is what I have right now:
// TicketController.php
public function index()
{
$tickets = Ticket::with('feedback')->with('user')->orderBy("created_at", "desc")->get();
//dd($tickets);
return View::make('modules.helpdesk.index')->withTickets($tickets);
}
And the following models
// Ticket.php
class Ticket extends Eloquent {
protected $table = 'helpdesk_tickets';
public function feedback()
{
return $this->hasMany('Feedback');
}
public function user()
{
return $this->belongsTo('User');
}
}
// Feedback.php
class Feedback extends Eloquent {
protected $table = 'helpdesk_tickets_feedback';
public function ticket()
{
return $this->belongsTo('Ticket');
}
}
// User.php
class User extends Eloquent {
protected $table = 'users';
public function ticket()
{
return $this->belongsTo('Ticket');
}
}
What I have now is the tickets, their related feedback and user who created the ticket. What I am trying to achieve now is to also get the user who created the feedback.
You need to fix the relation:
// User model
public function tickets()
{
return $this->hasMany('Ticket'); // adjust namespace if needed
}
Next add the relation:
// Feedback model
public function user()
{
return $this->belongsTo('User'); // namespace like above
}
then use eager loading:
// it will execute 4 queries:
// 1st for tickets
// 2nd for feedback
// 3rd for feedbacks' user
// 4th for tickets' user
$tickets = Ticket::with('feedback.user', 'user')->latest()->get();
you can then access the relations in a loop, like below:
#foreach ($tickets as $ticket)
{{ $ticket->title }} by {{ $ticket->user->name }}
#foreach ($ticket->feedback as $feedback)
{{ $feedback->content }}
#endforeach
#endforeach
What you want to do is create nested relations, just like Ticket add a belgonsTo relation on feeback
When you want to use it you can chain relations using the dot notation feedback.user
The code
// Feedback.php
class Feedback extends Eloquent {
protected $table = 'helpdesk_tickets_feedback';
public function ticket()
{
return $this->belongsTo('Ticket');
}
public function user()
{
return $this->belgonsTo('User')
}
}
// TicketController.php
public function index()
{
$tickets = Ticket::with('feedback')->with('user')->with('feedback.user')->orderBy("created_at", "desc")->get();
//dd($tickets);
return View::make('modules.helpdesk.index')->withTickets($tickets);
}
EDIT:
Even though this would work, it will execute more queries than needed. See Jareks answer.
Original Answer:
First of all you need to get your relationships straightened, in User.php you should call the user relationship with HasMany.
public function ticket() {
return $this->hasMany('Ticket');
}
In modules.helpdesk.index you should now have a Ticket Collection since your attaching the $ticket variable to the view.
If you loop through this collection with a foreach loop then what you should get is a model each loop:
foreach($tickets as $ticket) {
// Prints the name property of the Ticket model
print $ticket->name;
// Since a ticket only belongs to ONE user then that means that you are trying to fetch a model
// What we're doing here is getting the User model via the relationship you made in the model Ticket.php and then getting the name.
print $ticket->user()->first()->username;
// Since a ticket can have MANY feedbacks that means were fetching a collection
// which needs to be broken down to models so we do that looping the collection.
// Here we are doing the same thing as with the User model except with a collection.
foreach($ticket->feedback()->get() as $feedback) {
$feedback->text;
}
}
You should definitely check out the Laravel API and see Collection and Model there. http://laravel.com/api/ You get alot of help from there when you get stuck, trust me :)
I hope this answered your question.
I have two table User & Article the relationship between tables are
Model:
class Article extends Eloquent {
public static $table = 'article';
public function User()
{
return $this->has_one('user', 'id');
}
and
class User extends Eloquent {
public static $table = 'user';
public function Article()
{
return $this->belongs_to('article', 'id_user');
}
I want to get name value from User directly on Article view but don't works with error
Trying to get property of non-object
My Controller:
public function action_index()
{
$Article = Article::order_by('id')->paginate(10);
return View::make('article.index')->with('$articles', $Article);
}
My View:
#foreach ($articles->results as $Arti)
<tr>
<td>{{$Arti->id}}</td>
<td>{{$Arti->tag}}</td>
<td>{{$Arti->user->name }}</td> <------ ERROR
<td>{{$Arti->content}}</td>
<td>{{$Arti->date}}</td>
<td>
Have a look at the below, a few things are different to yours...
Article belongs_to User (not has_one)
User has_many Article (not belongs_to)
Your relationships should be named in lowercase, plural for has_many (i.e. articles or user)
Your relationship subjects should be class names (i.e. Article or User)
Foreign keys should be name relationship_id, i.e. user_id
Add ::with() to your query to eager load relationships
When you paginate you need to access ->results in your view
class Article extends Eloquent {
// 3: lowercase 'user'
public function user()
{
// 1: Article belongs to User
// 4: class name 'User'
// 5: Foreign key on article table is user_id
return $this->belongs_to('User');
}
}
// models/user.php
class User extends Eloquent {
// 3: lowercase plural 'articles'
public function articles()
{
// 2: User has many Articles
// 4: class name 'Article'
return $this->has_many('Article');
}
}
// controllers/articles.php
class Article_Controller extends Base_Controller {
public $restful = true;
public function get_index()
{
// 6: Eager load the user relationship, ::with('user')
$articles = Article::with('user')->order_by('id')->paginate(10);
return View::make('articles.index', compact('articles'));
}
}
// views/articles/index.blade.php
// 7: access $articles->results from the paginator
#foreach ($articles->results as $article)
<h1>{{ $article->title }}</h1>
<p>By {{ $article->user->name }}</p>
#endforeach
{{ $articles->links() }}