proper way to create ManyToMany relationship in laravel - php

I just started to work on a laravel project for my school assignment. I just have started it for about a week so my fundamental knowledge about laravel is not complete.
Today I bump into a problem with model many to many relationship in laravel. I create 2 model with migration A and B. In App\A.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class A extends Model
{
//
public function B(){
return $this->belongsToMany('App\B');
}
}
and in App\B.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class B extends Model
{
//
public function A(){
return $this->belongsToMany('App\A');
}
}
I think it should do the job. But when I use seeder to create dummy data, I got the error with is that table A_B is not created. I assume that I must create empty table A_B for 2 pivot columns which is annoying. Is there a better way, a proper way to create many to many relationship without manually create pivot table for them?

I'm afraid not. There are shortcuts to doing it, like the way they show here but you will end up doing some manual work to create the table. You will end up using a migration anyway, but depending on the amount of control you want with the pivot, you might want to use a model for that. Using a model for a pivot table is not mandatory.
https://laravel.com/docs/5.5/migrations#creating-tables
This could be what your migration would end up looking like:
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateABTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('A_B', function (Blueprint $table) {
$table->increments('id');
$table->integer('a_id')->unsigned();
$table->integer('b_id')->unsigned();
$table->foreign('a_id')->references('id')->on('a')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('A_B');
}
}
But if you really wanted to, you could also create a custom table (with a custom name like ABRandomName) using a new model. Just look for the Defining Custom Intermediate Table Models header in the documentation:
https://laravel.com/docs/5.5/eloquent-relationships#many-to-many

Related

Can you delete models referenced in migrations with foreignIdFor()?

I have an old migration where I used the foreignIdFor() method to create a column.
I later decided to delete the model which was referenced and now, when I do a migrate:refresh locally, that migration triggers an error saying that the model doesn't exist, of course.
So, should one never delete models referenced with foreignIdFor() and use unsignedBigInteger()instead ?
EDIT:
I know foreignIdFor and unsignedBigInteger will create the same column. When I say I deleted the model, I mean the model class, not a model.
My migration file :
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use App\Models\Event;
use App\Models\OutlookCategory;
class CreateEventOutlookCategoryTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('event_outlook_category', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(Event::class);
$table->foreignIdFor(OutlookCategory::class);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('event_outlook_category');
}
}
The foreignIdFor method adds a {column}_id UNSIGNED BIGINT equivalent column for a given model class, so using unsignedBigInteger() doesn't make any difference.
Resources:
https://laravel.com/docs/9.x/migrations#column-method-foreignIdFor
https://laravel.com/docs/9.x/migrations#column-method-unsignedBigInteger
What you could/needed to do is to add cascade delete, and make sure that whenever associated model is deleted, all related models are deleted too.
You could do it like this:
table->foreignId('column_name_id')
->constrained()
->onDelete('cascade');
Resources:
https://laravel.com/docs/9.x/migrations#foreign-key-constraints
Answer to the edit:
If you delete Model that was used in foreignIdFor, that method will not be able to understand what you are referencing, and therefore it will fail. So, the answer to your question is YES and NO.
Let me elaborate. If your migrations will be run just once, on the production environment, for example, then you will be able to delete the model you've been referencing in the previous migrations and just create a new migration that will cleanup those columns.
In all other cases, when your migrations will be run for a few times (locally when you use migrate:fresh), you need to have Model that was referenced in them in your code base in order for it to work properly.
If you want to avoid these kind of problems that you are experiencing right now, just use unsignedBigInteger and pass to it string that is the name of the column, and you don't have to worry about deleting the model. However, you will still need to pay attention not to delete the column that is referenced there, because you will get another error for missing column.

Laravel - refactor table names (migration) will Models/Controllers go with it?

I would like to rename a table in the database from topics to galleries and I have created a migration that will rename my table.
Schema::rename('foo', 'bar');
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class RenameTopicsTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
//
Schema::rename('topics', 'galleries');
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
//
Schema::rename('galleries', 'topics');
}
}
However will the Topic Model and Topic Controller be automatically renamed? Or will I have to refactor my code? Does Laravel provide a way to do this easily?
In short my question is - How do you change your schema easily in laravel? (models/controllers/database/requests/transformers ect..)
To answer this question. Laravel does not provide a out-of-the-box way to rename your tables at the same time as your Models/Controllers.
You must manually refactor your code after you change your database schema.
An example of this is lets say i have a posts table and i want to rename it to blogs. Well my posts model and posts controller wont be helpful after i update the schema so i will need to change those over as well. Routes will need to be updated. If I am using views those will need to be updated. In my case i was using transformers and requests so those need to be manually updated.
If you can, try to avoid changing your schema :D

Intermediate Tables & Laravel

