Estoy corriendo laravel 7 en PHP 7.4 con MySQL 8.0.
Tengo tres tablas, User
, Company
y Department
, con sus respectivos modelos y las fábricas.
He creado una prueba en la que estoy añadiendo la relación:
// MyTest.php
$user = factory(User::class)->create();
$company = factory(Company::class)->make();
$company->user()->associate($user);
$company->create(); // it fails here because of NOT NULL constraint, companies.user_id
$department = factory(Department::class)->make();
$department->company()->associate($company);
$department->create();
Obtuve el siguiente error: Integrity constraint violation: 19 NOT NULL constraint failed: companies.user_id (SQL: insert into "companies" ("updated_at", "created_at") values (2020-03-10 07:27:51, 2020-03-10 07:27:51))
Mi esquema de la tabla se define así:
// users
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('phone');
$table->integer('user_type');
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
// companies
Schema::create('companies', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('name');
$table->string('contact_email');
$table->string('contact_phone');
$table->timestamps();
});
// departments
Schema::create('departments', function (Blueprint $table) {
$table->id();
$table->foreignId('company_id')->constrained()->onDelete('cascade');
$table->string('name');
$table->string('contact_email');
$table->string('contact_phone');
$table->timestamps();
});
Es mi entendimiento de que no debe haber valores NULL en SQL-mesas, por lo que estoy tratando de evitar deliberadamente ->nullable()
en mis migraciones. Especialmente para las claves externas como estos.
EDITAR:
He intentado hacerlo de esta manera, también hice una tabla dinámica para users_companies
. Ahora puedo adjuntar una empresa, pero sigo teniendo un SQL-error cuando se hace la prueba de la siguiente manera:
$user = factory(User::class)->create();
$company = factory(Company::class)->create();
$user->companies()->attach($company);
$company->departments()->create([
'name' => 'Department 1',
'contact_email' => '[email protected]',
'contact_phone' => '123456789',
]);
Esto también se produce el error se indica a continuación:
$company = factory(Company::class)->create();
$company->departments()->save(factory(Department::class)->make());
El error es el siguiente: Integrity constraint violation: 19 NOT NULL constraint failed: departments.company_id (SQL: insert into "departments" ("name", "contact_email", "contact_phone", "company_id", "updated_at", "created_at") values (Department 1, [email protected], '123456789', ?, 2020-03-11 07:59:31, 2020-03-11 07:59:31))
.
CompanyFactory.php
<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\Company;
use Faker\Generator as Faker;
$factory->define(Company::class, function (Faker $faker) {
return [
'name' => 'Company 1',
'contact_email' => '[email protected]',
'contact_phone' => '123456789',
];
});
Suerte
DepartmentFactory.php
<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\Department;
use Faker\Generator as Faker;
$factory->define(Department::class, function (Faker $faker) {
return [
'name' => 'Department 1',
'contact_email' => '[email protected]',
'contact_phone' => '123456789',
];
});
Algunos problemas con su estructura de la tabla son muy claras a primera vista.
- Parece que está tratando de agregar una
user_id
columna a lacompanies
tabla. Esto no es una buena idea, suponiendo que sus empresas tienen más de un empleado. - Si desea utilizar
NOT NULL
columnas, que le define mejor un valor por defecto para cada uno de ellos.
Así que podemos empezar por escribir el migraciones algo como esto, incluyendo tablas dinámicas de las relaciones empresa / usuario y el departamento / usuario:
// companies
Schema::create('companies', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('contact_email')->default('');
$table->string('contact_phone')->default('');
$table->timestamps();
});
// departments
Schema::create('departments', function (Blueprint $table) {
$table->id();
$table->foreignId('company_id')->constrained()->onDelete('cascade');
$table->string('name');
$table->string('contact_email')->default('');
$table->string('contact_phone')->default('');
$table->timestamps();
});
// users
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('name')->default('');
$table->string('phone')->default('');
$table->integer('user_type')->default(0);
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
Schema::create('company_user', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->foreignId('company_id')->constrained()->onDelete('cascade');
});
Schema::create('department_user', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->foreignId('department_id')->constrained()->onDelete('cascade');
});
Ahora tenemos enlaces entre tablas. Un departamento es parte de una empresa; un usuario puede formar parte de varios departamentos y / o múltiples empresas. Esto conduce a las siguientes relaciones :
class User extends Model {
// many-to-many
public function companies() {
return $this->belongsToMany(App\Company::class);
}
// many-to-many
public function departments() {
return $this->belongsToMany(App\Department::class);
}
}
class Company extends Model {
public function departments() {
// one-to-many
return $this->hasMany(App\Department::class);
}
public function users() {
// many-to-many
return $this->belongsToMany(App\User::class);
}
}
class Department extends Model {
public function company() {
// one-to-many (inverse)
return $this->belongsTo(App\Company::class);
}
public function users() {
// many-to-many
return $this->belongsToMany(App\User::class);
}
}
Ahora código como esto debería funcionar:
$user = factory(User::class)->create();
$company = factory(Company::class)->create();
$user->companies()->attach($company);
$company->departments()->create([
'name' => 'Department 1',
'contact_email' => '[email protected]',
'contact_phone' => '123456789',
]);
En concreto, el attach
método se utiliza para la actualización de muchos-a-muchos relaciones, que no parecía haber definido, basado en el diseño original de la tabla.