Repository: laravel-shift/blueprint (php)

Issue:
UUID column generates a reference when `use_constraints` config option is enabled
<!-- PLEASE COMPLETE THIS TEMPLATE -->

* Laravel Version: 8.22.1
* PHP Version: 8.0
* Blueprint Version: 1.20.2
* Platform: Mac

### Issue:
If I name a UUID column to 'uuid' Blueprint generates a foreign key to a table that doesn't exist.


### draft.yaml:
```yaml
models:
  Vat:
    uuid: uuid
```

Expected outcome
```php
        Schema::create('vats', function (Blueprint $table) {
            $table->id();
            $table->uuid('uuid');
            $table->timestamps();
        });
```

Blueprint generates a foreign key to a table that doesn't exist:
```php
        Schema::create('vats', function (Blueprint $table) {
            $table->id();
            $table->uuid('uuid');
            $table->foreign('uuid')->references('uuid')->on('uuids')->onDelete('cascade');
            $table->timestamps();
        });
```

Pull request description:
Do not generate foreign key for simple UUID column
This PR fixes an issue reported where `uuid` columns generated a foreign key relationships. In the process of solving this, I also added a feature where `uuid` columns with a _conventional_ name automatically creates the relationship.

For example:

```yaml
models:
  Subscription:
    product_id: uuid
```

Creates a `belongsTo` relationship for `Product`.

---
Closes #417

Patch:
diff --git a/src/Generators/MigrationGenerator.php b/src/Generators/MigrationGenerator.php
index ca2f6a9..a9f55ad 100644
--- a/src/Generators/MigrationGenerator.php
+++ b/src/Generators/MigrationGenerator.php
@@ -400,6 +400,7 @@ class MigrationGenerator implements Generator
             return true;
         }
 
-        return in_array($column->dataType(), ['id', 'uuid']) && config('blueprint.use_constraints');
+        return config('blueprint.use_constraints')
+            && ($column->dataType() === 'id' || $column->dataType() === 'uuid' && Str::endsWith($column->name(), '_id'));
     }
 }
diff --git a/src/Lexers/ModelLexer.php b/src/Lexers/ModelLexer.php
index 59a9290..e7378ad 100644
--- a/src/Lexers/ModelLexer.php
+++ b/src/Lexers/ModelLexer.php
@@ -187,7 +187,10 @@ class ModelLexer implements Lexer
                 return collect($modifier)->containsStrict('foreign') || collect($modifier)->has('foreign');
             })->flatten()->first();
 
-            if (($column->name() !== 'id') && ($column->dataType() === 'id') || $foreign) {
+            if (($column->name() !== 'id' && $column->dataType() === 'id')
+                || ($column->dataType() === 'uuid' && Str::endsWith($column->name(), '_id'))
+                || $foreign
+            ) {
                 $reference = $column->name();
 
                 if ($foreign && $foreign !== 'foreign') {


Test patch:
diff --git a/tests/Feature/BlueprintTest.php b/tests/Feature/BlueprintTest.php
index 74fa2b3..35682bf 100644
--- a/tests/Feature/BlueprintTest.php
+++ b/tests/Feature/BlueprintTest.php
@@ -130,6 +130,7 @@ class BlueprintTest extends TestCase
                 'Person' => [
                     'id' => 'uuid primary',
                     'timestamps' => 'timestamps',
+                    'company_id' => 'uuid',
                 ],
             ],
         ], $this->subject->parse($blueprint));
diff --git a/tests/Feature/Generators/MigrationGeneratorTest.php b/tests/Feature/Generators/MigrationGeneratorTest.php
index 4d5af5d..f915341 100644
--- a/tests/Feature/Generators/MigrationGeneratorTest.php
+++ b/tests/Feature/Generators/MigrationGeneratorTest.php
@@ -780,6 +780,64 @@ class MigrationGeneratorTest extends TestCase
         $this->assertEquals(['created' => [$post_migration, $user_migration, $image_migration]], $this->subject->output($tree));
     }
 
