Is there a way to have ManyToMany relationship in Laravel without creating a third table connecting both entities manually? - laravel

I am implementing a Laravel REST API backend and I have two tables, trip_announcements (user announcing their trip) and countries (self explanatory). Each announcement can have multiple countries.
I ended up creating two functions:
//Models\TripAnnouncement
public function countries(){
return $this->belongsToMany(Country::class);
}
and
//Models\Country
public function trips(){
return $this->belongsToMany(TripAnnouncement::class);
}
Then I created a new migration file:
class CreateTripCountriesTable extends Migration
{
public function up()
{
Schema::create('country_trip_announcement', function (Blueprint $table) {
$table->foreignId('trip_announcement_id')->constrained('trip_announcements')->onDelete('cascade');
$table->foreignId('country_id')->constrained('countries')->onDelete('cascade');
});
}
public function down()
{
Schema::dropIfExists('country_trip_announcement');
}
}
It does work as intended but I am a bit confused whether it's a right way to do that. I expected the ORM to kind of link the two tables for me instead of having to create the linking table myself. Is my solution ok or is there a simpler way to do that in Laravel 8?

Many to many relationships require an intermediate table to link your two models together.
In Laravel, the Eloquent ORM is separate from database migrations. You need to create both the intermediate table as a migration, and link the two models via relationships.
The way you've coded your relationship is the correct way of doing it. Have a read of the docs here.

Related

Can I create a double-morphed many-to-many relationship in Laravel?

I have two models: Product and Group, which I want to both give a many-to-many 'shown' and a 'recommended' relation with itself and the other (so both have has to be morphed in both ways).
So a 'shown' resource of either type has a set of 'recommended' resources of either type (the Groups are actually acting as a container for grouped Products).
The table should be pretty straightforward:
Schema::create('recommendables', function (Blueprint $table) {
$table->id();
$table->morphs('shown');
$table->morphs('recommendable');
$table->timestamps();
});
Then relations are defined in the trait 'RecommendableTrait' which is then given to both classes. As far as these relationships are concerned both classes are then handled pretty much the same later on (even to be merged into a single collection), so there is merit in trying to do it this way.
The problem is that the following relation definitions won't work, because it will look for group_id if I call the morphed relations on Group, similarly with `morphedByMany'.
trait RecommendableTrait {
// RECOMMENDED GROUPS
public function recommendedGroups()
{
return $this->morphToMany(Group::class, 'recommendable', 'recommendables')
->withTimestamps();
}
// RECOMMENDED PRODUCTS
public function recommendedProducts()
{
return $this->morphToMany(Product::class, 'recommendable', 'recommendables')
->withTimestamps();
}
// SHOWN GROUPS
public function shownGroups()
{
return $this->morphToMany(Group::class, 'shown', 'recommendables')
->withTimestamps();
}
// SHOWN PRODUCTS
public function shownProducts()
{
return $this->morphToMany(Product::class, 'shown', 'recommendables')
->withTimestamps();
}
}
Now I know, I'm looking to create a bit of a weird relation tangle here. But I'm hoping defining a double-morphed many-to-many is possible in Laravel?
If not, I guess I'll have to either split it up in two single-morphed many-to-Many relationship sets for both classes. Which would be annoying because of the code that goes on top of these relations would have to be needlessly duplicated. Or I'll have to forego relations and just DB query the results directly. Thus giving up some of Eloquence's convenience, but at least allowing me to keep the code confined to a single shared trait.

How to model a dependency graph in Laravel

