Polymorphic relation - morphTo save() not recognizing custom primary key - php

User model:
public function userable()
{
return $this->morphTo();
}
Mentor model:
public function user()
{
return $this->morphOne('App\Models\User', 'userable');
}
The student model looks the same as the mentor model.
The tables for Students and Mentors contain a custom PK called user_id which references the primary key on the users table.
So what I'm trying to do is the following:
$user = new User();
$user->first_name = 'Trajce';
$user->last_name = 'Petkoski';
$user->nickname = 'ads';
$user->administrator = '0';
$user->email = 'asd';
$user->password = Hash::make('test');
$user->save();
$mentor = new Mentor();
$mentor->user_id = $user->id;
$mentor->save();
$user->userable_id = $mentor->user_id;
$mentor->user()->save($user);
However, on the Users table, userable_id is being set to 0 while userable_type value is set to the corret value. The issue here is that save() sets it to a predefined 0. Any idea what's going on behind the scenes?

Try this to add data to Polymorphic relation (morphOne):
Migrations:
// User
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('first_name');
$table->string('last_name');
$table->string('nickname');
$table->integer('administrator');
$table->string('email');
// add these two for the relation to work
$table->integer('userable_id')->unsigned();
$table->string('userable_type');
//
$table->rememberToken();
$table->timestamps();
});
// Mentor
Schema::create('mentors', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
});
User Model
public function userable()
{
return $this->morphTo();
}
Mentor Model
public function user()
{
return $this->morphOne('App\Models\User', 'userable');
}
Association Code:
$mentor = new Mentor();
// this is important: first save mentor
$mentor->save();
$userdata = [
'first_name' => 'Trajce',
'last_name' => 'Petkoski',
'nickname' => 'ads',
'administrator' => 0,
'email' => 'asd',
'password' => Hash::make('test')
];
$mentor->user()->create($userdata);
This works like a charm in my Laravel 5.4 test installation

Try this
public function users() {
return $this->morphMany('App\Models\User', 'userable');
}

Related

Add multiple categories for articles

I need to add several categories for a new article. I will write down everything I do in order:
migration of categories
public function up()
{
Schema::create('blog_categories', function (Blueprint $table) {
$table->BigIncrements('id');
$table->string('title', 128);
$table->timestamps();
});
}
migration of articles
public function up()
{
Schema::create('articles', function (Blueprint $table) {
$table->BigIncrements('id');
$table->string('title', 128);
$table->string('slug', 64);
$table->string('subtitle', 256)->nullable();
$table->timestamps();
});
}
creating another migration article_blog_category_table
public function up()
{
Schema::create('article_blog_category', function (Blueprint $table) {
$table->unsignedBigInteger('blog_category_id')->nullable();
$table->foreign('blog_category_id')
->references('id')->on('blog_categories')->onDelete('set null');
$table->unsignedBigInteger('article_id')->nullable();
$table->foreign('article_id')
->references('id')->on('articles')->onDelete('cascade');
});
}
Doing belongsToMany in models
article model
public function blog_categories()
{
return $this->belongsToMany('App\Models\BlogCategory');
}
category model
public function articles()
{
return $this->belongsToMany('App\Models\Article');
}
}
Next, I write the function for adding an article in the controller (I think there is no need to write the function for adding a category, everything is clear there)
Articles controller
public static function saveArticle(Request $request) {
$validator = Validator::make($request->all(), [
'blog_category_id' => 'required|numeric',
'title' => 'required|max:128',
'slug' => 'required|unique:articles|max:64',
'subtitle' => 'max:256',
]);
if ($validator->fails()) {
return response()->json([
'message' => $validator->errors()->first()
], 422);
}
$article = new Article();
$blog_category = BlogCategory::where('id', $request->blog_category_id)->first();
if(!$blog_category){
return response()->json([
'message' => 'Blog category not found'
], 404);
}
$article->blog_category_id = $request->blog_category_id;
$article->title = $request->title;
$article->slug = $request->slug;
$article->subtitle = $request->subtitle;
$article->save();
return Article::where('slug', $article->slug)->first();
}
I have a method in the function to add one category. The question of how to add here so that you can add several categories, I cannot figure it out. You need something like $article->blog_categories()->attach($request->blog_category_id); but how to apply it correctly?
Your naming convention is complicating your task.
Rename table in categories migration:
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->timestamps();
});
Also for simplicity, rename the joint (pivot) table
Schema::create('article_category', function (Blueprint $table) {
// You don't need a table id here
$table->foreignId('category_id')->index();
$table->foreignId('article_id')->index();
$table->unique(['article_id', 'category_id']);
// You also don't need timestamps
});
Defining relationships in the models:
// Article model
public function categories()
{
return $this->belongsToMany(\App\Models\Category::class);
}
// Category model
public function articles()
{
return $this->belongsToMany(\App\Models\Article::class);
}
Article controller
public function store() // No need to make the function static
{
$data = validator(request()->all(), [
// To save many categories, send them as an array
'categories' => 'array',
'categories.*' => [\Illuminate\Validation\Rule::exists('categories', 'id')], // Checks if category id is in the db
'title' => 'required|max:128',
'slug' => 'required|unique:articles|max:64',
'subtitle' => 'string',
])->validate();
$article = Article::create(
\Illuminate\Support\Arr::except($data, 'categories');
);
// Save categories
$article->categories()->attach($data['categories']);
return $article;
}
According to Documentation
Many To Many Relationships
Attaching / Detaching
//You can pass an array of id's categories in attach method:
$article->blog_categories()->attach($categories_ids_array);
/*
*if you want to pass more columns value you can pass an associative array of
*column names with their values e.g attach($categories ids array, an array of
*more columns with their values)
*/
$article->blog_categories()->attach($categories_ids_array, ['column_name' =>
$column_values]);

