Repository: laravel-shift/blueprint (php)

Issue:
belongsToMany Foreign UUID not working.
- Laravel Version: 10.10
- PHP Version: 8.1
- Blueprint Version: 2.9
- Platform: Windows

### Issue:
The pivot table on a belongsToMany is not respecting UUID from the related models. Should be **foreignUuid**

![image](https://github.com/laravel-shift/blueprint/assets/1649159/b5585a6c-358b-4bd9-ba4e-551c11845932)


### draft.yaml:
```
models:
  Brand:
    uuid
    name: string index
    slug: string unique index
    logo: string nullable
    softDeletes
    relationships:
      belongsToMany: Product
  Product:
    uuid
    name: string index
    slug: unique index
    price: double:8,2
    softDeletes
    relationships:
      belongsToMany: Brand
```

Pull request description:
Re-apply fixes from #661
This is a rebuild of #661 which closes #659

Patch:
diff --git a/src/Generators/MigrationGenerator.php b/src/Generators/MigrationGenerator.php
index 895fdf1..b8c63a5 100644
--- a/src/Generators/MigrationGenerator.php
+++ b/src/Generators/MigrationGenerator.php
@@ -390,7 +390,9 @@ class MigrationGenerator extends AbstractClassGenerator implements Generator
 
             if (config('blueprint.use_constraints')) {
                 $this->hasForeignKeyConstraints = true;
-                $definition .= $this->buildForeignKey($foreign, $on, 'id') . ';' . PHP_EOL;
+
+                $type = isset($models[$segment]) ? $models[$segment]->idType() : 'id';
+                $definition .= $this->buildForeignKey($foreign, $on, $type) . ';' . PHP_EOL;
             } else {
                 $definition .= $this->generateForeignKeyDefinition($segment, $foreign, $models);
             }
diff --git a/src/Generators/ModelGenerator.php b/src/Generators/ModelGenerator.php
index 9f1e38e..6e7f578 100644
--- a/src/Generators/ModelGenerator.php
+++ b/src/Generators/ModelGenerator.php
@@ -91,6 +91,16 @@ class ModelGenerator extends AbstractClassGenerator implements Generator
                 $phpDoc .= PHP_EOL;
                 $phpDoc .= ' * @property string|null $' . $column->name() . '_type';
                 $phpDoc .= PHP_EOL;
+            } elseif ($column->dataType() === 'ulidMorphs') {
+                $phpDoc .= ' * @property string $' . $column->name() . '_id';
+                $phpDoc .= PHP_EOL;
+                $phpDoc .= ' * @property string $' . $column->name() . '_type';
+                $phpDoc .= PHP_EOL;
+            } elseif ($column->dataType() === 'nullableUlidMorphs') {
+                $phpDoc .= ' * @property string|null $' . $column->name() . '_id';
+                $phpDoc .= PHP_EOL;
+                $phpDoc .= ' * @property string|null $' . $column->name() . '_type';
+                $phpDoc .= PHP_EOL;
             } elseif ($column->dataType() === 'uuidMorphs') {
                 $phpDoc .= ' * @property string $' . $column->name() . '_id';
                 $phpDoc .= PHP_EOL;
diff --git a/src/Models/Model.php b/src/Models/Model.php
index 6ffb29e..207e245 100644
--- a/src/Models/Model.php
+++ b/src/Models/Model.php
@@ -111,6 +111,15 @@ class Model implements BlueprintModel
         return $this->usesPrimaryKey() && $this->columns[$this->primaryKey]->dataType() === 'uuid';
     }
 
+    public function idType(): ?string
+    {
+        if (!$this->usesPrimaryKey()) {
+            return null;
+        }
+
+        return $this->columns[$this->primaryKey]->dataType();
+    }
+
     public function disablePrimaryKey(): void
     {
         $this->primaryKey = false;


Test patch:
diff --git a/tests/Feature/Generators/MigrationGeneratorTest.php b/tests/Feature/Generators/MigrationGeneratorTest.php
index 807036a..f265cc3 100644
--- a/tests/Feature/Generators/MigrationGeneratorTest.php
+++ b/tests/Feature/Generators/MigrationGeneratorTest.php
@@ -245,6 +245,70 @@ final class MigrationGeneratorTest extends TestCase
         $this->assertEquals(['updated' => [$model_migration, $pivot_migration]], $this->subject->output($tree, true));
     }
 
