Eloquent hasMany doesn't load collection - php

I'm relatively new to eloquent, and have problems loading data from a hasMany relation in an app that uses eloquent as the database layer. My (simplified) code is as follows:
use Illuminate\Database\Eloquent\Model;
class Answer extends Model{
protected $table = 'answer';
public $timestamps = false;
public $incrementing = false;
protected $fillable = [
"nerdId",
"invitationid",
"eatingno",
"price",
"haspaid",
"attending",
"noeating",
];
public function topping() {
return $this->hasMany(AnswerTopping::class, 'answerid');
}
}
class AnswerTopping extends Model{
protected $table = 'eating';
public $timestamps = false;
protected $fillable = [
'answerid',
'toppingid',
'add'
];
public function answer() {
return $this->belongsTo(Answer::class);
}
}
The SQL Schema is like below
CREATE TABLE `answer` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`nerdid` int(11) NOT NULL,
`invitationid` int(11) NOT NULL,
`eatingno` tinytext,
`price` float NOT NULL,
`haspaid` tinyint(4) NOT NULL,
`attending` tinyint(1) NOT NULL DEFAULT '0',
`noeating` tinyint(4) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2119 DEFAULT CHARSET=utf8;
CREATE TABLE `eating` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`answerId` int(11) NOT NULL,
`toppingid` int(11) NOT NULL,
`add` tinyint(4) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=280 DEFAULT CHARSET=utf8;
I then do the following query:
$answer = Answer::with('topping')->find(32);
return $answer->toJson());
This results in a json like the following
{"id":32,"nerdid":1,"invitationid":54,"eatingno":"51","price":60,"haspaid":1,"attending":2,"noeating":0,"topping":[]}
Raw SQL query shows me that I do have data in the relation, so it should return more in "topping".
UPDATE
Checking sql queries in mysql (Setting it up for logging), I see that it actually do the expected queries on the database:
select * from `answer` where `answer`.`id` = 32 limit 1;
select * from `eating` where `eating`.`answerid` in (32);
Manually executing the SQL are giving me 2 entries in the eating table. But they are not showing up on the upper "Answer" json.

Found the culprit.. db schema for the "eating" table, had answerId (uppercase I), and the relation in hasMany used answerid (lowercase i), which apparently confused eloquent..
Now I get the expected json..

Related

Laravel eager loading error "Trying to get property of non-object in .../BelongsToMany.php"