I'm kind of new to the Eloquent (Pivot / Intermediate Tables) idea.
I am using Laravel 5.3 and the docs are making a little sense, but not enough. Unfortunatley!
I have a few scenarios that I'd like to try get data from...
I have the following DB Tables
Companies
Company_Offers
Offers
Company_Attributes
Attributes
In my scenarios the following is said of these DB Tables...
A company can have many offers
A company can have many attributes
An Offer can be associated with many companies
An attribute can be associated with many companies
I have created the 5 models to correspond to the 5 DB Tables.
I am trying to work out, How I get these relationships into my models?
Thank You!
You want to use the belongsToMany relationship. For example:
class Company extends Model
{
public function offers()
{
return $this->belongsToMany(App\Offer::class);
}
}
If you have setup your pivot table with company_id and offer_id this relationship will work automatically, and a pivot table of company_offer (singular version of model name in alphabetical order). If you didn't follow the naming convention you can specify the pivot table and foreign keys like so:
return $this->belongsToMany('App\Offer', 'Company_Offers', 'Company_ID', 'Offer_ID');
Actually in laravel you don't have to create models for pivot tables. So you are down to three models that will look more less like this:
<?php
/* /app/Company.php */
namespace App;
use Illuminate\Database\Eloquent\Model;
class Company extends Model
{
/**
* The offers that belong to the company.
*/
public function offers()
{
return $this->belongsToMany('App\Offer');
}
/**
* The attributes that belong to the user.
*/
public function attributes()
{
return $this->belongsToMany('App\Attribute');
}
}
<?php
/* /app/Offer.php */
namespace App;
use Illuminate\Database\Eloquent\Model;
class Offer extends Model
{
public function companies()
{
return $this->belongsToMany('App\Company');
}
}
<?php
/* /app/Attribute.php */
namespace App;
use Illuminate\Database\Eloquent\Model;
class Attribute extends Model
{
public function companies()
{
return $this->belongsToMany('App\Company');
}
}
More on how to use it to select or update those relations you can find here:
https://laravel.com/docs/5.3/eloquent-relationships#many-to-many

'modelName->save' doesn't reference the name of the tabel in the database correctly in laravel

I am trying to learn laravel, and am following a bunch of tutorials
I am trying to save a object I created inside my model to my database table-contact,but when I do the modelObject->save command in tinker it replaces my table name by contacts instead of contact
Now I know about the snake case plural name system in laravel so I explicitly rename my table in the model as follows :
protected $table='contact';
But still I get the same error as
`'base table or view not found **laravel.contacts**'`
Here is my migration :
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateContactTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('contact', function (Blueprint $table) {
$table->increments('id');
$table->text('address');
$table->string('email');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('contact');
}
}
My model which I create like this:
php artisan make:model contact
The model that was created:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class contact extends Model
{
protected $table='contact';
}
note :that protected $table='contact' was manually added by me later
Now I create object in tinker as :
$contact=new App\contact
$contact->address='myaddress'
$contact->email='myemail'
And then try to save the object to the database using
$contact->save
But like I've said before laravel tries to save it to contacts instead of contact table
Also,The object '$contact' doesn't reference the default values of timestamp and id in the model as it does in the tutorial may be someone can hint me why..
I figured what I was I doing wrong ,apparently I have to restart tinker every time I change something in the model class I create. Writing the based object again referencing the App/modelClass doesn't seem to work

Eloquent doesn't get the "belongsTo" item

I have the Project model and the Contract model. When i execute Project:all() it gets me only the projects without the contract, same for contract. I tried to dd() inside contract and doesn't do anything, like is never executed. I also tried with App\ prefix and without.
use Illuminate\Database\Eloquent\Model;
class Project extends Model
{
protected $table = 'project';
public function contract() {
return $this->belongsTo('Contract');
}
}
namespace App;
use Illuminate\Database\Eloquent\Model;
class Contract extends Model
{
protected $table = 'contract';
public function project() {
return $this->hasMany('Project', 'ContractID', 'ContractID');
}
}
I try to retrieve them like this:
$projects = Project::all()->take(10);
You have a few problems here.
Project::all()->take(10);
This only returns a collection of projects. You havent specified that you want the contracts also.
$projects = Project::with('contract')->get();
In your belongsTo - You havent specified the column that the table should join on. You need to do this, because you have not used a standard id for primary key and contract_id for foreign key.
unrelated to specific question, but your relationship in contract model is also wrong.
public function project() {
return $this->hasMany('Project', 'ContractID', 'ContractID');
}
If one contract has many projects, then your public function project() should be public function projects();
Finally - Why are you using non-standard table / column naming conventions? What's wrong with contract_id? Are you aware that mysql is non-case sensitive? Also the project table could be renamed projects and the contract table could be renamed contracts. It will make you writing your eloquent relations much easier and makes more sense!
If you used standard naming conventions, then you could just do this to declare your model relations.
namespace App;
use Illuminate\Database\Eloquent\Model;
class Contract extends Model
{
public function projects() {
return $this->hasMany('Project');
}
}
Notice you dont need to specify the table name in the model, or how the table is related to the Project.

Categories