Laravel 4 Unable to Output Data for Complex Model Structure - php

First post so here goes:
I'm building a stats website with Laravel 4 and have set up relationships between several models -
Player, Game, Team, PlayerData, StatType
All of these have corresponding tables:
players: id, name, team_id
games: id, home_team_id, away_team_id, week (note 2 teams in a single game)
teams: id, name
stat_types: id, name
player_datas: id, player_id, stat_type_id, stat_value, game_id
The idea being that every player plays for a team, who plays once a week, and each stat for a player in each game will have an entry in the player datas table (e.g. player 1, stat_id 1, value 2, game 1, player 1, stat_id 2, value 10, game 1)
So what I'm looking to do is output a table when someone wants to view a player on the player show.blade.php (* represents placeholder):
****UPDATE: I've got the data i want to appear by making Watcher's suggested changes, but getting the 2nd and 3rd cells like below (in my view) seems inefficient? Think I'm missing something
#foreach($team_fixtures as $team_fixture)
<tr>
<td>{{$team_fixture->homeTeam->team_name}} vs {{$team_fixture->awayTeam->team_name}}</td>
<td>{{$team_fixture->playerData()->where('player_id', $player->id)
->where('stat_type_id', '1')
->pluck('stat_value')}}</td>
<td>{{$team_fixture->playerData()->where('player_id', $player->id)
->where('stat_type_id', '2')
->pluck('stat_value')}}</td>
</tr>
#endforeach
I can't tell whether I'm missing a pivot table (player_data_stat_types??) or have got the relationships wrong? If there's a better way to structure I'd be happy to do that, I'm just not sure where to start with this one. I started doing a for each $team_fixtures but could not get the stats to output. My problem is that the fixture is the left hand column, but the player data table has multiple entries against a game_id...
My player controller looks like:
public function show($id)
{
$player = Player::findOrFail($id);
$team_fixtures = Game::where('home_team_id', '=', $player->team_id)
->orWhere('away_team_id', '=', $player->team_id)
->get();
return View::make('site/players.show', compact('player', 'team_fixtures'));
}
And Models are linked as follows:
Team:
public function players() {
return $this->hasMany('Player');
}
public function games() {
return $this->belongsToMany('Game');
}
Player:
public function team() {
return $this->belongsTo('Team');
}
public function playerData(){
return $this->hasMany('PlayerData');
}
Game:
public function playerData() {
return $this->hasMany('PlayerData');
}
public function homeTeam()
{
return $this->hasOne('Team', 'id', 'home_team_id');
}
public function awayTeam()
{
return $this->hasOne('Team', 'id', 'away_team_id');
}
public function playerData(){
return $this->belongsTo('Player', 'player_id', 'id');
}
PlayerData:
public function statType(){
return $this->belongsTo('StatType', 'stat_id', 'id');
}
public function game(){
return $this->belongsTo('Game');
}
StatType:
public function playerData(){
return $this->hasMany('PlayerData');
}

Firs off, your relations are wrong. Here's how they should look like:
// Player
public function team()
{
return $this->belongsTo('Team');
}
public function stats()
{
return $this->belongsToMany('StatType', 'player_datas')
->withPivot('game_id', 'stat_value');
}
// Team
public function players()
{
return $this->hasMany('Player');
}
public function awayGames()
{
return $this->hasMany('Game', 'away_team_id');
}
public function homeGames()
{
return $this->hasMany('Game', 'home_team_id');
}
// Game
public function homeTeam()
{
return $this->belongsTo('Team', 'home_team_id');
}
public function awayTeam()
{
return $this->belongsTo('Team', 'away_team_id');
}
public function players()
{
return $this->belongsToMany('Player', 'player_datas')
->withPivot('stat_value', 'stat_type_id');
}
I don't think you need PlayerData model at all, and I assume your stat_types table has name instead of value field.
Then, to achieve what you wanted, ie. to show user's performance (stats values) for each game, you want something like this:
// controller
$player = Player::find($id);
$stats = $player->stats->groupBy('pivot.game_id');
// view
<ul>
#foreach ($games as $game)
<li>
{{ $game->homeTeam->name }} vs {{ $game->awayTeam->name }}
<table>
#foreach ($stats->get($game->id) as $stat)
<tr><td>{{ $stat->name }}: </td><td>{{ $stat->pivot->stat_value }}</td>
#endforeach
</table>
</li>
#endforeach
</ul>

