Issues with Laravel hasManyThrough relationship - laravel

I'm having issues using the hasManythrough relationship in larval. Just following the documentation using the example there, which are:
countries
id - integer
name - string
users
id - integer
country_id - integer
name - string
posts
id - integer
user_id - integer
title - string
Here is how I set up the relationship in the models
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Country extends Model
{
public function posts() {
return $this->hasManyThrough('App\Post', 'App\User', 'user_id', 'country_id', 'id');
}
}
Here is the User model
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
public function posts() {
return $this->hasMany('App\Post');
}
public function country() {
return $this->hasOne('App\User');
}
}
Here is the Posts model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
public function user() {
return $this->belongsTo('App\User');
}
}
So, the website doesn't go through enough detail on how to extract the posts through the country model. Using the routes file, this is the query I used
Route::get('posts/countries/{id}', function($id) {
$countries = App\Country::where('id', $id)->get();
return $countries->posts;
});
It looks to me like I set up the relationship up correctly the way the docs say to. There is a country_id on the users table, so I'm not sure if the query is wrong or maybe I did set up the relationship incorrectly.

You aren't actually requesting the relationship, you are simply looking at the attribute on countries.
If you want to eagerload the posts in the query builder you will need to add with('posts') when you build the query. (Before you call ->get() which executes the query and turns it into a collection.)
Route::get('posts/countries/{id}', function($id) {
$country = App\Country::with('posts')->where('id', $id)->first();
return $country->posts;
});
Or if you want to lazyload you can ask for the relationship on the country model by doing ->posts() like this:
Route::get('posts/countries/{id}', function($id) {
$country = App\Country::with('posts')->where('id', $id)->first();
return $country->posts();
});
Notice: in both cases I changed the ->get() to ->first(). I assume you only want one country's posts returned.
->get() executes the query and returns the related models as a collection and ->first() takes the first model from the query.

#Nicklas Kevin Frank
Your solution didn't work for me. at least not completely, but you were right in some respects. I tinkered around, and discovered that the query worked like this better:
Route::get('posts/countries/{id}', function($id) {
$country = App\Country::where('id', $id)->first();
return view('country')->with('country', $country);
});
So, like you said, it diffidently needed the ->first() option, but it didn't need the with('posts') portion. But much thanks my friend. I couldn't have solved this without you.

Related

How to update one to many polymorphic relationship?

How to update multiple records in One to many polymorphic relationship?
I want to update the fields as a group, but how?
Skill Model:
class Skill extends Model
{
use HasFactory;
protected $fillable = ['title', 'percentage'];
/**
* Get the owning skillable model.
*/
public function skillable(): MorphTo
{
return $this->morphTo();
}
}
User Model:
class User extends Model
{
use HasFactory;
/**
* Get all of the skill's user.
* #return MorphMany
*/
public function skills(): MorphMany
{
return $this->morphMany(Skill::class, 'skillable');
}
}
There are a number of ways to do so:
$user->skills->each(function ($skill) {
$skill->update([...]);
});
$user->skills->each(fn($skill) => $skill->update([...]));
$user->skills->each->update([...]);
$user->skills()->update([...]);
I recommend the first three approaches. Because if there are any model events, those will be fired. Model events won't be fired in the fourth one.
Specifically to your problem, you might want to do something like this in the controller:
public function update()
{
$skills = collect(request('skill_titles'))
->zip(request('skill_percentages'))
->map(function ($pair) {
return [
'title' => $pair[0],
'percentage' => $pair[1],
]
});
$skills->each(function ($skill) use ($user) {
$user->skills()->where('title', $skill['title'])
->update($skill['percentage']);
});
}
you can use update method
// make sure you have the desired attributes in fillable array property in your model class
$model->related_model->update([inputs]);
if you write the relationship methods "related_method" in model class correctly you can use them as properties of your model and access their attributes or update them.

Get data from different tables using inner joins