+    #[Test]
+    public function output_also_creates_constraints_for_pivot_table_migration_for_ulids(): void
+    {
+        $this->app->config->set('blueprint.use_constraints', true);
+
+        $this->filesystem->expects('stub')
+            ->with('migration.stub')
+            ->andReturn($this->stub('migration.stub'));
+
+        $now = Carbon::now();
+        Carbon::setTestNow($now);
+
+        $journey_model_migration = str_replace('timestamp', $now->copy()->subSeconds(2)->format('Y_m_d_His'), 'database/migrations/timestamp_create_journeys_table.php');
+        $diary_model_migration = str_replace('timestamp', $now->copy()->subSecond()->format('Y_m_d_His'), 'database/migrations/timestamp_create_diaries_table.php');
+        $pivot_migration = str_replace('timestamp', $now->format('Y_m_d_His'), 'database/migrations/timestamp_create_diary_journey_table.php');
+
+        $this->filesystem->expects('exists')->times(3)->andReturn(false);
+
+        $this->filesystem->expects('put')
+            ->with($journey_model_migration, $this->fixture('migrations/belongs-to-many-key-constraints-using-ulid-columns-journey-model.php'));
+        $this->filesystem->expects('put')
+            ->with($diary_model_migration, $this->fixture('migrations/belongs-to-many-key-constraints-using-ulid-columns-diary-model.php'));
+
+        $this->filesystem->expects('put')
+            ->with($pivot_migration, $this->fixture('migrations/belongs-to-many-pivot-key-constraints-using-ulid-columns.php'));
+
+        $tokens = $this->blueprint->parse($this->fixture('drafts/belongs-to-many-using-ulids.yaml'));
+        $tree = $this->blueprint->analyze($tokens);
+
+        $this->assertEquals(['created' => [$journey_model_migration, $diary_model_migration, $pivot_migration]], $this->subject->output($tree));
+    }
+
+    #[Test]
+    public function output_also_creates_constraints_for_pivot_table_migration_for_uuids(): void
+    {
+        $this->app->config->set('blueprint.use_constraints', true);
+
+        $this->filesystem->expects('stub')
+            ->with('migration.stub')
+            ->andReturn($this->stub('migration.stub'));
+
+        $now = Carbon::now();
+        Carbon::setTestNow($now);
+
+        $journey_model_migration = str_replace('timestamp', $now->copy()->subSeconds(2)->format('Y_m_d_His'), 'database/migrations/timestamp_create_journeys_table.php');
+        $diary_model_migration = str_replace('timestamp', $now->copy()->subSecond()->format('Y_m_d_His'), 'database/migrations/timestamp_create_diaries_table.php');
+        $pivot_migration = str_replace('timestamp', $now->format('Y_m_d_His'), 'database/migrations/timestamp_create_diary_journey_table.php');
+
+        $this->filesystem->expects('exists')->times(3)->andReturn(false);
+
+        $this->filesystem->expects('put')
+            ->with($journey_model_migration, $this->fixture('migrations/belongs-to-many-key-constraints-using-uuid-columns-journey-model.php'));
+        $this->filesystem->expects('put')
+            ->with($diary_model_migration, $this->fixture('migrations/belongs-to-many-key-constraints-using-uuid-columns-diary-model.php'));
+
+        $this->filesystem->expects('put')
+            ->with($pivot_migration, $this->fixture('migrations/belongs-to-many-pivot-key-constraints-using-uuid-columns.php'));
+
+        $tokens = $this->blueprint->parse($this->fixture('drafts/belongs-to-many-using-uuids.yaml'));
+        $tree = $this->blueprint->analyze($tokens);
+
+        $this->assertEquals(['created' => [$journey_model_migration, $diary_model_migration, $pivot_migration]], $this->subject->output($tree));
+    }
+
     #[Test]
     public function output_also_creates_constraints_for_pivot_table_migration(): void
     {
diff --git a/tests/fixtures/drafts/belongs-to-many-using-uuids.yaml b/tests/fixtures/drafts/belongs-to-many-using-uuids.yaml
new file mode 100644
index 0000000..f5100b6
--- /dev/null
+++ b/tests/fixtures/drafts/belongs-to-many-using-uuids.yaml
@@ -0,0 +1,10 @@
+models:
+  Journey:
+    uuid
+    name: string
+    relationships:
+      belongsToMany: Diary
+  Diary:
+    uuid
+    relationships:
+      belongsToMany: Journey
diff --git a/tests/fixtures/drafts/belongs-to-many.yaml b/tests/fixtures/drafts/belongs-to-many.yaml
index 2ba9449..aa08c35 100644
--- a/tests/fixtures/drafts/belongs-to-many.yaml
+++ b/tests/fixtures/drafts/belongs-to-many.yaml
@@ -3,4 +3,4 @@ models:
     name: string
     user_id: id
     relationships:
-      belongstoMany: Diary
+      belongsToMany: Diary
diff --git a/tests/fixtures/migrations/belongs-to-many-key-constraints-using-ulid-columns-diary-model.php b/tests/fixtures/migrations/belongs-to-many-key-constraints-using-ulid-columns-diary-model.php
new file mode 100644
index 0000000..96987a1
--- /dev/null
+++ b/tests/fixtures/migrations/belongs-to-many-key-constraints-using-ulid-columns-diary-model.php
@@ -0,0 +1,31 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::disableForeignKeyConstraints();
+
+        Schema::create('diaries', function (Blueprint $table) {
+            $table->ulid('id')->primary();
+            $table->timestamps();
+        });
+
+        Schema::enableForeignKeyConstraints();
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('diaries');
+    }
+};
diff --git a/tests/fixtures/migrations/belongs-to-many-key-constraints-using-ulid-columns-journey-model.php b/tests/fixtures/migrations/belongs-to-many-key-constraints-using-ulid-columns-journey-model.php
new file mode 100644
index 0000000..a97c396
--- /dev/null
+++ b/tests/fixtures/migrations/belongs-to-many-key-constraints-using-ulid-columns-journey-model.php
@@ -0,0 +1,33 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::disableForeignKeyConstraints();
+
+        Schema::create('journeys', function (Blueprint $table) {
+            $table->ulid('id')->primary();
+            $table->string('name');
+            $table->foreignUlid('user_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate();
+            $table->timestamps();
+        });
+
+        Schema::enableForeignKeyConstraints();
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('journeys');
+    }
+};
diff --git a/tests/fixtures/migrations/belongs-to-many-key-constraints-using-uuid-columns-diary-model.php b/tests/fixtures/migrations/belongs-to-many-key-constraints-using-uuid-columns-diary-model.php
new file mode 100644
index 0000000..fcf0c35
--- /dev/null
+++ b/tests/fixtures/migrations/belongs-to-many-key-constraints-using-uuid-columns-diary-model.php
@@ -0,0 +1,31 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::disableForeignKeyConstraints();
+
+        Schema::create('diaries', function (Blueprint $table) {
+            $table->uuid('id')->primary();
+            $table->timestamps();
+        });
+
+        Schema::enableForeignKeyConstraints();
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('diaries');
+    }
+};
diff --git a/tests/fixtures/migrations/belongs-to-many-key-constraints-using-uuid-columns-journey-model.php b/tests/fixtures/migrations/belongs-to-many-key-constraints-using-uuid-columns-journey-model.php
new file mode 100644
index 0000000..e09a923
--- /dev/null
+++ b/tests/fixtures/migrations/belongs-to-many-key-constraints-using-uuid-columns-journey-model.php
@@ -0,0 +1,28 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::create('journeys', function (Blueprint $table) {
+            $table->uuid('id')->primary();
+            $table->string('name');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('journeys');
+    }
+};
diff --git a/tests/fixtures/migrations/belongs-to-many-pivot-key-constraints-using-ulid-columns.php b/tests/fixtures/migrations/belongs-to-many-pivot-key-constraints-using-ulid-columns.php
new file mode 100644
index 0000000..6344cb0
--- /dev/null
+++ b/tests/fixtures/migrations/belongs-to-many-pivot-key-constraints-using-ulid-columns.php
@@ -0,0 +1,31 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::disableForeignKeyConstraints();
+
+        Schema::create('diary_journey', function (Blueprint $table) {
+            $table->foreignUlid('diary_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate();
+            $table->foreignUlid('journey_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate();
+        });
+
+        Schema::enableForeignKeyConstraints();
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('diary_journey');
+    }
+};
diff --git a/tests/fixtures/migrations/belongs-to-many-pivot-key-constraints-using-uuid-columns.php b/tests/fixtures/migrations/belongs-to-many-pivot-key-constraints-using-uuid-columns.php
new file mode 100644
index 0000000..66c63ef
--- /dev/null
+++ b/tests/fixtures/migrations/belongs-to-many-pivot-key-constraints-using-uuid-columns.php
@@ -0,0 +1,31 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::disableForeignKeyConstraints();
+
+        Schema::create('diary_journey', function (Blueprint $table) {
+            $table->foreignUuid('diary_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate();
+            $table->foreignUuid('journey_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate();
+        });
+
+        Schema::enableForeignKeyConstraints();
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('diary_journey');
+    }
+};