Laravel 5.3 - HasMany Relationship not working with Join statement - php

I am trying to retrieve symbols with their comments using hasMany in laravel 5.3
Symbol.php
public function comments() {
return $this->hasMany('App\Comment');
}
Comment.php
public function symbol() {
return $this->belongsTo('App\Symbol');
}
when I run:
$symbols = Symbol::with('comments')->paginate(100);
I get the correct output (lists all symbols with their comments)
#foreach ($symbols as $s)
{{ $s->name }}
#foreach ($s->comments as $c)
{{ $c->body }}
#endforeach
#endforeach
but when I add a join to the statement:
$symbols = Symbol::with('comments')
->join('ranks', 'symbols.id', '=', 'ranks.symbol_id')
->join('prices', 'symbols.id', '=', 'prices.symbol_id')
->paginate(100);
The foreach loop has no comments for every symbol. Any idea why the join would be causing this?

When you are doing joins like this, attributes with the same names will be overwritten if not selected. So select the attributes you need for your code, and nothing else. As shown below.
$symbols = Symbol::with('comments')
->join('ranks', 'symbols.id', '=', 'ranks.symbol_id')
->join('prices', 'symbols.id', '=', 'prices.symbol_id')
->select('symbols.*', 'ranks.importantAttribute', 'prices.importantAttribute')
->paginate(100);
Basicly i think your ids are being overwritten, by the two joins because they also have id fields, i have had a similar problem doing joins, and it breaks relations if the id is overwritten.
And you have to be carefull, all fields who shares names can be overwritten and parsed wrong into the models.

Related

Laravel 5.5: Convert query builder to eloquent