I have column in results as 'user_id','test','subject' and in datatable i want to get the 'test_name' which is saved in 'tests' table, Student name 'name' saved in 'users' and 'subject name' in table 'subjects' in column 'subjects' now tell me what is the best way to get this data.
So far i had tried this but getting null while dumping.
$result = DB::table('results')
->where([
['results.subject',$request->subject],
['test',$request->test],
['user_id',$request->name]
])
->join('users','results.user_id','=','users.name')
->join('tests','tests.id','=','results.test')
->join('subjects','subjects.id','=','results.subject')
->select('results.*','users.name','tests.test_name','subjects.subjects As s_subject')
->first();
dd($result);
anyone who can guide me the best possible solution.
You should use the power of the Eloquent model of Laravel. You have to create Results model class as follow(and obviously need to create User, Test and Subject model class)
Results.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Carbon\Carbon;
class Results extends Model
{
/**
* The attributes that aren't mass assignable.
*
* #var array
*/
protected $guarded = ['id'];
/**
* Get the user profile associated with the results.
*/
public function user()
{
return $this->hasOne('App\User', 'user_id');
}
/**
* Get the test details associated with the results.
*/
public function test()
{
return $this->hasOne('App\Test', 'test');
}
/**
* Get the subject associated with the results.
*/
public function subject()
{
return $this->hasOne('App\Subject', 'subject');
}
}
Controller.php
$results = App\Results::where(['subject' => $request->subject, 'test' => $request->test, 'user_id' => $request->name])->first();
Now you will get the Result object with the test, subject, and test property.
Note that:- You should provide the output when you are asking any question.

Laravel - one-to-one relation through pivot table with eager load

I have this relationship
A Movement can have multiples steps
A Step can belongs to multiples Movements
So a had to create a pivot table and a belongsToMany relationship, but my pivot table have some extras columns, like finished and order
I want to have two relationships, one to get all steps from a movement and another one to get the current step from the movement (the last finished step)
I know how to get all steps
public function steps()
{
return $this->belongsToMany(MovementStep::class, 'movement_movement_steps')
->withPivot('order', 'finished')
->orderBy('pivot_order');
}
But how about the current step? I need this kind of relationship, but returning only one record and be able to eager load it cause I'm passing it to vue.js
public function current_step()
{
return $this->belongsToMany(MovementStep::class, 'movement_movement_steps')
->withPivot('order', 'finished')
->where('finished', true)
->orderBy('pivot_order', 'desc');
}
Notice, I'd like to do that without extras packages
alternative solution, but with extra package: Laravel hasOne through a pivot table (not the answer marked as correct, the answer from #cbaconnier)
A different approach from the answer provided by #mrhn is to create a custom relationship. Brent from Spatie did an excellent article about it
Although my answer will do the exact same queries than the one provided by staudenmeir's package it makes me realized that either you use the package, this answer or #mrhn answer, you may avoid the n+1 queries but you may still ends up will a large amount of hydrated models.
In this scenario, I don't think it's possible to avoid one or the other approach. The cache could be an answer though.
Since I'm not entirely sure about your schema, I will provide my solution using the users-photos example from my previous answer.
User.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public function photos()
{
return $this->belongsToMany(Photo::class);
}
public function latestPhoto()
{
return new \App\Relations\LatestPhotoRelation($this);
}
}
LastestPhotoRelation.php
<?php
namespace App\Relations;
use App\Models\User;
use App\Models\Photo;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\Relation;
class LatestPhotoRelation extends Relation
{
/** #var Photo|Builder */
protected $query;
/** #var User */
protected $user;
public function __construct(User $user)
{
parent::__construct(Photo::query(), $user);
}
/**
* #inheritDoc
*/
public function addConstraints()
{
$this->query
->join(
'user_photo',
'user_photo.photo_id',
'=',
'photos.id'
)->latest();
// if you have an ambiguous column name error you can use
// `->latest('movement_movement_steps.created_at');`
}
/**
* #inheritDoc
*/
public function addEagerConstraints(array $users)
{
$this->query
->whereIn(
'user_photo.user_id',
collect($users)->pluck('id')
);
}
/**
* #inheritDoc
*/
public function initRelation(array $users, $relation)
{
foreach ($users as $user) {
$user->setRelation(
$relation,
null
);
}
return $users;
}
/**
* #inheritDoc
*/
public function match(array $users, Collection $photos, $relation)
{
if ($photos->isEmpty()) {
return $users;
}
foreach ($users as $user) {
$user->setRelation(
$relation,
$photos->filter(function (Photo $photo) use ($user) {
return $photo->user_id === $user->id; // `user_id` came with the `join` on `user_photo`
})->first() // Photos are already DESC ordered from the query
);
}
return $users;
}
/**
* #inheritDoc
*/
public function getResults()
{
return $this->query->get();
}
}
Usage
$users = \App\Models\User::with('latestPhoto')->limit(5)->get();
The main difference from Brent's article, is that instead of using a Collection we are returning the latest Photo Model.
Laravel has a way to create getters and setters that act similar to columns in the database. These can perfectly solve your problem and you can append them to your serialization.
So instead your current_step is gonna be an accessor (getter). The syntax is getCurrentStepAttribute() for the function which will make it accessible on the current_step property. To avoid N + 1, eager load the steps when you retrieve the model(s) with the with('steps') method. Which is better than running it as a query, as it will execute N times always.
public function getCurrentStepAttribute() {
return $this->steps
->where('finished', true)
->sortByDesc('pivot_order')
->first();
}
Now you can use the append property on the Movement.php class, to include your Eloquent accessor.
protected $appends = ['current_step'];

