Get relationship from another relationship with Eloquent and Laravel 8 - laravel

I try to get Address with Country's name, which belongs to InvoiceData. Invoice Data belongs to Personal or Business Profile. Last condition was made with morphTo() Laravel's feature.
Simplified db structure:
personal_profiles:
id, name
invoice_data:
id, address_id, invoiceable_id, invoiceable_type
addresses:
id, country_id, postal_code, city
countries
id, name
Then models:
class UserProfile extends Model
{
public function invoiceData()
{
return $this->morphOne(InvoiceData::class, 'invoiceable');
}
}
class InvoiceData extends Model
{
public function imageable()
{
return $this->morphTo();
}
public function address()
{
return $this->belongsTo(Address::class)->with(Country::class);
}
}
class Address extends Model
{
public function country()
{
return $this->belongsTo(Country::class);
}
}
class Country extends Model
{
public function address()
{
return $this->hasMany(Address::class);
}
}
And the Controller:
public function getPersonalInvoiceData()
{
/** #var User $user */
$user = \Auth::user();
/** #var UserProfile $profile */
$profile = $user->userProfile;
/** #var InvoiceData $invoiceData */
$invoiceData = $profile->invoiceData;
/** #var Address $address */
$address = $invoiceData->address;
//$address = $invoiceData->address()->with(Country::class)->get();
$responseData = [
'name' => $user->name,
'surname' => $user->surname,
'address' => $address,
];
return response()->json($responseData);
}
There is no problem with get address:
"address": {
"id": 1,
"country_id": 171,
"city": "Foo City",
"postal_code": "00-100",
}
But I don't know how to "replace" country_id with related name using Eloquent. As you can see, I tried to use with() on but I got the exception with message: Call to undefined relationship [App\\Models\\Country] on model [App\\Models\\Address]. I thought that relations I mentioned are enough. For what did I forgot?

You shouldn't use class in "with" there should be the relation name but I think you don't want to use with method because it will get all countries when you call get method.
All you have to do is:
$address = $invoiceData->address->load('country');

Related

laravel 8 store request with foreign key user_id not working

I would like to store the corresponding logged in user when adding a new School data. What I'm trying to do is store the logged in user_id in the schools table, in order to know on who added the school data. I have a users table already, which will establish the relation in the schools table.
My goal is when an admin is logged in, he/she can see all of the School records, otherwise if it's a user, then only fetch the records he/she added. The problem is that I can't figure out on when and where to insert the user_id data during the store request as I'm getting an error "user id field is required". Here's what I've tried so far:
Migration:
class CreateSchoolsTable extends Migration
{
public function up()
{
Schema::create('schools', function (Blueprint $table) {
$table->id();
$table->string('school_name');
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->timestamps();
});
}
}
School Model:
class School extends Model
{
use HasFactory;
protected $fillable = ['school_name', 'user_id'];
public function User() {
return $this->belongsTo(User::class);
}
}
Store Request:
class StoreSchoolRequest extends FormRequest
{
public function rules(): array
{
return [
'school_name' => 'required|string|max:255',
'user_id' => 'required|exists:users,id'
];
}
}
Controller:
class SchoolController extends Controller
{
public function store(StoreSchoolRequest $request) {
$school_data = $request->validated();
$user_id = \Auth::user()->id;
$school_data['user_id'] = $user_id;
School::create($school_data );
return Redirect::route('schools.index');
}
}
Any inputs will be of big help! Thanks.
Laravel has elegant way to bind authenticated user_id. Remove user_id from request class and chaining method. Also setup relationship from User model to School Model
Form Request Class
class StoreSchoolRequest extends FormRequest
{
public function rules(): array
{
return [
'school_name' => 'required|string|max:255',
];
}
}
User Model
protected $fillable = ['school_name', 'user_id'];
...
// new line
public function schools() {
return $this->hasMany(School::class);
}
Your Controller
class SchoolController extends Controller
{
public function store(StoreSchoolRequest $request) {
auth()->user()->schools()->create($request->validated());
return Redirect::route('schools.index');
}
}
UPDATE ANSWER
Since user_id value is school name (based on image link from comment), probably there's something wrong either in User or School model. Here the quick fix
Your Controller
class SchoolController extends Controller
{
public function store(StoreSchoolRequest $request) {
auth()->user()->schools()->create(
array_merge(
$request->validated(),
['user_id' => auth()->id()]
)
);
return Redirect::route('schools.index');
}
}
You can add 'created_by' and 'updated_by' fields to your table. so you can register in these fields when additions or updates are made.
Then you can see who has added or updated from these fields.
class School extends Model
{
use HasFactory;
protected $fillable = ['school_name', 'user_id', 'created_by', 'updated_by'];
public function User() {
return $this->belongsTo(User::class);
}
}
Your controller part is correct but since you get the logged in user, you wont be having user_id in the request. So you should remove the rules about user_id from your StoreSchoolRequest.
class StoreSchoolRequest extends FormRequest
{
public function rules(): array
{
return [
'school_name' => 'required|string|max:255'
];
}
}
Problem is here ..
$school_data = $request->validated();
Since you are using $request->validated()..
You have to safe()->merge user_id into it , here Docs : .
$validated = $request->safe()->merge(['user_id' => Auth::user()->id]);
Then put this $validated into create query , Thanks. –

