Laravel querying polymorphic relationships with nested many to many items - laravel

I have 4 tables with the following relationships:
class Note extends Model
{
public function noteable() {
return $this->morphTo();
}
}
class Expense extends Model
{
public function notes()
{
return $this->morphMany(Note::class, 'noteable');
}
}
class Review extends Model
{
public function notes()
{
return $this->morphMany(Note::class, 'noteable');
}
public function repairs()
{
return $this->belongsToMany(Repair::class);
}
}
class Repair extends Model
{
public function reviews()
{
return $this->belongsToMany(Review::class);
}
}
as you can see Note is in the relationship one to many polymorphic and Reviews is in many to many with Repairs.
I would like to take all Notes that Reviews also has Repairs. How do I do such an operation?
Following the documentation I am trying to do something like this:
$notes = App\Note::query()
->with(['noteable' => function (MorphTo $morphTo) {
$morphTo->morphWith([
Review::class => ['repairs']
]);
}])->get();
I would like to my query should return something like:
[
{
"id": 11,
"noteable_id": 4,
"noteable_type": "App\\Expense",
"noteable": {
"id": 4,
"name": "Expense",
"category": "general"
}
},
{
"id": 13,
"noteable_id": 5,
"noteable_type": "App\\Review",
"noteable": {
"id": 5,
"name": "Review 5",
"mileage": 120000,
"repairs": [..., ...] //what I need
}
}
]