When you are declaring a relationship in a model, you should specify what you are relating the model to. In your code I see at least one example of this:
// Game model
public function teams() {
return $this->belongsToMany('Game');
}
This should really be relating your Game model to your Team model, but you are relating games to games here. Try this out:
public function teams() {
return $this->belongsToMany('Team');
}
The method names in the relationship simply set up an attribute with which you can use on an instance of your model, it has no other special meaning. This will also work:
public function chicken() {
return $this->belongsToMany('Team');
}
With this, I could access the relationship of an instance by using $game->chicken and be able to iterate over many Team instances.
Another issue is that Eloquent relies on convention when using the other default parameters when declaring relationships. If I have two models I want to relate, say Modela and Modelb, then it assumes the table names will be modela and modelb. Furthermore, the linking column will be assumed to be modelb_id, for instance, inside the modela table.
You can override this behavior (which you will need to do at least for your Game relationships, since you have home_team_id and away_team_id). I refer you to the documentation, but here's an example:
// Game model
public function homeTeam()
{
return $this->hasOne('Team', 'home_team_id');
}
Notice I also changed your belongsToMany to a hasOne, which I think will work better for you and is more in line with what you are trying to accomplish.

Related

Laravel nested eager load specific columns

I am working with nested eager loading is there a way you can pick out certain columns from the middle relation in account.user.location ?
User Model
public function account(): HasMany
{
return $this->hasMany(Account::class);
}
public function location(): BelongsTo
{
return $this->belongsTo(Location::class);
}
Account model
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
Location model
public function user(): HasMany
{
return $this->hasMany(User::class);
}
Controller method that works
This method returns the nested relation but i want certain columns from the user relation instead of listing them all.
public function show(string $id)
{
$film = Film::with([
'account.user.location'
])->findOrFail($id);
}
Controller method that doesn't work
This is my sample code i've tried to pick out the name column from users then display the location relation.
public function show(string $id)
{
$film = Film::with([
'account.user:id,name',
'account.user.location:id',city
])->findOrFail($id);
}
Response
This is the response which is returned its returning the location as null from the not working method
+"account": {#2061
+"id": "191067a6-4c38-423d-a972-bb3a842ca89e"
+"user": {#2064
+"id": "d9f381c1-3899-367c-8d60-6d2bc3db6d23"
+"name": "Domenick"
+"location": null
Im unsure on how i pick out specific columns from the middle relation and then joining the location. Can i get some assistance on where i am going wrong?
Laravel is loading each level of relationships after another. In other words, if you use A::with('b.c')->get(), then Eloquent will first load all As, then all of their referenced Bs and finally all of the Cs referenced by the loaded Bs. The ORM uses navigation properties, i.e. foreign keys, to do so. If you omit these foreign keys on intermediate models, the framework is not able to load the referenced models anymore.
If you'd do it manually, you would use the following queries (used IDs and foreign keys are examples):
SELECT * FROM a; // returns As with ids 1, 2, 3
SELECT * FROM b WHERE a_id IN (1, 2, 3); // returns Bs with ids 4, 5, 6
SELECT * FROM c WHERE b_id IN (4, 5, 6);
In your case, it should be sufficient to use the following code:
public function show(string $id)
{
$film = Film::with([
'account.user:id,account_id,location_id,name',
'account.user.location:id,city'
])->findOrFail($id);
}
Update your User Model
public function account()
{
return $this->hasMany(Account::class, 'user_id');
}
public function location()
{
return $this->belongsTo(Location::class);
}
Update you Account class to
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
In your controller method try this
public function show($id)
{
$film = Film::where('id', $id)
->with([
'account.user:id,name',
'account.user.location:id',city
])->get();
}

Laravel 4 Display ads to load any pictures from another table

I have a problem with displaying data from the form. He wants to load the data from two tables joined the foreign key.
I do not know what I'm doing wrong because I Chaly time returns the message:
Undefined property: Illuminate\Database\Eloquent\Relations\HasMany::$file
offers tabel:
id
user_id
title
...
photosofoffers tabel:
id
offer_id <- primary key offers.id
file (url photos)
...
my view:
#foreach($offers as $offer)
{{ HTML::image($offer->photosofoffers()->file, $offer->title, array('width'=>'50')) }}
my model Offer:
protected $fillable = array('id_category','user_id', 'title', 'description', 'price', 'availability');
public static $rules = array(
'id_category'=>'required|integer',
'title'=>'required|min:2',
'description'=>'required|min:2',
'price'=>'required|numeric',
'availability'=>'integer'
);
public function photosofoffers(){
return $this->hasMany('Photosofoffer');
}
public function category() {
return $this->belongsTo('Category');
}
}
my model Photosofoffer
<?php
class Photosofoffer extends Eloquent {
public function offer(){
return $this->belongsTo('Offer');
}
public function offers() {
return $this->hasMany('Offer');
}
}
How to display ads to load any pictures from another table?
hasMany means there are many photos of one offer. Therefore is it wise to call $something->photosofoffer()->photo ? If the return is an object, you'll definitely get an error.
First do dd($offer->photosofoffers()) or dd($offer->photosofoffers) to see what's happening. Then, if the object is properly being derived, You need to check loop through it. like #foreach($offer->photosofoffers as $photo) your loop of diplaying image #endforeach.
If there is nothing being derived, change the Controller function where you collect the actual $offer and make it Model::with('photoofoffers')->get() or first()
That should clear this up.
Hope this helps.
YK.

How to set Eloquent relationship belongsTo THROUGH another model in Laravel?

I have a model Listing that inherits through its belongsTo('Model') relationship should inherently belong to the Manufacturer that its corresponding Model belongs to.
Here's from my Listing model:
public function model()
{
return $this->belongsTo('Model', 'model_id');
}
public function manufacturer()
{
return $this->belongsTo('Manufacturer', 'models.manufacturer_id');
/*
$manufacturer_id = $this->model->manufacturer_id;
return Manufacturer::find($manufacturer_id)->name;*/
}
and my Manufacturer model:
public function listings()
{
return $this->hasManyThrough('Listing', 'Model', 'manufacturer_id', 'model_id');
}
public function models()
{
return $this->hasMany('Model', 'manufacturer_id');
}
I am able to echo $listing->model->name in a view, but not $listing->manufacturer->name. That throws an error. I tried the commented out 2 lines in the Listing model just to get the effect so then I could echo $listing->manufacturer() and that would work, but that doesn't properly establish their relationship. How do I do this? Thanks.
Revised Listing model (thanks to answerer):
public function model()
{
return $this->belongsTo('Model', 'model_id');
}
public function manufacturer()
{
return $this->belongsTo('Model', 'model_id')
->join('manufacturers', 'manufacturers.id', '=', 'models.manufacturer_id');
}
I found a solution, but it's not extremely straight forward. I've posted it below, but I posted what I think is the better solution first.
You shouldn't be able to access manufacturer directly from the listing, since manufacturer applies to the Model only. Though you can eager-load the manufacturer relationships from the listing object, see below.
class Listing extends Eloquent
{
public function model()
{
return $this->belongsTo('Model', 'model_id');
}
}
class Model extends Eloquent
{
public function manufacturer()
{
return $this->belongsTo('manufacturer');
}
}
class Manufacturer extends Eloquent
{
}
$listings = Listing::with('model.manufacturer')->all();
foreach($listings as $listing) {
echo $listing->model->name . ' by ' . $listing->model->manufacturer->name;
}
It took a bit of finagling, to get your requested solution working. The solution looks like this:
public function manufacturer()
{
$instance = new Manufacturer();
$instance->setTable('models');
$query = $instance->newQuery();
return (new BelongsTo($query, $this, 'model_id', $instance->getKeyName(), 'manufacturer'))
->join('manufacturers', 'manufacturers.id', '=', 'models.manufacturer_id')
->select(DB::raw('manufacturers.*'));
}
I started off by working with the query and building the response from that. The query I was looking to create was something along the lines of:
SELECT * FROM manufacturers ma
JOIN models m on m.manufacturer_id = ma.id
WHERE m.id in (?)
The query that would be normally created by doing return $this->belongsTo('Manufacturer');
select * from `manufacturers` where `manufacturers`.`id` in (?)
The ? would be replaced by the value of manufacturer_id columns from the listings table. This column doesn't exist, so a single 0 would be inserted and you'd never return a manufacturer.
In the query I wanted to recreate I was constraining by models.id. I could easily access that value in my relationship by defining the foreign key. So the relationship became
return $this->belongsTo('Manufacturer', 'model_id');
This produces the same query as it did before, but populates the ? with the model_ids. So this returns results, but generally incorrect results. Then I aimed to change the base table that I was selecting from. This value is derived from the model, so I changed the passed in model to Model.
return $this->belongsTo('Model', 'model_id');
We've now mimic the model relationship, so that's great I hadn't really got anywhere. But at least now, I could make the join to the manufacturers table. So again I updated the relationship:
return $this->belongsTo('Model', 'model_id')
->join('manufacturers', 'manufacturers.id', '=', 'models.manufacturer_id');
This got us one step closer, generating the following query:
select * from `models`
inner join `manufacturers` on `manufacturers`.`id` = `models`.`manufacturer_id`
where `models`.`id` in (?)
From here, I wanted to limit the columns I was querying for to just the manufacturer columns, to do this I added the select specification. This brought the relationship to:
return $this->belongsTo('Model', 'model_id')
->join('manufacturers', 'manufacturers.id', '=', 'models.manufacturer_id')
->select(DB::raw('manufacturers.*'));
And got the query to
select manufacturers.* from `models`
inner join `manufacturers` on `manufacturers`.`id` = `models`.`manufacturer_id`
where `models`.`id` in (?)
Now we have a 100% valid query, but the objects being returned from the relationship are of type Model not Manufacturer. And that's where the last bit of trickery came in. I needed to return a Manufacturer, but wanted it to constrain by themodelstable in the where clause. I created a new instance of Manufacturer and set the table tomodels` and manually create the relationship.
It is important to note, that saving will not work.
$listing = Listing::find(1);
$listing->manufacturer()->associate(Manufacturer::create([]));
$listing->save();
This will create a new Manufacturer and then update listings.model_id to the new manufacturer's id.
I guess that this could help, it helped me:
class Car extends Model
{
public function mechanical()
{
return $this->belongsTo(Mechanical::class);
}
}
class CarPiece extends Model
{
public function car()
{
return $this->belongsTo(Car::class);
}
public function mechanical()
{
return $this->car->mechanical();
}
}
At least, it was this need that made me think of the existence of a belongsToThrough
You can do something like this (Student Group -> Users -> Poll results):
// poll result
public function studentGroup(): HasOneDeep
{
return $this->hasOneDeepFromRelations($this->user(), (new User())->studentGroup());
}

Eloquent model with multiple one to many to same table

I have an existing table structure I'm trying to model with Eloquent (Laravel 4) which has 3 one to many relationships to the same table. Basically, each unit can have a home location, a current location and a customer's location.
Note, I've simplified this for the question. The unit table has an unitid, and a homeid, currentid and customerid. Each of homeid, currentid and customerid is a foreign key in the mysql database to the location table on the locationid. The location table also has a name field.
In my Unit model, I have
public function home() { return $this->belongsTo('Location', 'homeid', 'locationid'); }
public function current() { return $this->belongsTo('Location', 'currentid', 'locationid'); }
public function customer() { return $this->belongsTo('Location', 'customerid', 'locationid'); }
In my Location model I have
public function homes() { return $this->hasMany('Unit', 'homeid', 'locationid'); }
public function currents() { return $this->hasMany('Unit', 'currentid', 'locationid'); }
public function customers() { return $this->hasMany('Unit', 'customerid', 'locationid'); }
Now, in my Units controller I have
$units = Unit::with(['home','current','customer'])->paginate(10);
return View::make('units.index')->with('units',$units);
In units.index view I can refer to
foreach ($units as $unit) {
...
$unit->home->name //<-- this works
$unit->current->name //<-- this doesn't
$unit->customer->name //<-- neither does this
...
}
As fas as I can tell from the documentation, I've done everything right. Why would the first FK work, but neither of the two others?
Edit: the error given on the lines marked as not working (when uncommented) is
"Trying to get property of non-object"
The models are correct, thanks #deczo for pointing out the obvious.
The error was mine - not checking for null on the relations before trying to reference the related records.
I.E. $unit->current->name needed to be is_null($unit->curent)?'':$unit->current->name
A stupid PEBKAC error :-)
--Quog