Laravel Eloquent How Can I Select Using Condition "where" for pivot table

I have three database tables called user(id,name), group(id,name) and user_group(user_id, group_id,valid_before) with relations many to many.
class User extends Model
{
protected $table = 'user';
public function groups()
{
return $this->belongsToMany(Group::class, 'user_group')
->withPivot('valid_before');
}
}
class Group extends Model
{
protected $table = 'group';
public $timestamps = false;
public function user()
{
return $this->belongsToMany(User::class, 'user_group');
}
}
How can I select all users (using Eloquent) who have
valid_before < $some_date
?
There are many ways to achieve this goal. I'll show you an example using query scopes.
In your User class you have to make a little update:
class User extends Model
{
protected $table = 'user';
public function groups()
{
return $this->belongsToMany(Group::class, 'user_group')
//->withPivot('valid_before'); <-- Remove this
}
}
and create a scope in your Group model:
class Group extends Model
{
protected $table = 'group';
public $timestamps = false;
public function user()
{
return $this->belongsToMany(User::class, 'user_group');
}
/**
* This scope gets as input the date you want to query and returns the users collection
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #param string $date
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeUsersValidBefore($query, $date)
{
return $query->users()->wherePivot('valid_before', '<', $date);
}
}
Now, I imagine you have a GroupController that somewhere creates a query to retrieve the valid before users. Something like:
// [...]
$users = Group::usersValidBefore($yourDate)->get();
// [...]
If you want to create the query from the other side, I mean you want to use the User model and list all the Users that has a pivot relation with valid_before populated, than the right approach is creating a UserGroup intermediate model that can be easily used to create a query.
If you are using Laravel 8.x.x
It's much easier with Inline Relationship Existence Queries
If you would like to query for a relationship's existence with a single, simple where condition attached to the relationship query, you may find it more convenient to use the whereRelation and whereMorphRelation methods. For example, we may query for all posts that have unapproved comments:
use App\Models\Post;
$posts = Post::whereRelation('comments', 'is_approved', false)->get();
Of course, like calls to the query builder's where method, you may also specify an operator:
$posts = Post::whereRelation(
'comments', 'created_at', '>=', now()->subHour()
)->get();

Laravel: Multiple tables in one model

I have the following model for Users:
class User extends Authenticatable
{
use Notifiable;
protected $table = 'login_info';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
public function getDashboards()
{
return \DB::table('dashboard')
->select('type')
->where('id', Auth::id())
->orderBy('column', 'asc')
->get();
}
}
Users have different information in many tables
user info like name, office, dashboard,2FA etc
Is the way I do it now "best practice" (like the getDashboards function) for getting information from different tables?
Or should I create a model for each of the tables and then "join them" (hasMany, belongsToMany, and so on) for each of the tables?
EDIT:
I am now using models, but the result of the query is always an empty array.
class Dashboard extends Model
{
protected $table = 'dashboard';
public function user()
{
return $this->belongsTo(User::class,'user_id','id');
//user_id
}
}
user_id is the id of the user which is used in the login_info table.
And in the User class I have:
public function dashboards()
{
return $this->hasMany(Dashboard::class,'id','user_id');
}
In the login controller I have:
$user = \App\User::find(1);
$user->dashboards;
Anyone see what the problem could be?
Thanks for any help!
public function dashboards()
{return $this->hasMany(\App\Dashboard::class);
}
And in your Dashboard Model you do it this way
protected $casts = [
'user_id' => 'int',
];
public function user()
{
return $this->belongsTo(\App\User::class);
}
The more Laravel way is to rather created the related Dashboard model and use the eloquent relationships, and harness the features of the ORM. Nothing wrong to include an orderBy on the relationship if you always need ordering on that column.
class User extends Authenticatable
{
public function dashboards()
{
return $this->hasMany(Dashboard::class)
->orderBy('column', 'asc');
}
}
class Dashboard extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
You do not have to do anything in the model! Just refer to the model in the controller, for example:
User::where('id', Auth::id())->pluck('type');

Resources