To load your Repair models for your Reviews, try this:
if ($type == 'App\Review') {
$query->with('repairs')
}
EDIT :
I've just done some more reading on whereHasMorph and I think i was mistaken as to what it is doing. It is at it's most basic level a where clause, designed to constrain the results of the query. What I have suggested above would the the equivalent of joining something to a MySQL subquery. Not what we want!
What you want to do is actually under
Nested Eager Loading morphTo Relationships
(https://laravel.com/docs/6.x/eloquent-relationships#querying-polymorphic-relationships)

Related

Getting multiple related models together

In my Laravel app, I have three models, User, Course and CourseScore. I want to get all courses for an specific user with his score. something like:
{
id: 1,
name: first_name,
corses: [
{
id: 1,
name: 'course one',
score: 17, // this is what i need for every course,
},
{
id: 2,
name: 'course two',
score: 19, // this is what i need for every course,
},
]
}
Here are my Models:
User
<?php
class User extends Authenticatable
{
public function courseScores()
{
return $this->hasMany(CourseScore::class);
}
public function courses()
{
return $this->belongsToMany(Course::class);
}
}
Course
<?php
class Course extends Model
{
public function courseScores()
{
return $this->hasMany(CourseScore::class);
}
public function users()
{
return $this->belongsToMany(User::class);
}
}
CourseScore
<?php
class CourseScore extends Model
{
protected $table = 'course_scores';
public function course()
{
return $this->belongsTo(Course::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
}
And of course, please let me know if the relations are incorrect.
If I understand it correctly, you can do something like this,
in your User model
public function courses(){
return $this->hasMany(Course::class); // changed belongsToMany to hasMany
}
in your Course model
public function users(){
return $this->belongsTo(User::class); // changed belongsToMany to belongsTo
}
In your controller
$usersData = User::with('courses.courseScores')->get();
dd($usersData);
I dont fully understand your table structure so Im assuming you have multiple row of Scores for single Course (One to Many).
If the relationship is one is to one then add in your Course Model
public function courseScores(){
return $this->hasOne(CourseScore::class); //changed hasMany to hasOne
}
You can try using this
$val = DB::table('course as c')
->join('user as u', 'u.id','c.id')
-join('courseScore as cs','u.id','cs.id')
->get();
Then just dd($val) to see what value it shown

Get specific field from one to many table relationship with laravel eloquent?

I have table like this following image:
so, user have many balance_transactions and last inserted balance_transactions record will be user account balance. my question, how to make user model have property account_balance with value last inserted total in balance_transactions table
I was tried using something like this in user model
public function balance {
return $this->hasMany(App\BalanceTransaction::class);
}
public function account_balance {
return $this->balance()->orderBy('id', 'DESC')->limit(1);
}
And I get the data like this
$user = User::where('id', 1)->with('account_balance')->first();
return response()->json($user);
and the result look like this folowing json:
{
"id": 1,
"username": "john0131",
"full_name": "john doe",
"email": john#test.com,
"account_balance": [
{
"id": 234,
"user_id": 1,
"total": 7850
"added_value": 50
"created_date": "2020-02-28 12:18:18"
}
]
}
but what I want, the return should be like this following json:
{
"id": 1,
"username": "john0131",
"full_name": "john doe",
"email": "john#test.com",
"account_balance": 7850
}
my question, how to make something like that in laravel eloquent proper way? so I can get account_balance data only with simple code like $user = user::find(1);.
Thanks in advance
One way to tackle this is, you could use laravel Accessors
So in your User model you can create a method as follows
/**
* Get the user's balance.
*
* #return float
*/
public function getAccountBalanceAttribute()
{
return $this->balance->last()->total;
}
then wherever you want to use it you can use it by:
$user->account_balance;
I would suggest loading only one row from your transaction table with an eye on performance. Additionally you can append the value of the accessor to the serialised output of the model (e.g. __toArray()) and return the acutal value only if the relationship has already been loaded.
class User extends Authenticatable
{
protected $appends = ['account_balance'];
public function balance()
{
return $this->hasMany(App\BalanceTransaction::class);
}
public function latestBalance()
{
return $this
->hasOne(App\BalanceTransaction::class)
->latest();
}
public function getAcountBalanceAttribute()
{
if (! $this->relationLoaded('latestBalance')) {
return null;
}
return $this->latestBalance->total;
}
}

Laravel - getting a value from a relationship's attributes as an attribute of the current model

I have 2 models: Car and Condition
Car and Condition have a 1 to 1 polymorphic relationship. Condition gets joined onto Car:
class Car extends Model
{
// ...
public function condition()
{
return $this->morphOne(Condition::class, 'carable');
}
}
class Condition extends Model
{
// ...
public function carable()
{
return $this->morphTo()
}
}
This returns the following structure:
{ // car
"id": 1,
"condition": {
"age": "1 year",
"serviced": "yes"
}
}
I want to also return the age attribute on the car's level, i.e.
{ // car
"id": 1,
"age": "1 year",
"condition": {
"age": "1 year",
"serviced": "yes"
}
}
I want to do this by getting the condition->age attribute inside the car model. I've tried setting up the following in the Car class:
protected $appends = ["age"];
protected function getAgeAttribute()
{
return $this->getFinancialAttribute()->getAgeAttribute();
}
as well as some other variations thereof, with no success. Is there any way for me to do this elegantly?
You can try to add this code to your Car model:
protected $appends = ['age'];
protected function getAgeAttribute()
{
return $this->condition->age;
}
But be sure to eager load condition with your Car model to avoid the N + 1 query problem.
$cars = Car::with('condition')->get();
In your case you can eager load by default adding this line to your Car model:
protected $with = ['condition'];
BTW your relationship on the Car model should be like this:
public function condition()
{
return $this->morphOne(Condition::class, 'carable');
}

Laravel eloquent relationships, 3 way join

I have 3 tables (simplified here)
users
id, name, email
teams
id, name
team_user
team_id, user_id
I want to send an API query to return all teams a user Id belongs to, and what other members are also in that team. Rather than returning just userIds, I want to fill the arrays with their actual user data, e.g name and email.
Route::get('/user/{id}/teams/', 'UserController#getTeams');
User.php (model)
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
public function teams()
{
return $this->belongsToMany('App\Team', 'team_user', 'team_id', 'user_id');
}
}
Team.php (model)
class Team extends Model
{
public function users()
{
return $this->belongsToMany('App\User', 'team_user', 'team_id', 'user_id');
}
}
TeamUser.php (model)
class TeamMember extends Model
{
public function user()
{
return $this->hasOne('App\User');
}
public function team()
{
return $this->hasOne('App\Team');
}
}
UserController.php
class UserController extends Controller
{
public function getTeams($userId) {
return User::find($teamId)->teams()->get();
}
}
This returns:
[
{
"id": 6,
"name": "P12",
"location": "Newbury",
"owner": 6,
"active": 1,
"created_at": "2017-12-20 10:18:00",
"updated_at": "2017-12-20 10:18:00",
"pivot": {
"team_id": 6,
"user_id": 6
}
},
{
"id": 4,
"name": "fwffewfffffff",
"location": "ffffffff",
"owner": 4,
"active": 1,
"created_at": "2017-12-19 19:56:27",
"updated_at": "2017-12-19 19:56:27",
"pivot": {
"team_id": 6,
"user_id": 4
}
}
]
However I would like to also include a list of other users in each of those 2 teams. With their names and emails (from the users table), not just the user_ids.
Is this possible without doing further seperate queries?
You would be able to eagerly load the relations and attach them to the model.
By using the dot notation teams.users, the output will include all users of all teams attached to user with id $userId.
I've added the findOrFail to make sure it will return a 404 when user can not be found.
class UserController extends Controller
{
public function getTeams($userId)
{
return User::with('teams.users')->findOrFail($userId);
}
}
This will return the User and attach the relations.
If you want to just return the teams, you can do something like this:
class UserController extends Controller
{
public function getTeams($userId)
{
$user = User::with('teams.users')->findOrFail($userId);
// $user->teams will hold a collection of teams with their users
return $user->teams;
}
}
Try using with() to retrieve the fields from different tables, for example
class UserController extends Controller {
public function getTeams($userId) {
return User::find($userId)->with('teams')->get();
// return User::find($userId)->teams()->get();
}
}
If you would like to select specific column from the team_members database, you could add function inside with, for example
class UserController extends Controller {
public function getTeams($userId) {
return User::find($userId)->with(['teams' => function($query) {
$query->select('id', 'name');
}])->get();
}
}
For now I have solved this with
class UserController extends Controller
{
public function getTeams($userId) {
$teamWithMembers = [];
$teams = User::find($userId)->teams()->get();
foreach($teams as $team) {
$team->members = Team::find($team->id)->users()->get();
$teamWithMembers[] = $team;
}
return $teamWithMembers;
}
}

Laravel: using "morphMany" and joining another table?

I'm new to laravel too. I have a question about Eloquent:
table structures:
User: id, username
Posts: id, user_id, content
Likes: id, likeable_type, likeable_id, user_id
(Because of I want to make 'Likes' table extendable for comments, too.)
Models\Likes.php
class Likes extends Model
public function likeable()
{
return $this->morphTo();
}
Models\Post.php
class Posts extends Model
public function likes()
{
return $this->morphMany("\App\Models\Likes","likeable");
}
And when I call
$post = Posts::find($id)->likes()->get();
in my controllers, it will return like this:
"likes": [
{
"id": 1,
"likeable_type": "App\\Models\\Posts",
"likeable_id": 1,
"user_id": 1,
},
{
"id": 2,
"likeable_type": "App\\Models\\Posts",
"likeable_id": 1,
"user_id": 1,
}]
But how can I get results like this:
"likes": [
{
"id": 1,
"likeable_type": "App\\Models\\Posts",
"likeable_id": 1,
"user_id": 1,
"username":"chenhui",//join user on user.id = likes.user_id
}...
Many thanks and sorry for my poor english!
I believe in your Likes model you need
class Likes extends Model
public function user()
{
return $this->belongsTo('App\Models\User');
}
and then it should be:
$post = Posts::find($id)->likes()->with('user')->get();

Resources