Strange behavior on laravel many to many relationship

I have two models User and Tenant and in my project, a User can have many Tenants connected to him and Tenant can have many users connect to him.
This is my User model
public function tenants()
{
return $this->beLongsToMany(\App\Models\TenantsUsers::class, 'tenants_user', 'user_id', 'tenant_id');
}
This is my Tenant model
public function users()
{
return $this->beLongsToMany(\App\Models\TenantsUsers::class, 'tenants_user', 'tenant_id', 'user_id');
}
And this is my TenantsUsers model
class TenantsUsers extends Model
{
use UtilTrait;
use Notifiable;
protected $table = 'tenants_user';
protected function serializeDate(DateTimeInterface $date)
{
return $date->format('Y-m-d H:i:s');
}
/**
* The attributes that should be casted to native types.
*
* #var array
*/
protected $casts = [
'user_id' => 'integer',
'tenant_id' => 'integer'
];
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
**/
public function tenants()
{
return $this->hasMany(\App\Models\Tenant::class, 'tenant_id');
}
public function users()
{
return $this->hasMany(\App\Models\User::class, 'user_id');
}
When I execute this function from the repository:
$userTemp = $this->userRepository->with(['tenants'])->findWhere(['email' => $userEmail])->first();
And I'm getting this error :
SQLSTATE[42712]: Duplicate alias: 7 ERROR: table name "typo_tenants_user" specified more than once (SQL: select
"typo_tenants_user".*, "typo_tenants_user"."user_id" as "pivot_user_id", "typo_tenants_user"."tenant_id" as
"pivot_tenant_id" from "typo_tenants_user" inner join "typo_tenants_user" on "typo_tenants_user"."id" =
"typo_tenants_user"."tenant_id" where "typo_tenants_user"."user_id" in (1))
What I'm doing wrong?
You don't need to create a model for pivot tables in Eloquent many-to-many relationships. Instead, use the related model's class when defining the relationship:
// User model
public function tenants()
{
return $this->belongsToMany(\App\Models\Tenant::class, 'tenants_user', 'user_id', 'tenant_id');
}
// Tenant model
public function users()
{
return $this->belongsToMany(\App\Models\User::class, 'tenants_user', 'tenant_id', 'user_id');
}
If you follow Eloquent naming conventions by defining the pivot table as tenant_user rather than tenants_user, things can be even further simplified to:
// User model
public function tenants()
{
return $this->belongsToMany(\App\Models\Tenant::class);
}
// Tenant model
public function users()
{
return $this->belongsToMany(\App\Models\User::class);
}

Laravel search based on foreign key from a different table

I have three tables post,user and country.
Post is saved by user who is from a country.
Now I want to search all posts of all users from a country.
so user will filter by country and I get country code from search box to controller $request->input($country);
Here are my model relationships:
POST MODEL:
class Post extends Model
{
protected $table = 'posts';
protected $dates = ['status_change'];
public function photos()
{
return $this->hasMany(Photo::class,'post');
}
public function make_rel()
{
return $this->belongsTo(Make::class, 'make_id' ,'id','make_logo');
}
public function user_rel()
{
return $this->belongsTo(User::class, 'created_by' ,'id');
}
}
COUNTRY MODEL:
class Country extends Model
{
public function users(){
return $this->hasMany('App\User');
}
}
USER MODEL:
class User extends Authenticatable
{
public function country_rel()
{
return $this->belongsTo(Country::class, 'country' ,'country_code');
}
}
SEARCH FUNCTION
public function search(Request $request)
{
$this->validate($request, [
'country' => 'required',
]);
$country = Country::where('country_name',$request->input('country'))->get();
$data = Post::where('created_by',$country->user_rel->name)
->get();
dd($data);
}
This is not working. Could anyone advise what am I doing wrong?
I would use hasManyThrugh. The documentation even uses your exact use case:
class Country extends Model
{
public function users(){
return $this->hasMany('App\User');
}
public function posts() {
return $this->hasManyThrough(
'App\Post',
'App\User',
'country_id', // Foreign key on users table...
'user_id', // Foreign key on posts table...
'id', // Local key on countries table...
'id' // Local key on users table...
);
}
}

How do I load a collection in a model then query it with the query builder

I have create a morphMany relationship for ratings and I'm having a problem loading the ratings relationship data inside the model using the model->load or model::with method both of them aren't letting me use the collections model builder.
if I do this inside a method of a model it throws an error:
$all = this->ratings()->get();
return $all;
Call to undefined method Illuminate\Database\Query\Builder::ratingInfo()
I need the ratings query builder so I can then query and filter the results but It's not using the query builder and even if I make this a scope it's still throws the same error.
all code:
class Product extends Model
{
use Rateable;
protected $table = "products";
protected $fillable = [
'title',
'sku',
'quantity',
'unit_price',
'created_by', 'updated_by'
];
public function created_by() {
return $this->belongsTo('App\User', 'created_by', 'id');
}
public function updated_by() {
return $this->belongsTo('App\User', 'updated_by', 'id');
}
public function ratings() {
return $this->morphMany('App\Rating', 'rateable');
}
public function ratingInfo() {
$all = $this->ratings()->get() error using get request for eager loading;
// i want to query like this
$two_star = $all->filter(function ($item, $key) {
return $item->rating === 2;
});
return $all;
}
}
public function show($id) {
$product = Product::findOrFail($id);
// it doesn't seem to matter if use the keyword ::with('ratingInfo')
$product->load('ratingInfo', 'created_by', 'updated_by');
return response()->json($product, 200, ['Content-Length' => strlen(json_encode($product))]);
}
class Rating extends Model
{
protected $table = 'ratings';
protected $fillable = ['rating', 'comment', 'user_id', 'rateable_id', 'rateable_type'];
public function rating()
{
return $this->morphTo();
}
}
Using phone numbers and user and companies as an example:
class PhoneNumber extends Model
{
/**
* Get all of the owning callable models.
*/
public function callable()
{
return $this->morphTo();
}
}
class Company extends Model
{
/**
* Get all of the model's phone numbers.
*
* #return mixed
*/
public function phoneNumbers()
{
return $this->morphMany(PhoneNumber::class, 'callable');
}
}
class User extends Model
{
/**
* Get all of the model's phone numbers.
*
* #return mixed
*/
public function phoneNumbers()
{
return $this->morphMany(PhoneNumber::class, 'callable');
}
}
To save a phone number to a user or company would be like this:
$phoneNumber = new PhoneNumber(['number' => '555-555-5555']);
$user->phoneNumbers()->save(phoneNumber);
$phoneNumber = new PhoneNumber(['number' => '555-555-5555']);
$company->phoneNumbers()->save(new PhoneNumber(phoneNumber));
Then to access the phone number collections associated with each, simply:
$user->phoneNumbers // this is a Collection
$company->phoneNumbers // this is a Collection
$user->phoneNumbers->count() // access to all Collection methods as this point

unique name rule validation based on pivot table laravel

I have a system where renting companies have cars. A car can be available in many renting companies. So it is a many-to-many relationship.
I want to provide the companies with the possibility to add new cars when they buy them. If the car already exists, the system wouldn't create it, but flash an error message saying they already have that car.
How to use the unique validation rule on the name field of the Car being added? The challenge is that the Car model doesn't have the id of the company, and the pivot table doesn't have the name of the car, it contains just the car_id and the company_id.
Many thanks
My Car Model
class Car extends Model
{
protected $fillable = ['name'];
protected $dates = ['purchased_at'];
public function company(){
return $this->belongsToMany('App\Company')->withPivot('quantity', 'purchased_at')->withTimestamps();
}
}
My Company Model
class Company extends Model implements AuthenticatableContract, CanResetPasswordContract
{
use Authenticatable, CanResetPassword;
protected $table = 'companies';
protected $hidden = ['password', 'remember_token'];
public function setPasswordAttribute($password){
$this->attributes['password'] = bcrypt($password);
}
public function cars(){
return $this->belongsToMany('App\Car')->withPivot('quantity', 'purchased_at')->withTimestamps();
}
}
My car Controller
class CarsController extends Controller
{
public function store(CarRequest $request)
{
$car = new Car;
$car->name = $request->input('name');
$car->save();
$car->company()->attach(Auth::user()->id,array('quantity' => $request->input('quantity'),'purchased_at' => \Carbon\Carbon::now()));
return Redirect('companies/'. Auth::user()->id .'/cars')->with(['success'=>'You have just created a new car!']);
}
}
My Car Request
class CarRequest extends Request
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'name' => 'required | unique:car_company,car_id',
'quantity' => 'required'
];
}
}
I found a solution. Basically, we can conditionally modify a rule entry. In this case, I look for cars inside the authenticated company, if the car name exists, then I change the rule to be unique on the cars table, which will fail because there is already a car with the same name in this table.
Here is my new rules function inside my CarRequest class:
public function rules()
{
$rules = [
'quantity' => 'required',
];
$car = Auth::user()->cars->where('name', $this->input('name'));
if (count($car) === 0)
{
$rules['name'] = 'required';
}
else{
$rules['name'] = 'required | unique:cars';
}
return $rules;
}

Resources