Laravel Eloquent one to many relationship not working - php

I'm obviously missing something. I thought I was comfortable around laravel relationships...
I've 2 tables, named ratings and ratingdetails. The models are named Rating & Ratingdetail:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Rating extends Model
{
public function ratingdetails()
{
return $this->hasMany('App\Ratingdetail');
}
public function campaigns()
{
return $this->hasMany('App\Campaign');
}
}
and
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Spatie\Translatable\HasTranslations;
class Ratingdetail extends Model
{
use HasTranslations;
public $translatable = ['value'];
public function rating()
{
return $this->belongsTo('App\Rating');
}
}
When I try to access to my Rating model it works fine, but I can't access the relationships; the output is the following, despite there should be 4 Ratingdetails rows...:
{"id":1,"description":"fontawesome","created_at":null,"updated_at":null,"deleted_at":null}
Thank you all for your time !

$rating = Rating::find($request->rating_id);
return $rating->toJson();
In the above line of code, you're never accessing the ratingdetails relationship. They are not included by default, and need to be loaded before being available:
$rating = Rating::with(["ratingdetails"])->find($request->rating_id);
return $rating->toJson();
Including it via with() will "Eager load" the relationship and expose it to be accessed via
console.log(rating.ratingdetails);
// Will contain an array of 4 objects
Before converting to json, you'd be able to access $rating->ratingdetails, but once converted, you lose access unless you have previously loaded the relationship.

Actually I can't answer for this question without having the Models' $fillable attributes, or without DB Tables structures. But I think your tables have following columns:
"raitings" -> "id", "description", "created_at", "updated_at", "deleted_at"
"raitingdetails" -> "id", "raiting_id", "value", ...
In normal way, you need to create OneToMany relation for that 2 tables with foreign key. So in your "raitingdetails" migration you need to have something like this:
$table->unsignedBigInteger('raiting_id')->nullable();
$table->foreign('raiting_id')->references('id')->on('raitings')->onUpdate('cascade')->onDelete('cascade');
Your models are correct, but it not just cool now.. You can improve them by adding $fillable columns and FKs of relations (Note: if you're using traditional foreign key concept, like "partents.id"->"childs.partent_id", then you can leave this part too).
For getting all Rating details of 1 Rating, you can do this:
$rating = Rating::find($rating_id);
$rating_details_of_one = $rating->ratingdetails()->get()->toJson();
If you want to have Rating Details for all actions, you can add Accessor in your Rating model and attach that to $appends like this:
protected $appends = [ 'rating_details' ]; public function
public function getRatingDetailsAttribute() {
return $this->ratingdetails;
}
And in logic parts you can access like this:
$ratings = Rating::find($rating_id); // this will get with their "ratingdetails" relation
Or you can attach accessor on the fly without protected $appends and getRatingDetailsAttribute() function like this:
$rating = Rating::find($rating_id);
$rating_details_of_one = $rating->setAppends([ 'rating_details' ])->get()->toJSON();
If you want to have some Ratings with their details, you can use something like this:
$rating_details_of_many = Rating::where('description', 'fontawesome')->with('ratingdetails')->get()->toJson();

Related

Return related eloquent models altogether in json format

I'm learning Eloquent Model Relationships in Laravel. In my demo project I have two tables Sets and Cards, they have One to many relationship. One Set has many Cards and a Card belongs to a Set.
I want the output to hold both Sets and Cards together in nested JSON array, Cards under Sets. I have set the relationships in both models.
My expected result
[
{
"set_id":1,
"name":"English",
"description":"English Language",
"created_at":"2020-03-31T15:41:55.000000Z",
"updated_at":"2020-03-31T15:41:55.000000Z"},
"cards:
{
"card_id":1,
"name":"Greetings",
"description":"Greetings related questions",
"set_id":1,
"created_at":"2020-03-31T15:45:03.000000Z",
"updated_at":"2020-03-31T15:45:03.000000Z"
}
}
]
Set.php
<?php
namespace App;
use App\Card;
use Illuminate\Database\Eloquent\Model;
class Set extends Model
{
protected $table = "sets";
public function cards() {
return $this->hasMany('App\Card');
}
}
Card.php
<?php
namespace App;
use App\Set;
use Illuminate\Database\Eloquent\Model;
class Card extends Model
{
protected $table = "cards";
public function sets() {
return $this->belongsTo('App\Set');
}
}
In my Controller I have tried to get my result by this line return Set::all()->cards;, but it says Property [cards] does not exist on this collection instance. My migration files have foreign key set in Cards table, so I think the relationship between tables is made correctly or maybe I'm wrong.
I have read the detailed Laravel Doc for Eloquent Relationships, but did not find any query that returns related models together in a nested JSON, the way I'm expecting.

Eloquent eager loading does not return related model data

