symfony / ux

Symfony UX initiative: a JavaScript ecosystem for Symfony
https://ux.symfony.com/
MIT License
812 stars 295 forks source link

[LiveComponent] Files validation #1739

Open BaptisteChabrol opened 4 months ago

BaptisteChabrol commented 4 months ago

Hello,

I have a problem to validate files with a DynamicForm.

There is my field :

$builder->add('fichierAudio', FileType::class, [
                'label' => 'Fichier audio',
                'required' => false,
                'mapped' => false,
                'help' => 'Merci de mettre un fichier de type mp3, mp4, ogg ou flac de moins de 200Mo auquel cas le fichier ne sera pas téléchargé.',
                'constraints' => [
                    new Assert\File(
                        maxSize: '200M',
                        maxSizeMessage: 'Le fichier est trop lourd. Max : 200Mo',
                        extensions: ['mp3', 'mp4', 'ogg', 'flac'],
                        extensionsMessage: "L'extension du fichier est invalide {{ extension }}, les extensions autorisées sont : {{ extensions }}",
                    )
                ]
            ])

Twig :

<div class="form-group">
    {{ form_label(form.fichierAudio) }}
    <div class="form-widget col-md-6 col-xxl-5">
        {{ form_widget(form.fichierAudio) }}

        <div class="form-text text-muted">
            Fichier téléchargé :
            {% if fichierAudioName %}
                {{ fichierAudioName }}
            {% else %}
                (Aucun)
            {% endif %}
        </div>

        {% if fichierAudioUploadError %}
            <div class="invalid-feedback d-block">{{ fichierAudioUploadError }}</div>
        {% endif %}

        <button data-action="live#action" data-live-action-param="files|uploadFile" class="btn btn-primary">
            Upload le fichier
        </button>
    </div>
    {{ form_help(form.fichierAudio) }}
    {{ form_errors(form.fichierAudio) }}
</div>

If I try to add file with this method, The form doesn't catch the error and the form is submitted. But if I do the constraint in the uploadFile method in my Form like this :

#[LiveAction]
    public function uploadFile(Request $request): void
    {
            $file = $request->files->get('rdv')['fichierAudio'];
            $this->validateFile($file);
            ...
    }

    private function validateFile(UploadedFile $file): void
    {
        $errors = $this->validator->validate($file, [
            new Assert\File(
                maxSize: '200M',
                maxSizeMessage: 'Le fichier est trop lourd. Max : 200Mo',
                extensions: ['mp3', 'mp4', 'ogg', 'flac'],
                extensionsMessage: 'L\'extension du fichier est invalide {{ extension }}, les extensions autorisées sont : {{ extensions }}',
            ),
        ]);

        if (0 === \count($errors)) {
            return;
        }

        $this->fichierAudioUploadError = $errors->get(0)->getMessage();
        throw new UnprocessableEntityHttpException('Validation failed');
    }

I have the validation error as I want but I can't replace the wrong file with another (It disappears from the form) and if the file is correct, the form is submitted si I don't want.

smnandre commented 4 months ago

There may be something you're doing different from the demo (https://ux.symfony.com/demos/live-component/upload) i think ... do you have LiveProp for this file ?

BaptisteChabrol commented 4 months ago

Yes I have LiveProps for my file like in the demo :

    #[LiveProp]
    public ?string $fichierAudioName = null;

    #[LiveProp]
    public ?string $fichierAudioUploadError = null;

The only thing different I can see is that I'm in a symfony form

smnandre commented 4 months ago

File input cannot be handled with other data i think

BaptisteChabrol commented 4 months ago

So there is no solution to add constraints to files with DynamicForm on symfony Form ?

smnandre commented 4 months ago

No, i'm saying you cannot have both Live action handling and standard form handling at the same time i think.

Not 100% sure to see who is who in your code, could you create a small reproducer ?

BaptisteChabrol commented 4 months ago

I reproduced the bug on this repo : https://github.com/BaptisteChabrol/bug_app

BaptisteChabrol commented 4 months ago

Any solutions ?

smnandre commented 4 months ago

I'm not sure because you have an error Warning: Trying to access array offset on null that fails the first two requests.

$file = $request->files->get('rdv')['fichierAudio'];

After that, i think you should reset the form error but you never do cause you return before

        if (0 === \count($errors)) {
            return;
        }

        // ...

Finally, while looking at the debugger, it seems you send two actions at once (upload and save) .. so i'd start there and look step by step what's going on :)

BaptisteChabrol commented 4 months ago

The error Warning: Trying to access array offset on null occurs when you don't choose file before clicking on the upload file button.

I don't understand why I need to reset the form error before this return because if I enter in the if condition, I have no errors on the file validation (it's done like that in the demo but it works)

Plus, when there is an error and I want to upload another file, It doesn't go to the validateFile method (only when I click on the uploadFile button) because the file disappears before

smnandre commented 4 months ago

As i said, I think there are two actions at once. Could you check in your profiler / web debugger ?

BaptisteChabrol commented 4 months ago

If I choose a file, and I click on the upload button, the save action is not called

image

smnandre commented 4 months ago

Could you look what the "_batch" is sending ?

BaptisteChabrol commented 4 months ago

image

I don't see differences when I upload good or wrong file in the step, but if I click on the save button with a good file, I have a value on fichierAudioName :

image