Repository: laravel-shift/blueprint (language: 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
```


Discussion / hints:
Could you pull from `master` and see if it work. A PR was recently merged that may address this...
Thanks Jason, will give it a try and thanks for Blueprint!
sorry, nope, it is still not working, just tried master. :(
Ok. @rcrosbourne is this something your PR might have fixed?
I have found the issue (i think...) there's this method in the **MigrationGenerator** class that passes the type to the **buildForeignKey** method. BUT the type is hard coded, trying to figure out how to correct it:

![image](https://github.com/laravel-shift/blueprint/assets/1649159/1225105f-0ac0-47cc-b5a0-fda294f2dbff)



You definitely aren't pulling from `master` as that code has changed. So it'd be good to ensure you have set your composer constraint to `dev-master` and run `composer update`.

Try again and see if it generates the "correct" code. Also, if not, disable `use_constraints` and see if it does (without the constraints of course).
I switched to dev-master again, but I'm still seeing no uuid on the pivot tables, I still think it has to do with that hardcoded "id" in that method.

![image](https://github.com/laravel-shift/blueprint/assets/1649159/30ccd495-37d0-4de8-ae15-ee0802a2d867)

![image](https://github.com/laravel-shift/blueprint/assets/1649159/82a47502-db86-4eaf-a12d-defa1e0fb08a)

UPDATE:
It worked with the use_constraints set to false:
![image](https://github.com/laravel-shift/blueprint/assets/1649159/db4ecd06-7572-4b69-b747-0d66e045285a)

I guess I can use it like this, thanks so much @jasonmccreary 
The other path should be supported.

@rcrosbourne, seems your PR missed the path for setting foreign UUID/ULID columns with constraints (i.e. `use_constraints` enabled). I'd appreciate if you could fix that path.
Yea I will take a look at it. 
This [PR](https://github.com/laravel-shift/blueprint/pull/661) addresses the issue.
Any news about this?
Nope.
No plan to merge the #661? 
It seems ready. It's not a big problem to edit migrations manually, but if it's fixed...
As noted on the PR, it has some changes requested and like 46 commits which it difficult to review/trust. I plan to give more time to Blueprint after the release of Laravel 11. However, until then I don't have the time to wade through this PR unless the author can clean it up a bit.
I understand, sorry I saw the commits, but didn't have time either to read all the comments. Anyway if this issue is easy to fix (I guess it is), would be opening a new PR just for it, be a solution?  
I welcome any PRs. It's easier on the maintainer if the changes are concise, have a well explained PR description, and contain passing tests.

Fix patch:
diff --git a/src/Generators/MigrationGenerator.php b/src/Generators/MigrationGenerator.php
index d4075db8..6341ebe7 100644
--- a/src/Generators/MigrationGenerator.php
+++ b/src/Generators/MigrationGenerator.php
@@ -380,7 +380,9 @@ protected function buildPivotTableDefinition(array $segments, array $models = []
 
             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 21c78297..6ac94d66 100644
--- a/src/Generators/ModelGenerator.php
+++ b/src/Generators/ModelGenerator.php
@@ -91,6 +91,16 @@ protected function buildClassPhpDoc(Model $model): string
                 $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 d4703b81..f7905f1c 100644
--- a/src/Models/Model.php
+++ b/src/Models/Model.php
@@ -109,6 +109,15 @@ public function usesUuids(): bool
         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 c4330475..18c91cdc 100644
--- a/tests/Feature/Generators/MigrationGeneratorTest.php
+++ b/tests/Feature/Generators/MigrationGeneratorTest.php
@@ -239,6 +239,70 @@ public function output_also_updates_pivot_table_migration(): void
         $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 00000000..f5100b62
--- /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 2ba94499..aa08c35c 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 00000000..96987a13
--- /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 00000000..a97c3967
--- /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 00000000..fcf0c35e
--- /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 00000000..e09a9234
--- /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 00000000..6344cb07
--- /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 00000000..66c63ef1
--- /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');
+    }
+};