Search code examples
phplaraveleloquentlaravel-10laravel-nova

Laravel polymorphic relationship not working in Laravel Nova


I have the following Models and relationships.

App\Subscriber.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Grosv\LaravelPasswordlessLogin\Traits\PasswordlessLogin;

class Subscriber extends Authenticatable
{
    use Notifiable, PasswordlessLogin;

    public function customFieldValues()
    {
        return $this->morphMany(CustomFieldValue::class, 'customFieldValueable');
    }
}

App\CustomField.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use App\Models\Campaign;

class CustomField extends Model
{
    protected $fillable = [
        'name',
        'key',
        'type',
        'field_options'
    ];

    public function customFieldValues()
    {
        return $this->hasMany(CustomFieldValue::class);
    }
}

App\CustomFieldValues.php

<?php

namespace App\Models;

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

class CustomFieldValue extends Model
{
    use HasFactory;

    protected $fillable = ['custom_field_id', 'value', 'custom_field_valueable_id', 'custom_field_valueable_type'];

    public function customField()
    {
        return $this->belongsTo(CustomField::class);
    }

    public function customFieldValueable()
    {
        // If you are using custom column names, specify them here. Otherwise, ensure they match the migration.
        return $this->morphTo(null, 'custom_field_valueable_type', 'custom_field_valueable_id');
    }
}

App\Campaign.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use App\Models\CustomField;

class Campaign extends Model
{
    protected $dates = [
        'imported_at'
    ];

    protected $casts = [
        'imported_at' => 'datetime',
        'exported_at' => 'datetime'
    ];

    protected $fillable = [
        'uuid',
        'name',
        'slug',
        'client_id'
    ];
    
    public function customFieldValues()
    {
        return $this->morphMany(CustomFieldValue::class, 'customFieldValueable');
    }
   }

App\Newsletter.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Newsletter extends Model
{

    protected $fillable = [
        'uuid',
        'name',
        'brand_id',
        'active',
        'create_send_list_id',
        'last_imported'
    ];

    public function brand()
    {
        return $this->belongsTo(Brand::class);
    }

    public function subscribers()
    {
        return $this
            ->belongsToMany(Subscriber::class)
            ->withPivot('active')
            ->withTimestamps();
    }

    public function customFieldValues()
    {
        return $this->morphMany(CustomFieldValue::class, 'customFieldValueable');
    }
}

My migration to create the table to support this looks like this,

<?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('custom_field_values', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('custom_field_id');
            $table->string('value');
            // Manually create the polymorphic columns
            $table->unsignedBigInteger('custom_field_valueable_id');
            $table->string('custom_field_valueable_type');
            // Manually specify a shorter index name
            $table->index(['custom_field_valueable_type', 'custom_field_valueable_id'], 'custom_field_valueable_index');
            $table->timestamps();

            $table->foreign('custom_field_id')->references('id')->on('custom_fields')->onDelete('cascade');
        });


    }

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

However in the Nova interface when I try and view a subscriber I get the following error,

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'custom_field_values.customFieldValueable_type' in 'where clause' (Connection: mysql, SQL: select * from custom_field_values where custom_field_values.customFieldValueable_type = App\Models\Subscriber and custom_field_values.customFieldValueable_id = 1016 and custom_field_values.customFieldValueable_id is not null order by custom_field_values.id desc limit 6 offset 0)

If someone could explain to me where I have gone wrong that would great. I am very new to polymorphic relationships.


Solution

  • the laravel query is looking for a column customFieldValueable_type but you have a custom_field_valueable_type in your db.

    The error seems to be coming from the App\CustomFieldValues.php specifically in the function:

    public function customFieldValueable()
    {
        // If you are using custom column names, specify them here. Otherwise, ensure they match the migration.
        return $this->morphTo(null, 'custom_field_valueable_type', 'custom_field_valueable_id');
    }
    

    The correct naming conventions can be found here in the laravel docs.

    The conventions should be:

    public function customFieldValueable(): MorphTo
    {
        return $this->morphTo(__FUNCTION__, 'custom_field_valueable_type', 'custom_field_valueable_id');
    }
    

    If you want to use the standard naming convention:

    public function customFieldValueable(): MorphTo
    {
        return $this->morphTo();
    }
    

    And then change the db columns name accordingly