Semi-complex Eloquent query

I am working on a eloquent query to compile a newsletter but I have hit a brick wall.
What I'm trying to do is create a UI where the user can select a publication and date. Ideally it would then compile a list of that publication's categories (where stories > 0) and stories belonging to it.
Here are my 3 models:
Story
public function user()
{
return $this->belongsTo('User', 'user_id');
}
public function publication()
{
return $this->belongsTo('Workbench\Dailies\Publication', 'publication_id');
}
public function category()
{
return $this->belongsTo('Workbench\Dailies\Category', 'category_id');
}
Publication
public function story()
{
return $this->belongsTo('Workbench\Dailies\Story');
}
public function stories()
{
return $this->hasMany('Workbench\Dailies\Story', 'publication_id');
}
public function category()
{
return $this->belongsTo('Workbench\Dailies\Category', 'publication_id');
}
Category
public function story()
{
return $this->belongsTo('Workbench\Dailies\Story', 'category_id');
}
public function publications()
{
return $this->belongsTo('Workbench\Dailies\Publication', 'publication_id');
}
public function stories()
{
return $this->hasMany('Workbench\Dailies\Story', 'category_id');
}
Here is how my tables look:
Story
content
user_id
publication_id
category_id
publish_date
Publication
id
name
Category
id
name
publication_id
Here is what I currently have in my Repository.
public function compileStories($input)
{
return Category::has('stories', '>', 0)
->with('publications')
->whereHas('stories', function ($query) use ($input)
{
$query->where('publish_date', $input['publish_date']);
$query->where('publication_id', $input['publication_id']);
});
}
Am I headed in the right direction here or is there any way to improve the code above? It is not currently functioning as expected.
There are a couple of things I see here that may help straighten you out.
First - Some of the models have strange relationships without knowing more about your whole application. The Story model does not need the publication relationship since it can be retrieved through the category relationship, unless you have need of it otherwise. The Category model does not need both a story and a stories relationship, again, unless there's more to the story I don't know. In your example, you should only need the hasMany relationship. The Publication model only needs the categories relationship.
Now, after some cleanup of the models, let's look at your query. Using the category model to return your results seems completely appropriate for your desired results. You can check for the publication without having to dive into your stories, though. I haven't tested it, but you may not need the use $input line since $input is in the larger scope. You're also missing a conditional check in your where statments in the whereHas clause. The query should be able to be simplified as follows:
Category::where('publication_id', '=', $input['publication_id'])
->whereHas('stories', function($query)
{
$query->where('publish_date', '=', $input['publish_date']);
})
->get()

Categories