In my Laravel project I have two Eloquent Models Set and Card, they have one to many relationship between them. Set has many Cards and Cards belong to Set.
I'm trying to pull the data using Eloquent Eager Loading using the with() function. But for some reason the cards array inside sets is returning as blank [].
Set.php
<?php
namespace App;
use App\Card;
use Illuminate\Database\Eloquent\Model;
class Set extends Model
{
protected $table = "sets";
public function cards() {
return $this->hasMany('App\Card');
}
}
Card.php
<?php
namespace App;
use App\Set;
use Illuminate\Database\Eloquent\Model;
class Card extends Model
{
protected $table = "cards";
public function sets() {
return $this->belongsTo('App\Set');
}
}
Controller code
$sets = Set::with([
'cards' => function($query){
$query->select('name', 'description');
},
])->get();
return $sets;
name and description are the fields that I have in Cards table except of the primary key and foreign key.
It returns with "cards": [] in my JSON array of sets.
I have tried using below code in controller,
$sets = Set::with('cards.*')->get();
return $sets;
Did not work.
Tried adding the foreign key (set_id which is in the Cards table) in the hasMany method in my Set model that creates relationship with Cards table. But of no result.
To be mentioned, when I tried pulling the data from Cards table separately from my CardController, it returned all Cards data.
Some clue would mean great help. Thank you for your time.
Try this code in your controller,
$sets = Set::with('cards')->get();
return $sets;
Let me know if it works
Try something like this:
public function sets() {
return $this->belongsTo('App\Set','id','id_set');
}
the second parameter "id" is the id of table Set, and the third parameter "id_set" is the foreing key in the Card Table.

How to Inverse the Eloquent Has One and Has Many Through (laravel 5.8)?

