Booking system with many to many laravel relationship - php

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,
]);
}

Related

laravel nova - how to make a self referential model with restriction

I have a laravel model based on the following table:
public function up()
{
Schema::create('things', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->string('label');
$table->foreignId('user_id')->nullable()->constrained('users');
});
There is also a pivot table that makes this a many-to-many self-referential model.
public function up()
{
Schema::create('thing_thing', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->string('message')->nullable();
$table->unsignedBigInteger('parent_id')->nullable();
$table->unsignedBigInteger('child_id')->nullable();
$table->unique(['parent_id', 'child_id']);
$table->foreign('parent_id')->references('id')->on('things')->onDelete('cascade');
$table->foreign('child_id')->references('id')->on('things')->onDelete('cascade');
});
}
When I create a Nova resource linked to this model, I would like to restrict the attaching of a thing to itself. So a thing with id = 1, for example, would not show up in the selector for attachments for the thing with id = 1. Here's my Nova resource:
public function fields(Request $request)
{
return [
ID::make(__('ID'), 'id')->sortable(),
Text::make('label'),
ID::make('user_id')->hideWhenUpdating()->hideWhenCreating(),
BelongsToMany::make('Trees', 'trees'),
BelongsToMany::make('Things', 'childOf'),
BelongsToMany::make('Things', 'parentOf')
];
}
You can solve this through the App\Nova\Ressource's relatableQuery method. Simply override the method in your nova resource:
class Thing extends Resource {
// ...
public static function relatableQuery(NovaRequest $request, $query)
{
// Make sure you only apply the filter to the things-things relatable query
if( $request->route('resource') === 'things' ) {
$currentId = $request->route('resourceId');
$query->where('id', '!=', $currentId);
}
return $query
}
}
You can find the docs here
In addition, you might want to make the column-combination of parent_id and child_id unique in your migration to further ensure uniqueness.

Booking system ala airbnb many to many laravel relationship?

I'm trying to create a system to log all bookings for a few airbnb hosts, at first I had two models Home and Guest, where a home can have many guests and a guest could book for many homes,
then I thought about creating a guest_home pivot table to link both tables... then things got complicated in my head, what about a Booking model, and (have that booking model act as a pivot table ?) , to me doing new Booking() seemed better than attaching ids to home model?
How could you guys go about this, it's making my brain malt right now...
I thought of doing something like this in my BookingController:
public function createBooking(Request $request)
{
$guestIds = Guest::latest()->take(3)->pluck('id');
$home = Home::findOrFail($request->input('home_id', 2));
$booking = new Booking();
$booking->home_id = $home->id;
$booking->guests()->attach($guestIds);
$booking->save();
return response()->json([
'booking' => $booking,
]);
}
Should I create home_guest pivot table, is a pivot table even needed? what models would I link, please bear with me, so far this is what I got:
Models
class Guest extends Model
{
public function bookings()
{
return $this->belongsToMany('App\Models\Home', 'bookings', 'guest_id', 'home_id');
}
}
class Home extends Model
{
public function guests()
{
return $this->belongsToMany('App\Models\Guest', 'bookings', 'home_id', 'guest_id');
}
public function bookings()
{
return $this->hasMany('App\Models\Booking');
}
}
class Booking extends Model
{
//Is Booking needed or I could make a pivot table like home_guest called 'bookings' ?
public function guests()
{
return $this->belongsToMany('App\Models\Guest', 'booking_guest', 'booking_id', 'guest_id');
}
}
Migrations:
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->unsignedInteger('guest_id')->nullable()->index();
$table->foreign('guest_id')->references('id')->on('guests')->onDelete('cascade')->onUpdate('cascade');
$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();
});
Schema::create('guests', function (Blueprint $table) {
$table->increments('id');
$table->string('fullName')->unique();
$table->string('identificationType')->unique();
$table->text('country')->nullable();
$table->timestamps();
});

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);

how to insert one product to many categoies laravel attach()