I am stuck modelling the following relationship in a new application:
A Module can require (many) other Modules.
In Laravel I added the Module model and a ModuleRequirement model with its migrations. The second one has a foreign id field for the module it belongs to:
Schema::create('module_requirements', function (Blueprint $table) {
$table->id();
$table->foreignId('module_id')->constrained();
// How to reference other modules?
$table->integer('requires');
});
The relationship itself is defined by:
// Module.php
public function moduleRequirements()
{
return $this->hasMany(ModuleRequirement::class);
}
This method returns a collection of ModuleRequirements as expected. Unfortunately in order to get the real Module objects I need a foreach loop and build my own collection. I wonder if there is a simpler and faster solution. The perfect way I could imagine is sth. like $module->moduleRequirements returning a collection of the actual models instead of its ids.
My understanding that a module has relation with other modules in the same table.
you should use many to many relationship ,in that case you will not need a model for ModuleRequirement just a pivot table would be sufficient.
In your case module_requirements would act as the pivot table and you just need to change the relation in Module.php to :-
public function moduleRequirements(){
return $this->belongsTomany(Module::class,"module_requirements", 'module_id', 'requires');
}
the migration should be something like :-
Schema::create('module_requirements', function (Blueprint $table) {
$table->foreignId('module_id');
$table->foreignId('requires');
$table->foreign('module_id')->references('id')->on('models')->onDelete('cascade');
$table->foreign('requires')->references('id')->on('models')->onDelete('cascade');
});

Creating a Custom ID (Primary Key) in Laravel

I have two tables(models) called Users and Books and need to create custom IDs (primary keys) values for my tables say, u25894r for users and b419k for books. I searched in the internet and stackoverflow a lot but I didn't understand WHERE I should define my custom ID generator function e.g., AppServiceProvider boot function or in Model constructor function. I'd be so appreciate it if you show the way with a little example.
EDIT:
I need to write functions that generate unique custom IDs for the tables.
You can set the custom id name in the Model class. Use
protected $primaryKey = "u25894r";
for user table and then make a migration.
You need to make a change in both the class/model definition (ie protected $primaryKey = "myUniqueName") and also in the migrations file:
public function up()
{
Schema::create('recruiters', function (Blueprint $table) {
// this is in the migration file:
$table->id('recruiter_id');
$table->timestamps();
});
}
you can set that in migration file
laravel documents is powerful for that you should read about migration from this link
https://laravel.com/docs/5.6/migrations

Laravel 5 - defining relationships

everything was working fine with a single Model, but now I am implementing more, I have noticed an issue.
I have several document Models which represent a different type of document. For now, let's say I have DocumentA and DocumentB.
Each Document allows file uploads, so I have created a FileUpload Model. A Document can have many FileUploads.
So, seems pretty straight forward at this point. My FileUpload table has a documentId field, which is a reference to the id field of the Document that is using it.
In DocumentA, I have something like so
public function uploadFile()
{
return $this->hasMany('App\UploadFile', 'documentId');
}
So DocumentA can have many UploadFiles, linked by the documentId.
DocumentB has the same function within its Model.
My problem lies with the UploadFiles model. Firstly, this model now has two belongTo events e.g.
public function documentA()
{
return $this->belongsTo('App\DocumentA', 'documentId');
}
public function documentB()
{
return $this->belongsTo('App\DocumentB', 'documentId');
}
This could be the problem, not sure if I can have multiple belongs to? My immediate problem however is to do with the migration of the doc_file table. At the moment I have this
Schema::table('doc_file', function (Blueprint $table) {
$table->integer('documentId')->unsigned()->default(0);
$table->foreign('documentId')->references('id')->on('document_a')->onDelete('cascade');
});
Schema::table('doc_file', function (Blueprint $table) {
$table->integer('documentId')->unsigned()->default(0);
$table->foreign('documentId')->references('id')->on('document_b')->onDelete('cascade');
});
So I am trying to provide foreign keys to my documents. When I try to migrate, it tells me that
Column already exists: 1060 Duplicate column name documentId
Am I handling my relationships correctly? Essentially, I have many documents, and each document can have many files.
Any assistance with my database relationships appreciated.
Many thanks
Looking at your problem at first glance, it seems that there is a little confusion for you regarding the concept model.
The Model Concept
A model is in fact a conceptualization of a real-world object as it is used to represent a real-world entity.
In other words, it represents a whole class of objects with similar properties. For instance a Car model would represent all cars, whether they are of type Lamborghini or Mercedez. The fact is that they all come under the Car classification.
Same concept goes in Eloquent, and with your use case; therefore a Document model is sufficient to represent both of your documents (DocumentA and DocumentB).
Eloquent Relationships
To refine what you've achieved so far, your models' relationships can be refactored as such:
Document Model
public function fileUploads(){
return $this->hasMany('App\FileUpload');
}
FileUpload Model
public function document(){
return $this->belongsTo('App\Document');
}
Based on the relationship "EACH document has MANY file uploads", and the inverse "EACH file upload BELONGS to exactly one document", as you can see, there is only one belongsTo() method in the FileUpload model to define the latter part of the relationship.
Similarly, the schema for the tables defining the above relationship are as follows:
// Schema for Document table
Schema::table('document', function (Blueprint $table) {
$table->increment('id');
$table->string('name', 100);
});
// Schema for FileUpload table
Schema::table('doc_file', function (Blueprint $table) { // file_uploads would have been a more friendly name in my opinion
$table->integer('documentId')->unsigned()->default(0); // note that `documentId` is interpreted as `documentid` in MySQL
$table->foreign('documentId')->references('id')->on('document')->onDelete('cascade');
});

