Laravel belongsTo returning null when using 'with' - php

I'm just getting started with Laravel so please forgive any noobness.
I have a User and Order model, a user has many orders:
# Inside User model
public function orders()
{
$this->hasMany('Order');
}
# Inside Order
public function user()
{
return $this->belongsTo('User');
}
// Not sure if this is upsetting anything (also in Order)
public function products()
{
return $this->belongsToMany('Product');
}
So I think I have the above right.
But when I do this:
$users = User::with('orders')->find(1);
return $users;
I get Call to a member function addEagerConstraints() on null.
However, if I do it the other way around, it works great:
$orders = Order::with('User')->get();
return $orders;
What am I doing wrong / what don't I understand?! Or is my problem bigger than I think?
Database:

The problem is you don't have return for your orders relationship. It should be:
public function orders(){
return $this->hasMany('Order');
}
You should also use your relationships case sensitive. you showed:
$orders = Order::with('User')->get();
is working, but you should rather use
$orders = Order::with('user')->get();
to avoid extra queries to your database in future

For anyone else that runs across this, I was having the same issue, but my problem was that I had the foreign/local keys swapped. Example:
// This is correct for hasX relationships
public function user() {
return $this->hasOne('App\Models\User', 'user_id', 'local_key_user_id');
}
// This is correct for belongsTo relationships
public function user() {
return $this->belongsTo('App\Models\User', 'local_key_user_id', 'user_id');
}
Notice that for hasX relationships, the foreign key is the second parameter, and the local key is the third. However, for belongsTo relationships, these two are swapped.

Probably doesn't answer this particular question but it relates to the title. I had the same issue here is the wrong query
$offer = Offer::with([
'images:name,id,offer_id',
'offer_options:offer_option,value,id,offer_id',
'user:id,name,avatar'])
->select(['id', 'views', 'type', 'status'])
->where('id', $id)->get();
the model look like this
class Offer extends Model {
function user(): BelongsTo {
return $this->belongsTo(User::class);
}
}
The User
class User extends ..... {
function offer(): HasMany {
return $this->hasMany(Offer::class);
}
}
The issue with the query is I was not selecting user_id, i.e in my select function user_id column was not included and that is why I was getting null for user
according to Laravel docs
When using this feature, you should always include the id column and
any relevant foreign key columns in the list of columns you wish to
retrieve.
So the correct query is
$offer = Offer::with([
'images:name,id,offer_id',
'offer_options:offer_option,value,id,offer_id',
'user:id,name,avatar'])
->select(['id', 'views', 'type', 'status','user_id'])
->where('id', $id)->get();

Related

1 to 1 and 0 to many relationship Laravel not working