Is there a way to transform this query builder to eloquent?
$transactions = DB::table('transactions')
->join('accounts', 'transactions.account_id', '=', 'accounts.id')
->select('transactions.*', 'accounts.name as account_name')
->paginate(5);
I tried it with One To Many. But it need the find() function so it give me one account's transactions but I need to select all transactions with accounts.name
In comments, you've said you have one to many relationship defined (I assume it's Accounts has many Transactions) and you need to get all transactions with account name, so do this:
Transaction::with('account')->paginate(5);
Where account is relationship:
public function account()
{
return $this->belongsTo(Account::class);
}
Then you'll be able to display the data like this:
#foreach ($transactions as $transaction)
{{ $transaction->id }}
{{ $transaction->account->name }}
#endforeach
You will need to use models to do that.
Transaction model and Account model.
Define their relationships
Create a method in the transaction model to retrieve the related account names.
function accoutNames() { //blablabla }
Do the query in controller, something like that:
Transaction->accountNames();

How to remove brackets and quote when displaying array results - Laravel

I am new to Laravel & working on my first project. A small issue I am having is when I build a collection (array) and insert results into Laravel Form Select, and export to Excel, my results are wrapped in ["array"] brackets and quotes.
Code to build the collection:
$p3 = DB::table('players')
->where ('id', $player3)
->selectRaw('id, CONCAT(fname," ",lname) as full_name')
->pluck('full_name', 'id');
$played['p'] .= $p3;
$p4 = DB::table('players')
->where ('id', $player4)
->selectRaw('id, CONCAT(fname," ",lname) as full_name')
->pluck('full_name', 'id');
Note, there are 16 above (grabbing names from associated table)... I then build the collection:
$played = collect([$p1, $p2, $p3, $p4, $p5, $p6, $p7, $p8, $p9, $p10, $p11, $p12, $p13, $p14, $p15, $p16]);
and pass to view:
return view('grades.matchcard.edit', compact('data','data1', 'played', 'mid'));
As per image, the results are wrapped in brackets/quotes... Same when I export to Excel... I tried join & implode (get errors). Tried using
str_replace (kind of worked - but not great...
Any sugggestions would be greatly appreciated. thanks
Each of your DB queries generates a collection of one element, and then you collect those collections into your final $played variable. So iterating over it, each item is actually another collection. You want to flatten the collections, like so:
$played = collect([$p1, $p2, ...])->flatten();
BTW, I don't see the rest of your code but the chunk you've shown is quite inefficient, and might be better written:
$played = DB::table('players')
->whereIn('id', [$player3, $player4, $player5 .... all your ids ...])
->selectRaw('id, CONCAT(fname," ",lname) as full_name')
->pluck('full_name', 'id');
Here you are doing just one DB query, instead of many, and the query returns your collection, no need for further collecting and flattening. More info in the docs.
UPDATE to clarify based on comments:
The pluck() method above keys the resulting collection by the second parameter. So $played should be a collection of correctly matched id => full_name elements. To display the player with id 3's full name, you should be able to use $played[3].
BTW: do you have a Player model? If not, to do things the neat Laravel way you probably should, and it would make things a lot simpler. For eg:
In your model
Define an accessor, to make getting full name easier.
public function getFullNameAttribute($value) {
return $value->fname . ' ' . $value->lname;
}
More info about accessors in the docs.
In your controller
$players = App\Player::find([$player3, $player4, $player5 .... all your ids ...]);
return view('whatever.your.view.is', ['players' => $players]);
More info about retrieving models in the docs.
In your view
<select>
#foreach ($players as $player)
<option value='{{ $player->id }}'>{{ $player->full_name }}</option>
</select>
#endforeach
If you want players to be listed in a particular order, you should either define that order in your model/database (eg a players.ranking field?), or, if the order is based on something derivable you could order it on the fly. Eg if you need them alphabetical you can add ->orderBy('lname') to the $players query in your controller.
try
$p4 = DB::table('players')
->where ('id', $player4)
->selectRaw('id, CONCAT(fname," ",lname) as full_name')
->get();
And in blade
{{$p4->id}}
{{$p4->full_name}}
EDIT
Sorry i didnt see the hole question
you can do something like this
$p4 = DB::table('players')
->where ('id', $player4)
->selectRaw('id, CONCAT(fname," ",lname) as full_name')
->get();
$played = new Collection();
$played.push($p4);
And in blade this should work
#foreach($played as $object)
{{$object->id}}
{{$object->full_name}}
#endforeach

Laravel #foreach

I need to get each comment of news . Its working good for firstorFail() item
{{$news->comments()->firstOrFail()->name}}
But bring me empty result when im try this one with foreach:
#foreach($news->comments() as $comment)
{{$comment->name}}
#endforeach
The function firstOrFail will return exactly one comment. Seems like you actually want all comments? You should use this in your controller
$news->comments; // yeah that's it, it will load all comments
Also return news
return view('my.view', compact('news'));
Then use it in blade
#foreach($news->comments as $comment)
{{$comment->name}}
#endforeach
the line
$news->comments()
would require you to also call ->get() , because comments() will return the relation instead of the actual data.
This: $news->comments() bringing you query builder probably. So what you need is to ->get() for execute query like this:
#foreach($news->comments()->get() as $comment)
{{$comment->name}}
#endforeach
Edit
or as a #Roy Philips mentioned: $news->comments

How to manipulate an object in laravel

I have a laravel query as the one below:
$campaign = Campaign::with(array('tracks.flights' => function($q) use ($dates)
{
$q->whereRaw("flights.start_date BETWEEN '". $dates['start']."' AND '".$dates['end']."'")->orderBy('start_date')
->with('asset')
->with('comments');
}
))
->with('tracks.group')
->with('tracks.media')
->orderBy('created_at', 'desc')
->find($id);
I am very new to laravel and right now the response from this query returns all the data required including the comments with the comments attributes from the DB.
What i want to achieve is manipulate the comments object so it includes the user name from the users table as the comments table has the user_id only as an attribute.
How can I achieve this? I am very new to laravel.
Your Comment model must have a relationship with the User model, such as:
public function user()
{
return $this->belongsTo('App\User');
}
Now when you are iterating comments you are able to do $comment->user->name or in your case it will be something like this:
#if(!$campaign->tracks->flights->isEmpty())
#foreach($campaign->tracks->flights as $flight)
#if(!$flight->comments->isEmpty())
#foreach($flight->comments as $comment)
{!! $comment->user->name !!}
#endforeach
#endif
#endforeach
#endif
Once you can do this, next step is to understand eager load with eloquent.

Data from Two tables duplicating on join

I have got two tables , I would use facebook POST as an example.
Post Table
Comment Table
My query
$result = DB::table('posts')
->join('comments', 'posts.post_ID', '=', 'comments.post_ID')
->get();
I will receive an array of posts and comments merge. For each comments that exist , they will have the posts data.
What i want is to be able to do something like
foreach($posts as $post){
foreach($post['comment']{
}
}
Any idea how i can do that?
Something like this should work:
$result = DB::table('posts')
->join('comments', 'posts.id', '=', 'comments.post_id')
->get();
In the view:
#foreach($posts as $post)
{{ $post->post_title }}
{{ $post->message }}
#endforeach
Make sure that, field names in ->join('comments', 'posts.id', '=', 'comments.post_id') are right or change accordingly. post_title/comment_text is used to demonstrate the example, change to it's original table's field name and {{ }} is used in the example to echo the data, if you are not using Blade then use echo $post->post_title instead.
Update::
If you use Eloquent then use:
// Assumed you have defined comments as relationship method
$posts = Post::with('comments')->get(); // it's faster and called eager loading
Then in the view:
#foreach($posts as $post)
{{ $post->post_title }}
#foreach($post->comments as $comment)
{{ $comment->message }}
#endforeach
#endforeach
I will receive an array of posts and comments merge. For each comments that exist , they will have the posts data.
This is correct SQL behavoir when using a join. You will get the contents of both the rows inside the posts and comments rows on your JOIN.
According to Laravel 4.2's documentation on the join method, the parameters of the join function are:
join($table, $one, $operator = null, $two = null, $type = 'inner', $where = false)
Using an INNER JOIN, you are only going to get rows returned to you with your query (using an inner join) if you have a comment for all of the posts that you want data from. Additionally, with your INNER JOIN, if there is no comment on your post, you will not get any posts returned to you.
Also, you are not going to be able to separate all of your comments from your posts, which may mean that you are getting results returned for posts that you
The simple way to solve this for you would be to make two eloquent models:
class Post extends Eloquent {
protected $table = 'posts';
public function getComments() {
return $this->hasMany('Comments', 'post_id');
}
}
class Comments extends Eloquent {
protected $table = 'comments';
}
From that you can query for all of the posts with eager loading:
$posts = Post::with('comments')->get();
and inside your view, you go:
foreach($posts as $post) {
// echo the $post? (title, author etc)
foreach($post->comments() as $comment) {
// echo the $comment? (author, content etc)
}
}

Categories