Laravel Eloquent ORM - Many to Many Delete Pivot Table Values left over

Using Laravel, I have the following code
$review = Review::find(1);
$review->delete();
Review has a many to many relationship defined with a Product entity. When I delete a review, I'd expect it to be detached from the associated products in the pivot table, but this isn't the case. When I run the code above, I still see the linking row in the pivot table.
Have I missed something out here or is this the way Laravel works? I'm aware of the detach() method, but I thought that deleting an entity would also detach it from any related entities automatically.
Review is defined like this:
<?php
class Review extends Eloquent
{
public function products()
{
return $this->belongsToMany('Product');
}
}
Product is defined like this:
<?php
class Product extends Eloquent
{
public function reviews()
{
return $this->belongsToMany('Review');
}
}
Thanks in advance.
The detach method is used to release a relationship from the pivot table, whilst delete will delete the model record itself i.e. the record in the reviews table. My understanding is that delete won't trigger the detach implicitly. You can use model events to trigger a cleanup of the pivot table, though, using something like:
protected static function booted()
{
static::deleting(function ($review) {
$review->product()->detach()
});
}
Also, I would suggest that the relationship would be a one to many, as one product would have many reviews, but one review wouldn't belong to many products (usually).
class Review extends \Eloquent {
public function product()
{
return $this->belongsTo('Product');
}
}
class Product extends \Eloquent {
public function reviews()
{
return $this->hasMany('Review');
}
}
Of course, this would then require that you tweak your database structure. If you wanted to leave the database structure and your current relationships as they are, the other option would be to apply a foreign key constraint on the pivot table, such that when either a review or product is removed, you could cascade the delete on the pivot table.
// Part of a database migration
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
$table->foreign('review_id')->references('id')->on('reviews')->onDelete('cascade');
Edit: In adding the constraint, you push the cleanup work onto the database, and don't have to worry about handling it in code.
Simpler Steps:
In this example an Account has many Tags:
To delete the Tags, then the Account do this:
// delete the relationships with Tags (Pivot table) first.
$account->find($this->accountId)->tags()->detach();
// delete the record from the account table.
$account->delete($this->accountId);
On the Pivot Table make sure you have ->onDelete('cascade');
$table->integer('account_id')->unsigned()->index();
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$table->integer('tag_id')->unsigned()->index();
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
$review->product()->sync([]) also works.
However $review->product()->detach() is much more explicit.
public function destroy($id)
{
$review = Review::find($id);
$review ->tagged_users()->detach();
return $review ->delete();
}
also works
If that way doesn't solve your problem, you can delete Pivot table records to using the DB option like below ;
DB::table('any_pivot_table')->where('x_column', $xParameter)->delete();
You can solve the problem just like this.
Enjoy your coding !
i guess you have a error in your models relationship conception, the product has many reviews but the review associated with one product so you have here one to many relationship.
but in general the answer in the general case will be using:
$product->reviews()->sync([]);

Resources