I have three relational table attached below.
https://drive.google.com/file/d/1q1kdURIwFXxHb2MgdRyBkE1e3DMug7r-/view?usp=sharing
I have also three separate models where defined relation among all of my table's.I can read the City Model's information from Country model using hasManyThrough() relation But cannot read the Country information from City model. I have tried to retrieve City model's using ``hasManyThrough``` but didn't get result (attached as commented country method ). Please read my model and it's relational method here..
Is there someone to help me for getting City model's information using Eloquent method hasManyThrough / hasManyThrough or using inverse of hasManyThrough / hasManyThrough ?
01.
<?php
namespace App\Hrm;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Country extends Model
{
//use SoftDeletes;
protected $fillable = ['name','description','status'];
public function districts(){
return $this->hasMany(District::class);
}
public function cities(){
return $this->hasManyThrough(City::class,District::class);
}
}
02.
<?php
namespace App\Hrm;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class District extends Model
{
//use SoftDeletes;
protected $fillable = ['country_id','name','description','status'];
public function country(){
return $this->belongsTo(Country::class);
}
public function cities(){
return $this->hasMany(City::class);
}
}
3.
namespace App\Hrm;
use App\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class City extends Model
{
//use SoftDeletes;
protected $fillable = ['district_id','name','description','status'];
public function district(){
return $this->belongsTo(District::class);
}
// public function country(){
// return $this->hasOneThrough(Country::class, District::class);
// }
Doesn't look like there is a native way to define the inverse of a "hasManyThrough" relationship yet in Laravel. There have been a few issues opened on github to request it, but they were closed.
You could use the staudenmeir/belongs-to-through package if you don't mind installing a third-party package for this functionality. Then you should be able to define a belongsToThrough relationship like this:
class City extends Model
{
use \Znck\Eloquent\Traits\BelongsToThrough;
public function country() {
return $this->belongsToThrough(Country::class, District::class);
}
}
Why can't use parent method?
$city = City::find(1);
$country = $city->district->country();
i just had a similar situation i was able to accomplish a belongsToThrough with hasOneThrough
public function country()
{
return $this->hasOneThrough(
Country::class, // model we are trying to get
District::class, // model we have an _id to
'id', // WHERE `district`.`id` = `city`.`district_id`
'id', // `countries`.`id`
'district_id', // local column relation to our through class
'country_id' // `district`.`country_id`
);
}
what this should generate is
SELECT * FROM `countries`
INNER JOIN `districts`
ON `districts`.`country_id` = `countries`.`id`
WHERE `districts`.`id` = ?
-- ? == city.district_id
Database structure:
City:
id: increments
district_id: integer
...
Country:
id: increments
...
District:
id: increments
country_id: integer
...
we can then do $city->country
note: i have not fully tested this but with the testing that i have done it 'works'
Edit: i originally thought that i needed to leave the localKey
parameter null otherwise the relation wont work. it turns out i didnt
fully understand what that column was doing and that was wrong. That
key is the local column that relates to our through column (unless i
still have more to learn/figure out), when left the value as null, it
would use the local id column which a. is the wrong value, b. can also
be out of range (which is how i discovered it was using the wrong
value)
in my testing i only had two rows, both with the same relations. what
i didnt realize though was that on the "through table" both row 1 and
2 and the same related (relation where are trying to reach) so i didnt
notice the issue right away. hopefully now its all working

Laravel 5 Eloquent relationship: can't modify/overwrite relationship table property

I am using Laravel 5's belongsToMany method to define related tables using an intermediary pivot table. My application is using the eloquent models Tour and TourCategory. In the Tour Model I have:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Tour extends Model
{
public function cats(){
return $this->belongsToMany('App\TourCategory', 'tour_cat_assignments', 'tour_id', 'cat_id');
}
}
In my controller I am retrieving all the data from the tour table along with the associated category data using Laravel's with method:
$tours = Tour::with('cats')->get();
That all works fine. The problem is that I don't want the category data in its current raw form, I need to first rearrange it. However I cannot overwrite the cats property without unsetting it first:
public function serveTourData(){
$tours = Tour::with('sections', 'cats')->get();
foreach($tours as $tour){
unset($tour->cats); // If I unset first, then it respects the new value. Why do I need to do this?
$tour->cats = "SOME NEW VALUE";
}
Log::info($tours);
}
Can someone explain the logic behind this please?
To override relations on some model, you can use:
public function serveTourData(){
$tours = Tour::with('sections', 'cats')->get();
foreach($tours as $tour){
$tour->setRelation('cats', "SOME NEW VALUE");
}
Log::info($tours);
}
For laravel 5.4 - setRelation
Of course if you are using laravel >= 5.6, you can unset relations by unsetRelation

getting the value of an extra pivot table column laravel

I have a phone_models, phone_problems, and a phone_model_phone_problem pivot table. The pivot table has an extra column 'price'.
PhoneModel:
class PhoneModel extends \Eloquent
{
public function problems()
{
return $this->belongsToMany('RL\Phones\Entities\PhoneProblem')->withPivot('price');
}
}
PhoneProblem:
class PhoneProblem extends \Eloquent
{
public function models()
{
return $this->belongsToMany('PhoneModel')->withPivot('price');
}
}
What I'm trying to do is get the price of a specific phone with a specific problem.
This is how I have it now but I feel like Laravel has a built in Eloquent feature I can't find to do this in a much simpler way:
$model = $this->phoneService->getModelFromSlug($model_slug);
$problem = $this->phoneService->getProblemFromSlug($problem_slug);
all this does is select the specific model and problem from their slug.
then what I do is with those credentials I get the price like so:
$row = DB::table('phone_model_phone_problem')
->where('phone_model_id', '=', $model->id)
->where('phone_problem', '=', $problem->id)
->first();
so now I can get the price like so $row->price but I feel like there needs to be a much easier and more 'Laravel' way to do this.
When using Many to Many relationships with Eloquent, the resulting model automatically gets a pivot attribute assigned. Through that attribute you're able to access pivot table columns.
Although by default there are only the keys in the pivot object. To get your columns in there too, you need to specify them when defining the relationship:
return $this->belongsToMany('Role')->withPivot('foo', 'bar');
Official Docs
If you need more help the task of configuring the relationships with Eloquent, let me know.
Edit
To query the price do this
$model->problems()->where('phone_problem', $problem->id)->first()->pivot->price
To get data from pivot table:
$price = $model->problems()->findOrFail($problem->id, ['phone_problem'])->pivot->price;
Or if you have many records with different price:
$price = $model->problems()->where('phone_problem', $problem->id)->firstOrFail()->pivot->price;
In addition.
To update data in the pivot you can go NEW WAY:
$model->problems()->sync([$problemId => [ 'price' => $newPrice] ], false);
Where the 2nd param is set to false meaning that you don't detach all the other related models.
Or, go old way
$model->problems()->updateExistingPivot($problemId, ['price' => $newPrice]);
And remind you:
To delete:
$model->problems()->detach($problemId);
To create new:
$model->problems()->attach($problemId, ['price' => 22]);
It has been tested and proved working in Laravel 5.1 Read more.
Laravel 5.8~
If you want to make a custom pivot model, you can do this:
Account.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Account extends Model
{
public function users()
{
return $this->belongsToMany(User::class)
->using(AccountUserPivot::class)
->withPivot(
'status',
'status_updated_at',
'status_updated_by',
'role'
);
}
}
AccountUserPivot.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Relations\Pivot;
class AccountUserPivot extends Pivot
{
protected $appends = [
'status_updated_by_nice',
];
public function getStatusUpdatedByNiceAttribute()
{
$user = User::find($this->status_updated_by);
if (!$user) return 'n/a';
return $user->name;
}
}
In the above example, Account is your normal model, and you have $account->users which has the account_user join table with standard columns account_id and user_id.
If you make a custom pivot model, you can add attributes and mutators onto the relationship's columns. In the above example, once you make the AccountUserPivot model, you instruct your Account model to use it via ->using(AccountUserPivot::class).
Then you can access everything shown in the other answers here, but you can also access the example attribute via $account->user[0]->pivot->status_updated_by_nice (assuming that status_updated_by is a foreign key to an ID in the users table).
For more docs, see https://laravel.com/docs/5.8/eloquent-relationships (and I recommend press CTRL+F and search for "pivot")

Categories