Search code examples
phplaravellaravel-9php-8php-8.2

How can if fix this Laravel one to many with a custom foreign key behavior


I'm new to laravel and trying to make two tables with a one (customer) to many(table) relation and a custom Foreign Key tables.customer(I can not change this)

The connection is over customers.id on tables.customer.

After Running php artisan migrate:fresh --seed everything is created as expected. But tables.customer is always 0. I don't get any errors. And both tables are created correctly. What do I miss?

Here are my settings:

Models:

Customers.php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Customers extends Model {
  use HasFactory;

  public function tables() {
    return $this->hasMany(Tables::class, 'customer');
  }

  public $timestamps = false;
}

Tables.php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Tables extends Model {
  use HasFactory;

  public function customers() {
    return $this->belongsTo(Customers::class, 'customer');
  }

  public $timestamps = false;
}

Migration:

customers

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
  /**
   * Run the migrations.
   *
   * @return void
   */
  public function up() {
    Schema::create('customers', function (Blueprint $table) {
      $table->string('id', 6)->primary();
      $table
        ->string('img', 23)
        ->nullable()
        ->default(null);
      $table->tinyText('name');
      $table->tinyInteger('active')->default(1);
      $table->bigInteger('created'); // unix timestamp when created
      $table
        ->bigInteger('status')
        ->nullable()
        ->default(null); // null not deleted / unix timestamp when deleted
    });
  }

  /**
   * Reverse the migrations.
   *
   * @return void
   */
  public function down() {
    Schema::dropIfExists('customers');
  }
};

tables

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
  /**
   * Run the migrations.
   *
   * @return void
   */
  public function up() {
    Schema::create('tables', function (Blueprint $table) {
      $table->string('id', 8)->primary();
      $table->tinyText('number');
      $table->string('customer', 6); // TODO: repalce with uuid
      $table->bigInteger('created'); // unix timestamp when created
      $table
        ->bigInteger('status')
        ->nullable()
        ->default(null); // null not deleted / unix timestamp when deleted
    });
  }

  /**
   * Reverse the migrations.
   *
   * @return void
   */
  public function down() {
    Schema::dropIfExists('tables');
  }
};

Factories:

CustomersFactory.php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Customers>
 */
class CustomersFactory extends Factory {
  /**
   * Define the model's default state.
   *
   * @return array<string, mixed>
   */
  public function definition() {
    return [
      'id' => $this->faker->unique()->regexify('[A-Za-z0-9]{6}'),
      'name' => $this->faker->company(),
      'active' => $this->faker->boolean(),
      'created' => $this->faker->unixTime(),
      'status' => $this->faker->boolean() ? null : $this->faker->unixTime(),
    ];
  }
}

TablesFactory.php

namespace Database\Factories;

use App\Models\Customers;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Tables>
 */
class TablesFactory extends Factory {
  /**
   * Define the model's default state.
   *
   * @return array<string, mixed>
   */
  public function definition() {
    return [
      'id' => $this->faker->unique()->regexify('[A-Za-z0-9]{8}'),
      'number' => $this->faker->unique()->numberBetween(1, 1000),
      'customer' => Customers::factory()->create()->id,
      'created' => $this->faker->unixTime(),
      'status' => $this->faker->boolean() ? null : $this->faker->unixTime(),
    ];
  }
}

Seeders:

customersSeeder.php

namespace Database\Seeders;

use App\Models\Customers;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class CustomersSeeder extends Seeder {
  /**
   * Run the database seeds.
   *
   * @return void
   */
  public function run() {
    Customers::factory()
      ->count(10)
      ->hasTables(20)
      ->create();
  }
}

TablesSeeder.php

namespace Database\Seeders;

use App\Models\Tables;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class TablesSeeder extends Seeder {
  /**
   * Run the database seeds.
   *
   * @return void
   */
  public function run() {
    //
  }
}

DatabaseSeeder.php

namespace Database\Seeders;

// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder {
  /**
   * Seed the application's database.
   *
   * @return void
   */
  public function run() {
    $this->call([CustomersSeeder::class]);
  }
}

Solution

  • Your issue is that you did not tell each model that the id is not an integer, it is by default (check the source code).

    So add this to both models:

    protected $keyType = 'string';
    public $incrementing = false;
    

    Read about that here.