I am struggling with an eloquent request. Let me explain what I want to do:
I have two models: User and Item
One User can have many Item and one Item belongs to One user.
I wrote the two method for this relation in my models as followed:
class Item extends Model
{
public function user() {
return $this->belongsTo(User::class);
}
}
class User extends Model {
public function items() {
return $this->hasMany(Item::class, 'items', 'user_id', 'user_people_id');
}
}
I try to access to the items from my controller its user's relation with:
public function index()
{
$items = Item::with('user')->get();
dd($items);
FYI: I seeded my items table with 10 items and my user table with 4 users:
items table:
users table:
My problem is that in the when I check my query with dd() here is what I get: Only the 4 first items get the relation, the others 6 return a null value
Relation working:
Relation returning null:
Thank you for helping me!
According to Laravel doc, hasMany relationship parameters are the following:
return $this->hasMany(Myclass::class, 'foreign_key', 'local_key');
So try to change your relationship in your User class like that
// change this
return $this->hasMany(Item::class, 'items', 'user_id', 'user_people_id');
// to this
return $this->hasMany(Item::class, 'user_people_id', 'id');
The easiest solution would be to rename your foreign key to user_id. That is what Laravel expects, so you won't need to deal with extra arguments in your hasMany() functions.
If you can't do that I think this'll work: return $this->hasMany(Item::class, 'user_people_id');.
Please try to add all in your query to see if it will work:
Update
public function index()
{
$items = Item::all();
dd($items);
}

Laravel Eloquent getting data from relations

I have Task model. My Task model has some relationships and it currently looks like this:
class Task extends Model
{
use HasFactory;
public $timestamps = false;
public function city()
{
return $this->hasOne(City::class, 'id', 'city_id');
}
public function type()
{
return $this->hasOne(Type::class, 'id', 'type_id');
}
public function note()
{
return $this->hasOne(Note::class, 'id', 'note_id');
}
public function operator()
{
return $this->hasOne(User::class, 'id', 'operator_id');
}
}
Now, in my TasksController I need to get Tasks that match certain criteria, like this:
$tasks = Task::whereCityId($city->id)->whereTypeId($type->id)->get()->toArray();
The problem is that fields named city_id type_id note_id operator_id will get my integer values that they have.
Instead I would like to get certain value from a related Model.
For example:
operator_id should be replaced with username from User table that corresponds to the user id.
An obvious solution to this would be to simply use foreach loop, go through my results and get the data I need and simply create another array with the information replaced, but I am not sure if this is the best idea and perhaps there is something better.
You have to change in your code:
$this->hasOne(ClassName::class, 'id', 'foreign_key');
To
$this->belongsTo(ClassName::class, 'foreign_key', 'id');
because Task's id does not available as foreign key in these tables. These table's id present in task table as foreign key so you have to use belongsTo() relationship to tell script from where these id belongs.
Then access properties like this:
$tasks = Task::with("type", "city", "operator")
->whereCityId($city->id)->whereTypeId($type->id)->get();
foreach($tasks as $task){
echo $task->city->name;
}
first you should fix your relation:
public function city()
{
return $this->hasOne(City::class,'city_id','id');
}
and so one the same error, foreign key in argument order comes before the primary key.
after that you can use addSelect:
$tasks = Task::whereCityId($city->id)->whereTypeId($type->id)
->addSelect(['userName' => User::select('name')
->whereColumn('users.id', 'tasks.operator_id')
->limit(1)])->get()->toArray();
i think this will help better than what you ask.
$tasks = Task::whereCityId($city->id)
->whereTypeId($type->id)
->with('operator')
->get()->toArray();
with('operator') is ORM feature that make you collection to include its relation as collection property. In this case it will convert to array property.
you could access it from your foreach function as
#foreach($task as $key)
$key['operator']['username']
#endforeach
Have a nice day

Retrieving collection from linked eloquent models laravel

I am using laravel 5.3 and need a bit of help with Eloquent model queries. I have three models (UserDetails, Categories, Articles). I have a relationship between UserDetails->Categories (belongstoMany), and a relationship between Categories->Articles (belongstoMany) which work well. However how would I go about getting the relationship data between Userdetails->Categories->Articles.
Each individual relationship is working fine i.e. Userdetails::find(1)->categories and Categories::find(1)->Articles.
I have a feeling that scopes may be the answer but they don't seem to work when I've attempted it.
Relationships in models
UserDetails.php
public function Categories(){
return $this->belongstoMany('App\Categories', 'users_cats', 'user_id','cat_id');
}
Categories.php
public function articles(){
return $this->belongsToMany('App\Article', 'article_categories', 'categoryID', 'articleID');
}
Ive looked into HasManyThrough function but again, I'm having issues implementing it, as far as I can see it should be
return $this->hasManyThrough('App\Article', 'App\Categories', TertiaryForeignKey, FinalForeignKey, LocalForeignKey);
My tables are set up as
articles_categories pivot table
articleID – primary key of the article
categoryID – primary key of the category
users_cats pivot table
user_id – primary key of the userdetails
cat_id – primary key of the categories
Based on this it the hasManyThrough should look like this?
public function articles(){
return $this->hasManyThrough('App\Article', 'App\Categories', 'user_id', 'articleID', 'id');
}
however this returns the error
Column not found: 1054 Unknown column 'categories.user_id' in 'field list'
update
So if you want to have this kind of relationship
userdetails->categories->articles
then you need to make this:
Userdetail model:
public function categories()
{
return $this->hasMany(Category::class);
}
public function articles()
{
return $this->hasManyThrough(Article::class, Categories::class);
}
Category model:
public function userdetails()
{
return $this->belongsTo(Userdetails::class);
}
public function categories()
{
return $this->hasMany(Category::class);
}
Article model:
public function categories()
{
return $this->belongsToMany(Category::class);
}
Then you can call UserDetails::find(1)->articles->get(); directly
You just need to declare the relationship like this in the
UserDetails.php model:
public function categories()
{
return $this->hasMany(Categories::class);
}
in Categories.php model:
public function articles()
{
return $this->hasMany(Articles::class);
}
Then you can retrieve the collection of categories in your controller:
$userdetails = UserDetails::get();
pass that $categories variable into your View and display each record with an foreach loop (where the articles is the function in your model)
#foreach($userdetails->categories as $usercategories )
<div> {{$usercategories->name}} </div>
#foreach($usercategories->articles as $categoryarticles )
<div> {{$categoryarticles->name}} </div>
#endforeach
#endforeach
with the second foreach you will access the articles of the categories that belongs to the user.

Laravel hasManyThrough interrogate the intermediate relationship as well as distant

I want to perform the following (with some rather crude pseudo code):
SELECT a users orderLines WHERE the orderheader's status ='paid' AND the orderLine's productId>5
In other words, a user can place many orders. Each order has one or many order lines. I want to find all of the order lines that the user has placed (order lines, not orders) but only if the order header has a certain status, and only if the order line has another parameter checked. This could be the date the line was added, or the productId being x, and so on.
Simple enough to do with a standard MySql query.
I have the nescessary models:
User
OrderHeader (Intermediate relationship)
OrderLine (Distant relationship - this is what I want to fetch, via the intermediate)
Here are how the relationships are defined in each model:
User
public function orders()
{
return $this->hasMany('App\OrderHeader', 'user_id', 'id');
}
public function lines()
{
return $this->hasManyThrough('\App\OrderLine', 'App\OrderHeader', 'user_id', 'order_header_id');
}
OrderHeader
public function lines()
{
return $this->hasMany('App\OrderLine', 'order_header_id', 'id');
}
public function user(){
return $this->belongsTo('User', 'id', 'user_id');
}
OrderLine (Fetch these for the User, using hasManyThrough)
public function header()
{
return $this->belongsTo('App\OrderHeader', 'order_header_id');
}
public function products()
{
return $this->belongsToMany('App\Product');
}
So, I load the User, using:
$person = User::findOrFail($id)
Then I can use:
$user->lines()->where('product_id','>=',10)->paginate(20);
So, that works brilliantly to get ALL of the lines that the user has placed, which match the condition on the line records. However, I can't figure out how to add a second condition on the intermediate, so that not only do I check the product_id, but also interrogate the OrderHeader entity via the orders() relationship.
I've tried:
return $user->orders()->where('status','=','Paid')->lines()->where('product_id','>=',20))->paginate(20);
but that returns the error: Call to undefined method Illuminate\Database\Query\Builder::lines()
hasManyThrough is a special case in Eloquent, where table is joined (intermediate table), so it's pretty simple - just query that table. Nothing to do with the other relation.
This is what you want:
$throughTable = $user->lines()->getParent()->getTable();
$user->lines()
->where('product_id', '>=', 10)
->where('orderheaders.status', 'paid')
// or
// where("{$throughTable}.status', 'paid')
->paginate(20);
Btw this relation is wrong:
// OrderHeader model
public function user(){
return $this->belongsTo('User', 'id', 'user_id'); // wrong keys order
}
// should be
public function user(){
return $this->belongsTo('User', 'user_id', 'id');
}

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());
}

Categories