phplaravelfilamentphp

Is there a way to combine two fields and apply validation to them


I have two fields, one is the calling code and the other is the mobile phone number. What I want is to merge them (e.g. +385 95 123 456) and apply validation (PhoneNumber) to it.

I tried to add a third hidden field (contact) and use afterStateUpdated to set it, but the problem is that the hidden field does not display validation messages.

Group::make([
       Select::make('code')
           ->options(fn (PhoneNumberUtil $util) => $util->getSupportedCallingCodes())
           ->label('Country code')
           ->required()
           ->columnSpan(5),

        TextInput::make('number')
           ->rules(['regex:/^[0-9 -]+$/'])
           ->required()
           ->columnSpan(7),
])->columns(12),
  ->afterStateUpdated(fn(Set $set, $state) => $set('contact', $state['code'] . $state['number'])),

Hidden::make('contact')->rules(['required', new \App\Rules\PhoneNumber()]),

Solution

  • You can accomplish this by using the field lifecyle hook mutateStateForValidationUsing() method.

    I don't think this method was documented in the Filamentphp v3 docs, but you can read the method's implementation in the HasState trait.

    The code below shows how you would use the mutateStateForValidationUsing() to solve your problem.

    Group::make([
       Select::make('code')
           ->options([
               '+244' => 'Angola (+244)',
               '+375' => 'Belarus (+375)',
               '+1' => 'Canada (+1)'
             ])
            ->live()
            ->label('Country code')
            ->required()
            ->columnSpan(5),
        
       TextInput::make('phone')
            ->disabled(fn (Get $get): bool => !$get('code'))
            ->tel()
            ->telRegex('/^(?:(?:\+?\d{1,3})?\s*(?:\(\d{1,4}\)|\d{1,4})\s*)?\d{6,15}$/')
         // ->rules(['required', new \App\Rules\PhoneNumber()]) // you can use rules() for custom validation, remember to remove telRegex()
            ->mutateStateForValidationUsing(fn (Get $get, string $state) => $get('code') . $state)
            ->required()
            ->columnSpan(7),
    ])->columns(12),
    

    Explanation: I am disabling the phone/number TextInput field to force the users to select the country code first. This ensures that the user has selected the country code which we will need early on to validate with their provided phone number.

    Upon selecting a country code the phone TextInput field is enabled. The user enters their phone number. When the submit button is clicked, FilamentPHP calls the mutateStateForValidationUsing() which concatenates the selected country code with the phone/number field value and updates the phone field's value before regex validation is run on the field.

    If you plan to store the country code & phone number together as a single field. you can add ->dehydrated(false) to the country code. This will remove code field from the returned data.

    Reference.