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.