filament icon indicating copy to clipboard operation
filament copied to clipboard

Unable to Create Entity When Using HasOne Relationship

Open devadattas opened this issue 2 years ago • 12 comments

Package

filament/filament

Package Version

v2.13.24

Laravel Version

v9.19.0

Livewire Version

v2.10.6

PHP Version

PHP 8.0.19

Bug description

I followed the instructions at https://filamentphp.com/docs/2.x/forms/layout#saving-data-to-relationships to create the following form schema which should save user data while creating the employee and creating the relationship:

class EmployeeResource extends Resource
{
    protected static ?string $model = Employee::class;

    protected static ?string $navigationIcon = 'heroicon-o-collection';

    public static function form(Form $form): Form
    {
        return $form
            ->schema([
                Fieldset::make('Employee Data')
                    ->schema([
                        TextInput::make('employee_code')->required(),
                    ]),
                Fieldset::make('User')
                    ->relationship('user')
                    ->schema([
                        TextInput::make('name')->required(),
                        TextInput::make('email')->email()->required(),
                        TextInput::make('password')->password()->required(),
                    ])
            ]);
    }

But I keep getting this error:

SQLSTATE[HY000]: General error: 1364 Field 'user_id' doesn't have a default value
insert into `employees` (`employee_code`, `updated_at`, `created_at`) values (TEST, 2022-07-08 20:49:42, 2022-07-08 20:49:42)
image

Any ideas on what I am doing wrong here?

Steps to reproduce

  1. Create a model named Employee with hasOne relationship with User model
  2. Create EmployeeResource using make:filament-resource
  3. Add the following form schema to EmployeeResource as per https://filamentphp.com/docs/2.x/forms/layout#saving-data-to-relationships
    public static function form(Form $form): Form
    {
        return $form
            ->schema([
                Fieldset::make('Employee Data')
                    ->schema([
                        TextInput::make('employee_code')->required(),
                    ]),
                Fieldset::make('User')
                    ->relationship('user')
                    ->schema([
                        TextInput::make('name')->required(),
                        TextInput::make('email')->email()->required(),
                        TextInput::make('password')->password()->required(),
                    ])
            ]);
    }
  1. Run php artisan serve
  2. Go to http://127.0.0.1:8000/admin/employees/create and submit
  3. Error showing on screen
SQLSTATE[HY000]: General error: 1364 Field 'user_id' doesn't have a default value
insert into `employees` (`employee_code`, `updated_at`, `created_at`) values (TEST, 2022-07-08 20:49:42, 2022-07-08 20:49:42)
image

Reproduction repository

https://github.com/Uolsen/filament-hasone-bug

Relevant log output

SQLSTATE[HY000]: General error: 1364 Field 'user_id' doesn't have a default value
insert into `employees` (`employee_code`, `updated_at`, `created_at`) values (TEST, 2022-07-08 20:49:42, 2022-07-08 20:49:42)

devadattas avatar Jul 09 '22 13:07 devadattas

The error indicates that Employee belongsTo User, not the other way around. It should be users table that has employee_id in such situation.

employees (hasOne User)
- id
- code

users (belongsTo Employee)
- id
- employee_id [ref: - employees.id]
- email
- password

Filament will first save Employee and then User. This leads to chicken and egg situation, because you are trying to save Employee before User comes into existence, which can't be done. I'm not sure what will happen if you make user_id nullable.

I don't know whether Filament detects belongsTo and inverts saving order or not.

kamilst96 avatar Jul 09 '22 14:07 kamilst96

Actually the requirement is to have an employee as a child of a user i.e. employee belongs to user and user hasOne employee. Any suggestions on how this can be done?

Also, I discovered that if the relationship is created in the database directly the Edit/Update works fine and the issue is only with Creation.

The documentation here https://filamentphp.com/docs/2.x/admin/resources/getting-started#belongsto is giving an example similar to the requirements I have but its not very clear

image

devadattas avatar Jul 09 '22 15:07 devadattas

Hi @devadattas , were you able to resolve this issue?

francisakortia avatar Jul 19 '22 10:07 francisakortia

I am struggling with this also.

filip-paral avatar Jul 27 '22 20:07 filip-paral

Same problem. Even through relation manager it is not working. For BelongsTo it's working fine. But for HasOne it's not.

Uolsen avatar Jul 28 '22 12:07 Uolsen

Has anyone actually tried to debug it? It would really help us out, as there are other issues that need our attention too.

danharrin avatar Jul 28 '22 14:07 danharrin

So, can anyone provide us a reproduction repository URL?

That would allow us to download it and review your bug much easier, so it can be fixed quicker. Please make sure to give us some instructions on where to find the bug, and include a database seeder with everything we need to set it up quickly.

zepfietje avatar Jul 29 '22 10:07 zepfietje

@zepfietje It has been quite some time and I moved on with another work around. I will try to setup a demo of the exact issue sometime early next week for you to review and replicate.

It would be great if anyone else facing this issue freshly can help with this aswell.

devadattas avatar Jul 29 '22 11:07 devadattas

I cannot replicate this issue, it works fine for me. So I really do need that reproduction repo from someone.

Since the OP has managed to work around the issue, @francisakortia, @filip-paral or @Uolsen will need to help provide this.

danharrin avatar Jul 31 '22 11:07 danharrin

Okey, in the evening I will try to make a repo with bug.

Uolsen avatar Jul 31 '22 12:07 Uolsen

Closing this issue as we cannot reproduce it.

Feel free to open a new issue with a reproduction repository, and then we'll have another look.

zepfietje avatar Jul 31 '22 13:07 zepfietje

I will pin link also there:

https://github.com/Uolsen/filament-hasone-bug

In my spare time i will try to find bug. But it will be great if someone more acknowledged will look at it.

Uolsen avatar Aug 01 '22 07:08 Uolsen

Fixed by #3524.

danharrin avatar Aug 16 '22 20:08 danharrin

Experiencing the same error only on creation. Is there any workaround?

n3b0r avatar May 13 '23 22:05 n3b0r

@danharrin @devadattas can you guys advise me on how you solved this issue? Also, let me share some context about my current scenario.

In my case, I create coaches, which need to have a user associated. To create the coach, first, the user should be created, because there is a foreign user_id key on coaches which associates coaches and users in a belongsTo relation. I created only the belongsTo relation on the Coach model, but not the opposite on the user model.

Coaches migration

return new class extends Migration {
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('coaches', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('user_id');
            // (more stuff here)
            //FK
            $table->foreign('user_id')->references('id')->on('users')->onUpdate('cascade')->onDelete('cascade');
        });
    }
    
    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('coaches');
    }
};

