I'm upgrading to Laravel 5.2 from 4.2 and running into a weird issues where when I'm using Eager Loading on a relationship, it returns null, but I can call it manually.
Here's my parent model:
namespace App\Models\Hours;
class Hours extends Model {
/**
* Model Setup
*/
protected $table = 'leave_hours';
protected $primaryKey = 'leave_id';
public $timestamps = false;
/**
* Relationships
*/
public function hoursStatus()
{
return $this->belongsTo('App\Models\Hours\HoursStatusType', 'leave_status_code');
}
Here's the HoursStatusType model:
<?php
namespace App\Models\Hours;
use Illuminate\Database\Eloquent\Model;
class HoursStatusType extends Model {
/**
* Model Setup
*/
protected $table = 'leave_status_type';
protected $primaryKey = 'leave_status_code';
public $timestamps = false;
/**
* Relationships
*/
public function hours()
{
return $this->hasMany('App\Models\Hours\Hours');
}
}
Basically Hours has PTO requests that has a status (ie. Pending, Approved, etc). HoursStatusType has only 4 rows and it belongs to many of the Hours request.
I'm doing a big request on the Hours like:
$requests = Hours::with('hoursStatus')->get();
foreach($requests as $r){
print_r($r->hoursStatus);
}
When I try to print this out using a foreach loop, the hoursStatus relationship is blank. HOWEVER, when when I call it without the eager loading, it's fine. The only thing I have changed since upgrading from 4.2 (besides adding namespace) is change the hoursStatus relationship to belongsTo from a hasOne. Another couple of posts mentioned that changing it should fix it. Not so much.
Am I missing something? Thanks!
You should add public $incrementing = false; to your models setup, when the PK is not an autoincrementing int.
Related
I'm currently trying to create a eloquent relationship between two models in my database, one in sql and on in mongodb:
<?php
namespace App\Models\Trend;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Jenssegers\Mongodb\Eloquent\HybridRelations;
class Location extends Model
{
use HasFactory, HybridRelations;
protected $table = 'trends';
protected $fillable = [
'woeid',
'name',
];
/**
* #return \Illuminate\Database\Eloquent\Relations\HasMany|\Jenssegers\Mongodb\Relations\HasMany
*/
public function trends()
{
return $this->hasMany(Trend::class);
}
}
<?php
namespace App\Models\Trend;
use App\Models\Scopes\AscendingOrderScope;
use App\Models\Team;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Jenssegers\Mongodb\Eloquent\Model;
class Trend extends Model
{
use HasFactory;
protected $connection = 'mongodb';
protected $collection = 'trends';
protected $fillable = ['trends'];
/**
* #return void
*/
protected static function booted()
{
static::addGlobalScope(new AscendingOrderScope);
}
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function location()
{
return $this->belongsTo(Location::class);
}
}
Once i try to call the ->location relationship on the Trend model I only get the following error:
PHP Error: Call to a member function prepare() on null in /home/fmk/Code/socmint/vendor/laravel/framework/src/Illuminate/Database/Connection.php on line 413
In the database the location_id field is corretly set with an id of an existing Location model.
Since it is a hybrid relation I also added the HybridRelation trait as described in the packages example (I'm nearly doing the exact stuff as in the example).
The other way around (calling location->trends is working without a problem)...
What am I missing?
Thanks for your help,
FMK
Found the solution after debugging through the callstack.
Laravel seems to does not know which connection to use on the relationship model so it defaults back to the one of the model with the relationship defined.
In my case the trend was using the mongodb connection and laravel tried to search for the location in this connection aswell.
Solution was was to define the connection on the SQL Trend model aswell:
protected $connection = 'mysql';
I'm on Laravel 8 with Livewire, currently have 3 models, Category, SubCategory and MenuItem for 3 tables. All the above models have separate livewire controllers and have the code for the CRUD operations respectively. I have separate views and routes to edit the above tables and they all have a eloquent relationship between each other. Now what I need to do here to is, I need to display all the three tables in a single view to carry out the CRUD operations.
I tried to achieve this by using the sub-view function, to pass the view and make the variables available to the specific view, but it didn't work out and I think it isn't the way to do it, was just trying to figure a workaround. I'm mentioning my models down below for referencing. Please help me with this. Thanks a lot for your time!
App\Models\Category
class Category extends Model
{
use HasFactory;
protected $table = "categories";
protected $fillable = ['sub_category_name'];
public function SubCategories() {
return $this->hasMany(SubCategory::class, 'category_id');
}
public function MenuItems() {
return $this->hasManyThrough(
'MenuItem::class',
'SubCategory::class',
'sub_category_id',
'category_id'
);
}
}
App\Models\SubCategory
class SubCategory extends Model
{
use HasFactory;
protected $table = "sub_categories";
protected $fillable = ['category_id', 'sub_category_name'];
public function Categories() {
return $this->belongsTo(Category::class, 'category_id');
}
public function MenuItems() {
return $this->hasMany(MenuItem::class, 'sub_category_id');
}
}
App\Models\MenuItem
class MenuItem extends Model
{
use HasFactory;
protected $table = "menu_items";
protected $fillable = ['sub_category_id', 'item_name', 'item_description'];
public function SubCategories() {
return $this->belongsTo(SubCategory::class, 'sub_category_id');
}
}
This is what I tried to achieve the said result. As I needed to include the view with the sub category table to the menu items table view. I made the variables available to that specific view.
Resources\Views\Livewire\Menu-Item
<div>
#include('livewire.sub-category')
</div>
App\Providers\AppServiceProvider
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
use App\Models\SubCategory;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
View::composer('livewire.menu-item', function ($view) {
$view::with('sub_category_name', SubCategory::orderBy('sub_category_name')->get());
});
}
}
I managed to solve the above problem, using livewire's component feature. Created 3 separate component for 3 separate tables, and finally used all the three components in the master-menu.blade.php file. Working with livewire and laravel is a treat.
Eager loading not pulling relation model.
Hi, Im using Laravel 6. Our database was created with Zend so our model is a bit strange; I have to set the primary keys.
// Customer model
protected $table = 'customer';
protected $primaryKey = 'Customer_ID';
/**
* Get all appointments for the customer.
*/
public function appointments()
{
return $this->hasMany('App\Appointment');
}
Then for the appointments
protected $table = 'appointment';
protected $primaryKey = 'Appointment_ID';
/**
* Get the customer assigned to this appointment.
*/
public function customer()
{
return $this->belongsTo('App\Customer');
}
Now, in a controller:
$appointments = App\Appointment::with('customer')->take(5)->get();
return response()->json($appointments, 200);
The array has the appointments but customer is null:
{... Customer_ID: 1234, customer: null}
Any ideas? Thanks
When you create the relationship in the model, you can tell laravel which is the field of the foreign key.
If you do not do it, laravel supposes the foreign key is id, which is not your case.
The definition of the relationship should becomes:
public function customer()
{
return $this->belongsTo('App\Customer', 'Customer_ID');
}
For a unit test in laravel 5.1 I am trying to test a cascading delete function of the Client model, which, with the recursive flag set, should also delete all users associated with the client.
I want to use a mock user adn test only wether the delete function on the user is called, so I wont have to use the database, and to apply the same principle to other tests in the future.
at the moment the test fails because I cannot find a way to make the client model retreive the associated user without firing a query.
I think I need to mock the hasMany relation defining function of the client, but i have not found a way.
the client model:
class Client extends Model
{
protected $table = 'clients';
protected $fillable = [];
public function casDelete($recursive = false){
if($recursive) {
$users = $this->users()->get();
foreach($users as $user) {
$user->casDelete($recursive);
}
}
$this->delete();
}
public function users(){
return $this->hasMany('App\User');
}
}
the user model:
class User extends Model implements AuthenticatableContract, CanResetPasswordContract
{
use Authenticatable, CanResetPassword;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['name', 'email', 'password', 'client_id'];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = ['password', 'remember_token'];
public function casDelete($recursive = false){
$this->delete();
}
public function client(){
return $this->belongsTo('App\Client');
}
}
the test:
class ClientModelTest extends TestCase
{
use DatabaseTransactions;
function testCasDelete(){
$client = factory(Client::class)->create();
$user = factory(User::class)->make(['client_id' => $client->id]);
$observer = $this->getMock('user');
$observer->expects($this->once())->method('casDelete');
$client->casDelete(true);
}
}
When you are using DatabaseTransactions, this mean you want to persist the data in the database. And when you are using create() from the factory still you are using the database, so either you should not use the database at all or if you want you to use the database then you can simply solve the problem. but what I can suggest is this solution, which I'm not using the database init.
$user = \Mockery::mock();
$user->shouldReceive('casDelete')->andReturnNull();
$queryMock = \Mockery::mock();
$queryMock->shouldReceive('get')->andReturn([$user]);
$clientMock = \Mockery::mock(Client::class)->makePartial();
$clientMock->shouldReceive('users')->andreturn($queryMock);
$clientMock->casDelete(true);
This way you can be sure that you have called casDelete on each user model.
this is a very simple test case, you can extend it in the way you like base on what you want to achieve.
I'm trying to create an additional attribute method in a model which appends a ratings label column to an existing rating model. I have the exact same code working for another model which adds click data. I'm getting an Undefined index: rating_label error - can't figure out why? Any help much appreciated.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Rating extends Model
{
protected $table = 'rating';
protected $primaryKey = 'rating_id';
protected $appends = array('rating_label');
/**
* Define a 1-many inverse relationship in eloquent
*/
public function Article()
{
return $this->belongsTo(Article::class, 'article_id');// (Model, primary_key)
}
/**
* Add additional column to Rating model for use in ClassifyingService class
*/
public function getRatingLabelAttribute()//accessor getter/setter
{
return $this->attributes['rating_label'];
}
}