Laravel 8: How to send email notification to all admin users

I have a One To Many relationship between User Model & Order Model:
User.php:
public function order()
{
return $this->hasMany(Order::class);
}
Order.php:
public function user()
{
return $this->belongsTo(User::class);
}
Now I need to access user instance from order:
$order = Order::where('id', $id)->update(['status' => 'verified', 'price' => $data['price']]);
$user = $order->user;
dd($user);
But in this way, I get this error:
Trying to get property 'user' of non-object
So how to solve this issue and access user instance properly?
Here is the Migration of orders table:
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('material');
$table->string('color');
$table->string('dimensions');
$table->string('description')->nullable();
$table->tinyInteger('user_id')->unsigned()->nullable();
$table->timestamps();
});
update method wont return order detail's because update return true or false. So change it to
$order = Order::find($id);
$order->status='verified';
$order->price=$data['price'];
$order->save();
$user = $order->user;
update method takes array as argument update(array $values) and return int
Why don`t you add in the migration the relation as a foreignID?
$table->foreignID('user_id')->constrained();

Booking system with many to many laravel relationship

I'm trying to make an app where airbnb hosts can have a log of their bookings, I created three models: Home, Guest and Booking. Booking being the main player, I also think there should be a pivot table but I'm not sure which models should it link... I decided to go with booking_guest but I'm getting the following error when I create a booking:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'booking_id' cannot be null (SQL: insert into `booking_guest` (`booking_id`, `guest_id`) values (?, 1), (?, 2))
I do something like this in my BookingController:
public function create(Request $request)
{
$guestIds = Guest::latest()->take(2)->pluck('id');
$home = Home::findOrFail(1);
$booking = new Booking();
$booking->home_id = $home->id;
$booking->guests()->attach($guestIds);
$booking->save();
return response()->json([
'booking' => $booking,
]);
}
I'm not feeling too sure about this configuration, could you guys share some light on me.
These are my models:
class Home extends Model
{
public function guests()
{
return $this->belongsToMany('App\Models\Guest', 'guest_home', 'home_id', 'guest_id');
}
public function bookings()
{
return $this->hasMany('App\Models\Booking');
}
}
class Booking extends Model
{
public function guests()
{
return $this->belongsToMany('App\Models\Guest', 'booking_guest', 'booking_id', 'guest_id');
}
}
class Guest extends Model
{
public function bookings()
{
return $this->belongsToMany('App\Models\Booking', 'booking_guest', 'guest_id', 'booking_id');
}
}
My migrations:
//Booking guest pivot table
Schema::create('booking_guest', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('booking_id')->index();
$table->foreign('booking_id')->references('id')->on('bookings')->onDelete('cascade')->onUpdate('cascade');
$table->unsignedInteger('guest_id')->nullable()->index();
$table->foreign('guest_id')->references('id')->on('guests')->onDelete('cascade')->onUpdate('cascade');
$table->timestamps();
});
Schema::create('guests', function (Blueprint $table) {
$table->increments('id');
$table->string('fullName');
$table->text('country');
$table->timestamps();
});
Schema::create('bookings', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('home_id')->index();
$table->foreign('home_id')->references('id')->on('homes')->onDelete('cascade')->onUpdate('cascade');
$table->timestamp('entryDate')->nullable();
$table->timestamp('exitDate')->nullable();
$table->timestamps();
});
Schema::create('homes', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('host_id')->index();
$table->foreign('host_id')->references('id')->on('hosts')->onDelete('cascade')->onUpdate('cascade');
$table->string('fullAddress')->unique();
$table->integer('rooms')->unique();
$table->timestamps();
});
As you can see from here:
public function create(Request $request)
{
...
$booking = new Booking(); // <-- here
$booking->guests()->attach($guestIds); // <-- here
$booking->save(); // <-- here
...
}
you are creating a new instance of Booking, then associating to it a Guest and then saving the instance of Booking.
However ->attach(...) tries to associate the Booking with the Guest, but the Booking does not exists at that time on the DB.
I would suggest to use Booking::create, so that after that statement, the booking exists on the DB and so you can attach to it the Guest:
public function create(Request $request)
{
$guestIds = Guest::latest()->take(2)->pluck('id');
$home = Home::findOrFail(1);
$booking = Booking::create([ // <- using Booking::create
'home_id' => $home->id // <- using Booking::create
]); // <- using Booking::create
$booking->guests()->attach($guestIds);
return response()->json([
'booking' => $booking,
]);
}

Laravel FollowController relationship error

I am making FollowController where I have two tables following_users table and following_user_item table. which is in hasMany relationship. When a authenticate current_user wants to follow an user, the ID of the user will stored in following_users table and its relational table stored the current_user_id and the following_user_id (which is the id of following_users table). here is the schema.
following_users_table:
public function up()
{
Schema::create('following_users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('user_id')->unsigned();
$table->foreign('user_id')
->references('id')
->on('users');
$table->timestamps();
});
}
following_user_item_table:
public function up()
{
Schema::create('following_user_items', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('following_users_id')->unsigned();
$table->foreign('following_users_id')
->references('id')
->on('following_users');
$table->bigInteger('user_id')->unsigned();
$table->timestamps();
});
}
I have done the FollowController but the problem is comming when try to check whether the user is already followed or not.
Follow Relationship in User model
public function followingUserList()
{
return $this->hasOne('App\FollowingUser');
}
/**
* Get the stories associated with the user through an intermediate table
*
*/
public function followingUsers()
{
return $this->hasManyThrough(
'App\FollowingUserItem',
'App\FollowingUser',
null,
'following_users_id'
);
}
FollowingUser Model Relationship with User and FollowingUserItem
public function user()
{
return $this->belongsTo('App\User');
}
public function users()
{
return $this->hasMany('App\FollowingUserItem','following_users_id');
}
Here is my FollowController:
class FollowController extends Controller
{
//
public function index($id)
{
$user = User::find($id);
$logged_userId = Auth::User();
if ($user->id == $logged_userId->id) {
return [
'status' => false,
'message' => 'You can not Follow yourself',
];
}
if ($user && $logged_userId) {
$checkUsers = FollowingUser::where('user_id', $user->id)->get()->users()->where('user_id', $logged_userId->id);
if ($checkUsers)
{
return 'already followed';
}
else
{
$user->followingUserList()->save(new FollowingUser())->users()->save(new FollowingUserItem(['user_id' => $logged_userId->id]));
return 'sucess';
}
}
}
}
I go the error
Method Illuminate\Database\Eloquent\Collection::users does not exist.
When you call get() Laravel returns a collection as it does not know how many rows there will be there. This is why you get collection does not have users set error. Since you filter on an id you know there is only gonna be one, therefor you can utilize the first() method.
So change the code to use first().
$checkUsers = FollowingUser::where('user_id', $user->id)->first()->users()->where('user_id', $logged_userId->id);

Assign User to any Shop in Laravel Relationship?

In laravel, I have 3 table
User // for Authentication and Create Another User Once logged in
Expense
Shop
My Purpose- I want user can register and Also, can create another user when they logged in And Can assign user to another Shop as they want..
And Only User in the Same Shop Can see their Expense..
// My User Table
<pre>
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('shop_id')->nullable();
$table->unsignedBigInteger('user_id')->nullable();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
</pre>
// My Expense Table
<pre>
Schema::create('expenses', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('user_id');
$table->date('date');
$table->string('description');
$table->double('amount');
$table->timestamps();
$table->foreign('user_id')->references('id')->on('users');
});
</pre>
// My Shop Table
<pre>
Schema::create('shops', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('expense_id')->nullable();
$table->unsignedBigInteger('user_id');
$table->string('name');
$table->string('description');
$table->timestamps();
$table->foreign('expense_id')->references('id')->on('expenses');
$table->foreign('user_id')->references('id')->on('users');
});
</pre>
// My User Model
<pre>
public function expense()
{
return $this->hasMany(\App\Expense::class);
}
public function shop()
{
return $this->hasMany(\App\Shop::class, 'user_id');
}
</pre>
// My Expense Model
<pre>
class Expense extends Model
{
protected $fillable = ['date', 'description', 'amount', 'user_id', 'shop_id'];
public function user()
{
return $this->belongsTo(\App\User::class);
}
}
</pre>
// My Shop Model
<pre>
class Shop extends Model
{
protected $fillable = ['name', 'description', 'expense_id', 'shop_id'];
public function user()
{
return $this->belongsTo(\App\User::class, 'user_id');
}
}
</pre>
// Expense Controller
<pre>
public function index(Request $request)
{
$expense = Expense::with(['user'])->get();
return ExpenseResource::collection($expense);
// dd(auth()->user());
}
public function create(Request $request)
{
$request->validate([
'date' => 'required',
'description' => 'required',
'amount' => 'required',
]);
$expense = new Expense();
$expense->user_id = auth()->user()->id;
$expense->date = $request->date;
$expense->description = $request->description;
$expense->amount = $request->amount;
$expense->save();
return new ExpenseResource($expense);
}
</pre>
// in My UserController
<pre>
public function index()
{
$users = User::all();
$shops = Shop::all();
return view('user', compact('users', 'shops'));
// return UserResource::collection($users);
}
public function create(Request $request)
{
$request->validate([
'name' => 'required',
'email' => 'required',
'password' => 'required',
]);
$user = new user();
$user->user_id = auth()->user()->id;
$user->name = $request->name;
$user->email = $request->email;
$user->password = bcrypt($request->password);
$user->save();
return new UserResource($user);
}
</pre>
Is it make sense?
Any idea, thanks..
As stated in the comments, you'll need to check the current User and constrain the returned Expense records to only those that 1) have a User and 2) match the same Store as the current User. This can be done in a single whereHas() clause:
public function index(Request $request) {
$user = auth()->user(); // If using default `Auth` logic.
$expenses = Expense::whereHas('user', function($subQuery) use($user){
return $subQuery->where('shop_id', '=', $user->shop_id);
})->with(['user'])->get();
return ExpenseResource::collection($expenses);
}
What ->whereHas() does is constrains the query fetching your Expense models to respect the logic you pass it, which in this case is only include Expense models that have a user that has the same shop_id as the currently logged in User.
Note:If the current User does not have a Shop, it might return unexpected results, but you could protect the route to only allow a User with a Shop to access it, etc.

Categories