Coach model

class Coach extends Model
{
    use HasFactory;
    
    protected $guarded = ['id', 'created_at', 'updated_at'];
    
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}

Coach Filament resource

//(imports rid of)

class CoachResource extends Resource
{
    protected static ?string $model = Coach::class;
    
    
    public static function form(Form $form): Form
    {
        return $form->schema([

            // (coach entity fields rid of)

            Section::make('User profile')
                ->label('User profile')
                ->schema([
                    TextInput::make('email')
                        ->disabled()
                        ->email()
                        ->required()
                        ->maxLength(100),
                    
                    TextInput::make('password')
                        ->label('Password')
                        ->password()
                        ->minLength(8)
                        ->maxLength(255)
                        ->same('password_confirmation')
                        ->disableAutocomplete()
                        ->dehydrateStateUsing(fn($state) => Hash::make($state))
                        ->dehydrated(fn($state) => filled($state))
                        ->required(fn(string $context): bool => $context === 'create'),
                    
                    TextInput::make('password_confirmation')
                        ->label('Password confirmation')
                        ->password()
                        ->nullable()
                        ->minLength(8)
                        ->maxLength(255)
                        ->disableAutocomplete(),
                ])
                ->relationship('user')
                ->columns(3)
        ]);
    }
}

Here is the error, the same issue as initial, this leads to the chicken and egg situation as @kamilst96 described. The user should be created first before storing the new coach. The issue only happens on creation, edition works properly.

user_id_error

BTW, Filament is pretty awesome, thanks for creating something like that!

n3b0r avatar May 14 '23 11:05 n3b0r

BTW, adding nullable to user_id in the table structure seems to work as a workaround.


public function up(): void
    {
        Schema::create('coaches', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('user_id')->nullable();
            //FK
            $table->foreign('user_id')->references('id')->on('users')->onUpdate('cascade')->onDelete('cascade');
        });
    }

n3b0r avatar May 14 '23 11:05 n3b0r