Laravel 5.8
I'm having an issue getting eager loading to work on some models but not on others.
Using artisan tinker I can run;
$p = App\Programme::find(34)->reviews
and get the correct result. If I change this to;
$p = App\Programme::with('reviews')->find(34)
So that the reviews are eager loaded, it fails with the error
PHP Notice: Trying to get property of non-object in .../vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php on line 301
output from artisan with query log, bindings and execute time
$p = App\Programme::with('destinations', 'reviews')->find(34)
"select * from `programmes` where `programmes`.`id` = ? and `programmes`.`deleted_at` is null limit 1"
array:1 [
0 => 34
]
1.08
"select `destinations`.*, `programme_destination`.`programme_id` as `pivot_programme_id`, `programme_destination`.`destination_id` as `pivot_destination_id`, `programme_destination`.`created_at` as `pivot_created_at`, `programme_destination`.`updated_at` as `pivot_updated_at` from `destinations` inner join `programme_destination` on `destinations`.`id` = `programme_destination`.`destination_id` where `programme_destination`.`programme_id` in (34)"
[]
0.88
"select `reviews`.*, `programme_reviews`.`programme_id` as `pivot_programme_id`, `programme_reviews`.`review_id` as `pivot_review_id`, `programme_reviews`.`created_at` as `pivot_created_at`, `programme_reviews`.`updated_at` as `pivot_updated_at` from `reviews` inner join `programme_reviews` on `reviews`.`id` = `programme_reviews`.`review_id` where `programme_reviews`.`programme_id` in (34)"
[]
0.85
The final query if run manually works just fine.
I can run the exact same two commands using either the User or Destination models and get a successful response. So there must be something different about the relationship of $programme->reviews when compared to $programme->user or $programme->destinations
Here are my models (trimmed to the relevant functions);
App\BaseModel
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use OwenIt\Auditing\Contracts\Auditable;
use Log;
use DB;
class BaseModel extends Model
{
protected $guarded = ['alias', 'created_at', 'updated_at', 'deleted_at', 'slug'];
public $custom_attributes = [];
public $index_attributes = ['alias', 'user'];
public function alias()
{
return $this->morphOne('App\Alias', 'aliased');
}
public function user()
{
return $this->belongsTo('App\User');
}
}
App\Programme
<?php
namespace App;
use App\BaseModel;
use Illuminate\Database\Eloquent\SoftDeletes;
use OwenIt\Auditing\Contracts\Auditable;
use Log;
class Programme extends BaseModel implements Auditable
{
use SoftDeletes;
use \OwenIt\Auditing\Auditable;
protected $table = 'programmes';
protected $dates = ['deleted_at'];
function __construct(array $attributes = array())
{
parent::__construct($attributes);
}
public function destinations()
{
return $this->belongsToMany('App\Destination', 'programme_destination')
->withTrashed()
->withTimestamps();
}
public function reviews()
{
return $this->belongsToMany('App\Review', 'programme_reviews')
->withTrashed()
->withTimestamps();
}
}
App\Review
<?php
namespace App;
use App\BaseModel;
use Illuminate\Database\Eloquent\SoftDeletes;
use OwenIt\Auditing\Contracts\Auditable;
use Log;
class Review extends BaseModel implements Auditable
{
use SoftDeletes;
use \OwenIt\Auditing\Auditable;
protected $fillable = ['title', 'name', 'first_name', 'last_name', 'review_type', 'email_address', 'created_at'];
function __construct(array $attributes = array())
{
parent::__construct($attributes);
}
public function programmes()
{
return $this->belongsToMany('App\Programme', 'programme_reviews')
->withTrashed()
->withTimestamps();
}
}
I can run $p = App\Programme::with('destinations', 'alias')->find(34) successfully. Here's the model for destinations
App\Destination
<?php
namespace App;
use App\BaseModel;
use Illuminate\Database\Eloquent\SoftDeletes;
use OwenIt\Auditing\Contracts\Auditable;
class Destination extends BaseModel implements Auditable
{
use SoftDeletes;
use \OwenIt\Auditing\Auditable;
protected $table = 'destinations';
protected $dates = ['deleted_at'];
function __construct(array $attributes = array())
{
parent::__construct($attributes);
}
public function programmes()
{
return $this->belongsToMany('App\Programme', 'programme_destination')
->withTrashed()
->withTimestamps();
}
}
It seems the relationship works based on the first artisan command, so why does this not work when eager loading?
For reference here are the database create codes;
programmes
CREATE TABLE `programmes` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(191) NOT NULL COLLATE 'utf8_unicode_ci',
`destination_id` INT(10) UNSIGNED NULL DEFAULT NULL,
`user_id` INT(10) UNSIGNED NOT NULL DEFAULT '1',
`created_at` TIMESTAMP NULL DEFAULT NULL,
`updated_at` TIMESTAMP NULL DEFAULT NULL,
`deleted_at` TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `programmes_user_id_foreign` (`user_id`),
INDEX `programmes_destination_id_foreign` (`destination_id`),
CONSTRAINT `programmes_destination_id_foreign` FOREIGN KEY (`destination_id`) REFERENCES `destinations` (`id`),
CONSTRAINT `programmes_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
;
reviews
CREATE TABLE `reviews` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`title` VARCHAR(191) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
`name` VARCHAR(191) NOT NULL COLLATE 'utf8_unicode_ci',
`first_name` VARCHAR(191) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
`last_name` VARCHAR(191) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
`email_address` VARCHAR(191) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
`created_at` TIMESTAMP NULL DEFAULT NULL,
`updated_at` TIMESTAMP NULL DEFAULT NULL,
`deleted_at` TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (`id`)
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
;
programme_reviews - many to many
CREATE TABLE `programme_reviews` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`programme_id` INT(10) UNSIGNED NOT NULL,
`review_id` INT(10) UNSIGNED NOT NULL,
`created_at` TIMESTAMP NULL DEFAULT NULL,
`updated_at` TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `programme_reviews_review_id_foreign` (`review_id`),
INDEX `programme_id` (`programme_id`),
CONSTRAINT `programme_reviews_programme_id_foreign` FOREIGN KEY (`programme_id`) REFERENCES `programmes` (`id`) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT `programme_reviews_review_id_foreign` FOREIGN KEY (`review_id`) REFERENCES `reviews` (`id`) ON UPDATE CASCADE ON DELETE CASCADE
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
;
destinations
CREATE TABLE `destinations` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(191) NOT NULL COLLATE 'utf8_unicode_ci',
`user_id` INT(10) UNSIGNED NOT NULL DEFAULT '1',
`created_at` TIMESTAMP NULL DEFAULT NULL,
`updated_at` TIMESTAMP NULL DEFAULT NULL,
`deleted_at` TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `destinations_parent_id_index` (`parent_id`),
INDEX `destinations_user_id_foreign` (`user_id`),
CONSTRAINT `destinations_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
;
programme_destinations one to many
CREATE TABLE `programme_destination` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`programme_id` INT(10) UNSIGNED NOT NULL,
`destination_id` INT(10) UNSIGNED NOT NULL,
`created_at` TIMESTAMP NULL DEFAULT NULL,
`updated_at` TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `programme_destination_programme_id_foreign` (`programme_id`),
INDEX `programme_destination_destination_id_foreign` (`destination_id`),
CONSTRAINT `programme_destination_destination_id_foreign` FOREIGN KEY (`destination_id`) REFERENCES `destinations` (`id`) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT `programme_destination_programme_id_foreign` FOREIGN KEY (`programme_id`) REFERENCES `programmes` (`id`) ON UPDATE CASCADE ON DELETE CASCADE
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
;
The only real difference between $programme->destinations and $programme->reviews is that reviews is a many to many relationship.
So I've found the answer to this niggling query thanks to outside help.
My question as is would not have been answerable, as the problem actually existed with a $pivot class variable I had assigned on the Programme model (and removed for the post to attempt to shorten it).
This variable clashed with Laravel's internal workings, which seems quite obvious now I know about it.
Changing $pivot to anything else allowed the relationship to be eager loaded correctly.
It's unlikely someone will have the same issue as me, but who knows, it may help someone avoid the error I encountered.
Just adding another answer in case it helps someone searching for this problem.
I just spent an hour trying to work out why I was getting "Trying to get property of non-object" when eager-loading a many-to-many relationship with
Document::where('id',$id)->with('authors')->first();
I tracked the problem down to the Document model which contained a pivot table alias and a specified pivot field:
public function authors() {
return $this->belongsToMany('App\User')->withPivot('role')->as('role');
}
When I swapped the order of the methods and renamed the alias, it worked.
public function authors() {
return $this->belongsToMany('App\User')->as('author_role')->withPivot('role');
}
The "Trying to get property of non-object" error was completely unhelpful in debugging this error. But making the change above was enough to resolve it.