+    /**
+     * @test
+     */
+    public function output_does_not_generate_relationship_for_uuid()
+    {
+        $this->app->config->set('blueprint.use_constraints', true);
+
+        $this->files->expects('stub')
+            ->with('migration.stub')
+            ->andReturn($this->stub('migration.stub'));
+
+        $now = Carbon::now();
+        Carbon::setTestNow($now);
+
+        $timestamp_path = 'database/migrations/' . $now->format('Y_m_d_His') . '_create_vats_table.php';
+
+        $this->files->expects('exists')
+            ->with($timestamp_path)
+            ->andReturn(false);
+
+        $this->files->expects('put')
+            ->with($timestamp_path, $this->fixture('migrations/uuid-without-relationship.php'));
+
+        $tokens = $this->blueprint->parse($this->fixture('drafts/uuid-without-relationship.yaml'));
+        $tree = $this->blueprint->analyze($tokens);
+
+        $this->assertEquals(['created' => [$timestamp_path]], $this->subject->output($tree));
+    }
+
+    /**
+     * @test
+     */
+    public function output_generates_constraint_for_uuid()
+    {
+        $this->app->config->set('blueprint.use_constraints', true);
+
+        $this->files->expects('stub')
+            ->with('migration.stub')
+            ->andReturn($this->stub('migration.stub'));
+
+        $now = Carbon::now();
+        Carbon::setTestNow($now);
+
+        $timestamp_path = 'database/migrations/' . $now->format('Y_m_d_His') . '_create_people_table.php';
+
+        $this->files->expects('exists')
+            ->with($timestamp_path)
+            ->andReturn(false);
+
+        $this->files->expects('put')
+            ->with($timestamp_path, $this->fixture('migrations/uuid-shorthand-constraint.php'));
+
+        $tokens = $this->blueprint->parse($this->fixture('drafts/uuid-shorthand.yaml'));
+        $tree = $this->blueprint->analyze($tokens);
+
+        $this->assertEquals(['created' => [$timestamp_path]], $this->subject->output($tree));
+    }
+
     public function modelTreeDataProvider()
     {
         return [
@@ -797,6 +855,7 @@ class MigrationGeneratorTest extends TestCase
             ['drafts/disable-auto-columns.yaml', 'database/migrations/timestamp_create_states_table.php', 'migrations/disable-auto-columns.php'],
             ['drafts/uuid-shorthand.yaml', 'database/migrations/timestamp_create_people_table.php', 'migrations/uuid-shorthand.php'],
             ['drafts/uuid-shorthand-invalid-relationship.yaml', 'database/migrations/timestamp_create_age_cohorts_table.php', 'migrations/uuid-shorthand-invalid-relationship.php'],
+            ['drafts/uuid-without-relationship.yaml', 'database/migrations/timestamp_create_vats_table.php', 'migrations/uuid-without-relationship.php'],
             ['drafts/unconventional-foreign-key.yaml', 'database/migrations/timestamp_create_states_table.php', 'migrations/unconventional-foreign-key.php'],
             ['drafts/resource-statements.yaml', 'database/migrations/timestamp_create_users_table.php', 'migrations/resource-statements.php'],
             ['drafts/enum-options.yaml', 'database/migrations/timestamp_create_messages_table.php', 'migrations/enum-options.php'],
diff --git a/tests/fixtures/drafts/model-relationships.yaml b/tests/fixtures/drafts/model-relationships.yaml
index d2ea5c7..bb225a9 100644
--- a/tests/fixtures/drafts/model-relationships.yaml
+++ b/tests/fixtures/drafts/model-relationships.yaml
@@ -1,6 +1,7 @@
 models:
   Subscription:
     user_id: id
+    product_id: uuid
     relationships:
       belongsToMany: Team
       hasMany: Order
diff --git a/tests/fixtures/drafts/uuid-shorthand.yaml b/tests/fixtures/drafts/uuid-shorthand.yaml
index ddd12b1..17e2822 100644
--- a/tests/fixtures/drafts/uuid-shorthand.yaml
+++ b/tests/fixtures/drafts/uuid-shorthand.yaml
@@ -1,4 +1,5 @@
 models:
   Person:
     uuid
+    company_id: uuid
     timestamps
diff --git a/tests/fixtures/drafts/uuid-without-relationship.yaml b/tests/fixtures/drafts/uuid-without-relationship.yaml
new file mode 100644
index 0000000..b341595
--- /dev/null
+++ b/tests/fixtures/drafts/uuid-without-relationship.yaml
@@ -0,0 +1,3 @@
+models:
+  Vat:
+    uuid: uuid
diff --git a/tests/fixtures/migrations/uuid-shorthand-constraint.php b/tests/fixtures/migrations/uuid-shorthand-constraint.php
new file mode 100644
index 0000000..7cd790d
--- /dev/null
+++ b/tests/fixtures/migrations/uuid-shorthand-constraint.php
@@ -0,0 +1,37 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreatePeopleTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::disableForeignKeyConstraints();
+
+        Schema::create('people', function (Blueprint $table) {
+            $table->uuid('id')->primary();
+            $table->uuid('company_id');
+            $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade');
+            $table->timestamps();
+        });
+
+        Schema::enableForeignKeyConstraints();
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('people');
+    }
+}
diff --git a/tests/fixtures/migrations/uuid-shorthand.php b/tests/fixtures/migrations/uuid-shorthand.php
index ecc5e0c..0ee3943 100644
--- a/tests/fixtures/migrations/uuid-shorthand.php
+++ b/tests/fixtures/migrations/uuid-shorthand.php
@@ -15,6 +15,7 @@ class CreatePeopleTable extends Migration
     {
         Schema::create('people', function (Blueprint $table) {
             $table->uuid('id')->primary();
+            $table->uuid('company_id');
             $table->timestamps();
         });
     }
diff --git a/tests/fixtures/migrations/uuid-without-relationship.php b/tests/fixtures/migrations/uuid-without-relationship.php
new file mode 100644
index 0000000..521fdef
--- /dev/null
+++ b/tests/fixtures/migrations/uuid-without-relationship.php
@@ -0,0 +1,32 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreateVatsTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('vats', function (Blueprint $table) {
+            $table->id();
+            $table->uuid('uuid');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('vats');
+    }
+}
diff --git a/tests/fixtures/models/model-relationships-laravel8.php b/tests/fixtures/models/model-relationships-laravel8.php
index f75d93d..be90a57 100644
--- a/tests/fixtures/models/model-relationships-laravel8.php
+++ b/tests/fixtures/models/model-relationships-laravel8.php
@@ -16,6 +16,7 @@ class Subscription extends Model
      */
     protected $fillable = [
         'user_id',
+        'product_id',
     ];
 
     /**
@@ -53,4 +54,9 @@ class Subscription extends Model
     {
         return $this->belongsTo(\App\User::class);
     }
+
+    public function product()
+    {
+        return $this->belongsTo(\App\Product::class);
+    }
 }