I have tried much time but failed.
Ex: I want result like if I insert a Product into the database and select multiple categories and store them different table then show me error like this:
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'space.category_product' doesn't exist (SQL: insert into `category_product` (`category_id`, `product_id`) values (1, ))
If I rename database table From category_products To category_product manually the show me a new Error here below :
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'product_id' cannot be null (SQL: insert into `category_product` (`category_id`, `product_id`) values (1, ))
Here is my Database Code
Schema::create('products', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('model');
$table->string('slug')->unique();
$table->string('availability');
$table->float('price');
$table->longText('images');
$table->text('short_detail');
$table->text('feature');
$table->longText('purchase_delivery');
$table->longText('replace_policy');
$table->timestamps();
});
Schema::create('categories', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('slug');
$table->timestamps();
});
Schema::create('category_products', function (Blueprint $table) {
$table->increments('id');
$table->integer('product_id')->unsigned();
$table->foreign('product_id')->references('id')->on('products')->onUpdate('cascade')->onDelete('cascade');
$table->integer('category_id')->unsigned();
$table->foreign('category_id')->references('id')->on('categories')->onUpdate('cascade')->onDelete('cascade');
$table->timestamps();
});
Here is My Models:
class Category extends Model
{
public function Products()
{
return $this->belongsToMany(Product::class);
}
}
class Product extends Model
{
public function Categories()
{
return $this->belongsToMany(Category::class);
}
}
ProductController:
public function store(Request $request)
{
if ($request->hasFile('file')){
foreach ($request->file as $file) {
$fileExt = $file->getClientOriginalExtension();
$uniqId = uniqid('img-');
$fileName[] =$uniqId.'.'.$fileExt;
$Name = $uniqId.'.'.$fileExt;
$file->move('public/uploads',$Name);
}
$images = implode(",",$fileName);
$product = new Product();
$product->Categories()->attach($request->categories_id);
$product->name= $request->input('name');
$product->model= $request->input('model');
$product->slug= $request->input('slug');
$product->availability= $request->input('availability');
$product->price= $request->input('price');
$product->images= $images;
$product->short_detail= $request->input('short_detail');
$product->feature= $request->input('feature');
$product->purchase_delivery= $request->input('purchase_delivery');
$product->replace_policy= $request->input('replace_policy');
if ($product->save()) {
return redirect()->route('product.index')
->with('success', 'Product Added successfully');
}
return back()->withInput()->with('errors', 'Error Adding New Product');
}
}
Schema::create('category_product', function (Blueprint $table) {
$table->increments('id');
$table->integer('product_id')->unsigned();
$table->foreign('product_id')->references('id')->on('products')->onUpdate('cascade')->onDelete('cascade');
$table->integer('category_id')->unsigned();
$table->foreign('category_id')->references('id')->on('categories')->onUpdate('cascade')->onDelete('cascade');
$table->timestamps();
});
You have typo in the name it should be category_product.
Your table name should be alphabetically ordered and use singular names.
If you do not want to change the table names
public function Categories()
{
return $this->belongsToMany(Category::class, 'category_products,'category_id', 'product_id');
}
My ProductController :
public function store(Request $request)
{
if ($request->hasFile('file')){
foreach ($request->file as $file) {
$fileExt = $file->getClientOriginalExtension();
$uniqId = uniqid('img-');
$fileName[] =$uniqId.'.'.$fileExt;
$Name = $uniqId.'.'.$fileExt;
$file->move('public/uploads',$Name);
}
$images = implode(",",$fileName);
$product = new Product();
$product->name= $request->input('name');
$product->model= $request->input('model');
$product->slug= $request->input('slug');
$product->availability= $request->input('availability');
$product->price= $request->input('price');
$product->images= $images;
$product->short_detail= $request->input('short_detail');
$product->feature= $request->input('feature');
$product->purchase_delivery= $request->input('purchase_delivery');
$product->replace_policy= $request->input('replace_policy');
$product->save();
$product->Categories()->attach($request->categories_id);
if ($product->save()) {
return redirect()->route('product.index')
->with('success', 'Product Added successfully');
}
return back()->withInput()->with('errors', 'Error Adding New Product');
}
}
Use $product->categories()->attach($category_id) after the $product->save()
Here you can use your models
class Category extends Model
{
public function Products()
{
return $this->belongsToMany(Product::class, 'category_products');
}
}
class Product extends Model
{
public function Categories()
{
return $this->belongsToMany(Category::class, 'category_products);
}
}
Here category_products is a pivot table.
Now Save
$product = Product::create(['name' => 'product1']);
$product->Categories()->sync([1, 3, 4]);
OR
$product->Categories()->attach([1, 3, 4]);
Difference
Sync: You may also use the sync method to construct many-to-many associations. The sync method accepts an array of IDs to place on the
intermediate table. Any IDs that are not in the given array will be
removed from the intermediate table. So, after this operation is
complete, only the IDs in the given array will exist in the
intermediate table:
Attach: It is same like append, it append the new values with the existing one

Can I create custom pivot table name using another M:M entity?

I'm still thinking is there a ways how can I create a custom pivot table name? Because I created a documents and users table has a many to many relationship with document_user which is my pivot table and this table was created for received document that user created. And I'm planning to create a another pivot table for document and user this table was for sent documents so I can have history. See my code below.
create_document_user_table
public function up()
{
Schema::create('document_user',function (Blueprint $table)
{
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->integer('document_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('document_id')->references('id')->on('documents')->onDelete('cascade');
$table->unsignedInteger('sender_id')->nullable();
$table->foreign('sender_id')->references('id')->on('users')->onDelete('cascade');
$table->dateTime('dateReceived')->default(DB::raw('CURRENT_TIMESTAMP'));
$table->timestamp('dateModified')->default(DB::raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'));
});
}
documents_table
public function up()
{
Schema::create('documents', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->text('content');
$table->integer('category_id')->unsigned();
$table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
$table->timestamps();
});
}
users_table
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('first_name');
$table->string('last_name');
$table->string('middle_name');
$table->string('email');
$table->string('username');
$table->string('address');
$table->string('password');
$table->string('remember_token');
$table->integer('role_permission_id')->unsigned();
$table->foreign('role_permission_id')->references('id')->on('roles_permissions_dt')->onDelete('cascade');
$table->timestamps();
});
}
This works well inserting records to my pivot table. What I'm planning to achieve is every-time I inserted a records for documents this will inserted too in my custom pivot table not only in my document_user pivot table. Any help would appreciated! Thanks for your info or tips.
UPDATE
#Mina thanks for the tips that you given but actually this is my insert or save for my pivot table. How can I inserted this in my revisions table?
DocumentController
public function postDocuments(Request $request)
{
$this->validate($request,
[
'title' => 'required|regex:/(^[A-Za-z0-9 ]+$)+/|max:255',
'content' => 'required',
'category_id' => 'required',
'recipient_id' => 'required',
]);
$document = new Document();
//Request in the form
$document->title = $request->title;
$document->content = $request->content;
$document->category_id = $request->category_id;
$document->save();
$user = Auth::user();
foreach($request->recipient_id as $recipientId)
{
$document->recipients()->sync([ $recipientId => ['sender_id' => $user->id]],false );
}
return redirect()->back();
}
You can call your pivot tables as you like.
As mentioned previously, to determine the table name of the
relationship's joining table, Eloquent will join the two related model
names in alphabetical order. However, you are free to override this
convention. You may do so by passing a second argument to the
belongsToMany method:
return $this->belongsToMany('App\Role', 'user_roles');
(Eloquent: Relationships #Many To Many)
In your case you would need to define the relations like this:
class User extends Model
{
// other stuff
createdDocuments()
{
return $this->belongsToMany('App\Document', 'document_user_created');
}
sentDocuments() // or receivedDocuments()
{
return $this->belongsToMany('App\Document', 'document_user_sent');
}
// other stuff
}
class Document extends Model
{
// other stuff
createdByUsers()
{
return $this->belongsToMany('App\User', 'document_user_created');
}
sentToUsers() // or sentFromUsers() or whatever it does mean
{
return $this->belongsToMany('App\User', 'document_user_sent');
}
// other stuff
}
You do not need another pivot table. You need a table like that:
public function up()
{
Schema::create('revisions', function (Blueprint $table) {
$table->increments('id');
$table->integer('document_id')->unsigned();
//Rest of table structure
$table->foreign('document_id')->references('id')->on('document_user')->onDelete('cascade');
$table->timestamps();
});
}
When you need to create a new Revision:
$document = new Document;
//add $document attributes
$user->documents()->save($document);
$document->recipients->each(function($recipient){
$id = $recipient->pivot->id;
Revision::create(['document_id'=>$id]);
})

Categories