How to set a mysql bit datype in Laravel's Eloquent? (Laravel 5.4)

I have a single action to deactivate a user's resource. The action uses a bit value to activate/deactivate the resource but I was surprised that I couldn't set the bit value as I expected. The value is always set to "1", the default value when the resource is created.
public function deactivate(Request $request)
{
$resourceId= $request->get('resourceId');
$resource= \App\Resource::find($resourceId);
//I've tried the two statements below separetely
$resource->active= false; //didn't work
$resource->active= 0; //didn't work
//I performed the test below with another column and it worked
//so the problem isn't my model.
$resource->anotherProperty = 10;
$resource->save();
}
even though I don't think the problem is my model, I'm using the database first approach and it is possible that the problem is the model I created.
class Resource extends Model
{
protected $table = 'resource';
protected $primaryKey = 'resource_id';
protected $fillable = array( 'announcer_id', 'announcer_type', 'data', 'active');
public $timestamps = false;
protected $connection = 'custom_connection';
}
updated some minutes after
CREATE TABLE `resource` (
`resource_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`announcer_id` int(10) unsigned NOT NULL,
`announcer_type` tinyint(3) unsigned NOT NULL,
`data` date DEFAULT NULL,
`active` bit(1) DEFAULT b'1',
PRIMARY KEY (`resource_id`)
) ENGINE=InnoDB AUTO_INCREMENT=583 DEFAULT CHARSET=latin1;
After research I found that MySQL has a boolean data type which is stored as tinyint(1).
I've altered my table
alter table resource modify column active boolean not null;
now on my controller
$resource->active= false; //works

Get Filtered and associated records in Laravel 5.1

I have three Database Tables.
CREATE TABLE `tblprojecttype` (
`ProjectTypeID` int(11) NOT NULL,
`ProjectType` varchar(30) NOT NULL,
`Description` varchar(500) NOT NULL,
`IsActive` tinyint(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `tblprojecttypecurrencyprice` (
`ProjectTypeCurrencyID` int(11) NOT NULL,
`CurrencyID` int(11) NOT NULL,
`ProjectTypeID` int(11) NOT NULL,
`Price` decimal(10,0) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `tblcurrency` (
`CurrencyID` int(11) NOT NULL,
`Currency` varchar(100) NOT NULL,
`IsActive` tinyint(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Models
class Currency_Model extends Model
{
protected $table = "tblcurrency";
protected $primaryKey = "CurrencyID";
public $timestamps = false;
}
class ProjectType_Model extends Model
{
protected $table = "tblprojecttype";
protected $primaryKey = "ProjectTypeID";
public $timestamps = false;
public function projecttypecurrencyprice()
{
return $this->hasOne('\App\Models\ProjectTypeCurrencyPrice_Model',
"ProjectTypeID");
}
}
class ProjectTypeCurrencyPrice_Model extends Model
{
protected $table = "tblprojecttypecurrencyprice";
protected $primaryKey = "ProjectTypeCurrencyID";
public $timestamps = false;
public function Currency()
{
return $this->belongsTo('\App\Models\Currency_Model', "CurrencyID");
}
}
There is CurrencyID relationship and ProjectTypeID relationship
What I am trying ?
In my Laravel 5.1 code, I am trying to achive below sql statement so that I can get projecttypecurrencyprice records for each ProjectType record. Finally it should also show records from currency Table for each projecttypecurrencyprice record
$ProjectTypes = \App\Models\project\ProjectType\ProjectType_Model
::with("projecttypecurrencyprice")
->with("projecttypecurrencyprice.Currency")
->get();
What's the Problem ?
I am getting all Currency records for each projecttypecurrencyprice record. I just want those where currency ID = 1
You need to apply additional criteria when fetching related ** projecttypecurrencyprice** objects. You can do that with whereHas() method. The following code should do the trick:
$ProjectTypes = \App\Models\project\ProjectType\ProjectType_Model
::with("projecttypecurrencyprice")
->with(["projecttypecurrencyprice.Currency" => function($query) {
$query->where('CurrencyID', 1);
}])
->get();
If I am not missing something, shouldn't a simple where() clause in your eloquent query builder do the trick?
Like this:
$ProjectTypes = \App\Models\project\ProjectType\ProjectType_Model
::with("projecttypecurrencyprice")
->with("projecttypecurrencyprice.Currency")
->where('id', 1)
->get();
(not tested of course, please don't slap me)

Get nested associated records in Laravel 5.1

I have three Database Tables.
CREATE TABLE `tblprojecttype` (
`ProjectTypeID` int(11) NOT NULL,
`ProjectType` varchar(30) NOT NULL,
`Description` varchar(500) NOT NULL,
`IsActive` tinyint(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `tblprojecttypecurrencyprice` (
`ProjectTypeCurrencyID` int(11) NOT NULL,
`CurrencyID` int(11) NOT NULL,
`ProjectTypeID` int(11) NOT NULL,
`Price` decimal(10,0) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `tblcurrency` (
`CurrencyID` int(11) NOT NULL,
`Currency` varchar(100) NOT NULL,
`IsActive` tinyint(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Models
class Currency_Model extends Model
{
protected $table = "tblcurrency";
protected $primaryKey = "CurrencyID";
public $timestamps = false;
}
class ProjectType_Model extends Model
{
protected $table = "tblprojecttype";
protected $primaryKey = "ProjectTypeID";
public $timestamps = false;
public function projecttypecurrencyprice()
{
return $this->hasOne('\App\Models\ProjectTypeCurrencyPrice_Model',
"ProjectTypeID");
}
}
class ProjectTypeCurrencyPrice_Model extends Model
{
protected $table = "tblprojecttypecurrencyprice";
protected $primaryKey = "ProjectTypeCurrencyID";
public $timestamps = false;
public function Currency()
{
return $this->hasOne('\App\Models\Currency_Model', "CurrencyID");
}
}
There is CurrencyID relationship and ProjectTypeID relationship
What I am trying ?
In my Laravel 5.1 code, I am trying to achive below sql statement so that I can get projecttypecurrencyprice records for each ProjectType record. Finally it should also show records from currency Table for each projecttypecurrencyprice record
$ProjectTypes = \App\Models\project\ProjectType\ProjectType_Model
::with("projecttypecurrencyprice")
->with("projecttypecurrencyprice.Currency")
->get();
What's the Problem ?
I am not able to get Currency records for each projecttypecurrencyprice record.
For ProjectTypeCurrencyPrice_Model you should change Currency relationship this way:
public function Currency()
{
return $this->belongsTo('\App\Models\Currency_Model', "CurrencyID");
}
because you have CurrencyId column in tblprojecttypecurrencyprice and you don't have any connection to ProjectTypeCurrencyPrice_Model in tblcurrency.
And to get data instead of:
$ProjectTypes = \App\Models\project\ProjectType\ProjectType_Model
::with("projecttypecurrencyprice")
->with("projecttypecurrencyprice.Currency")
->get();
you can omit 1st with:
$ProjectTypes = \App\Models\project\ProjectType\ProjectType_Model
::with("projecttypecurrencyprice.Currency")
->get();
By the way if you start this project, you should consider changes in your database structure and read about PSR in PHP - you should rather not use ProjectTypeCurrencyPrice_Model as your model name if you really don't have to, same for ProjectTypeID columns in MySQL

Laravel: How to get associated table's columns in eager loading

I have three Database Tables.
CREATE TABLE `tblproject` (
`ProjectID` int(11) NOT NULL,
`ProjectStatusID` varchar(30) NOT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `tblSkills` (
`SkillID` int(11) NOT NULL,
`Skill` varchar(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `tblprojectSkills` (
`ProjectSkillID` int(11) NOT NULL,
`ProjectID` int NOT NULL,
`SkillID` int NOT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
In the above tables. SkillID is related in tblSkills and tblprojectSkills.
ProjectID is related in Project and projectSkills Table
My project Model is below.
class Project_Model extends Model
{
protected $table = "tblproject";
protected $primaryKey = "ProjectID";
public $timestamps = false;
public function ProjectSkills() {
return $this->hasMany('\App\Models\ProjectSkill_Model', 'ProjectID');
}
}
Database Query in laravel 5.1 is below.
\App\Models\Project\Project_Model
::with('ProjectSkills')
->where('ProjectID', '=', $ProjectID)->first();
Question
I can get the Skill ID, But, How can I get the Skill Name from Skill Table ?
You can select the fields you want with a closure:
\App\Models\Project\Project_Model
::with('ProjectSkills' => function($q)
{
$q->select('SkillID', 'Skill');
})
->where('ProjectID', '=', $ProjectID)->first();
Alternatively, you could add the fields you want directly in the relation of your model:
public function ProjectSkills() {
return $this->hasMany('\App\Models\ProjectSkill_Model', 'ProjectID')
->select('SkillID', 'Skill');
}

Categories