dustin10 / VichUploaderBundle

A simple Symfony bundle to ease file uploads with ORM entities and ODM documents.
MIT License
1.85k stars 519 forks source link

How to get/set mapping for OneToMany? #1464

Closed craigh closed 1 month ago

craigh commented 2 months ago
Q A
Bundle version 2.4.0
Symfony version 7.1.4
PHP version 8.2.18

Support Question

I have a pseudo-CMS application that allows the site admin to define 'dynamic' questions that a user can provide a response on a form. I'm trying to extend this to allow the definition of an upload field (using this library).

The User has a OneToMany relation to the QuestionResponse. The QuestionResponse has a ManyToOne relation with Question.

When the form is generated, Questions are not created as a Collection, but rather defined individually through a form listener. The Form fields are named something like custom_question_<id>_response. Then magic __get() and __set() methods are used to set the values in the OneToMany QuestionResponse entity.

In my QuestionResponse entity, I have taken the expected steps to define the entity as Uploadable and a $file and $fileName property with getters and setters. When the form is generated, it is using an extension of VichFileType Form.

#[ORM\Table(name: 'question_reponse')]
#[Vich\Uploadable]
class QuestionResponse
{
    //...

    #[ORM\ManyToOne(targetEntity: Question::class)]
    protected ?Question $question = null;

    #[Vich\UploadableField(mapping: 'custom_file', fileNameProperty: 'fileName')]
    protected ?File $file = null;

    #[ORM\Column(nullable: true)]
    protected ?string $fileName = null;

When the form is generated, it cannot find the mapping for the field. I think because it never finds it when generating the metadata in the first place. Therefore I get an error like so:

Vich\UploaderBundle\Exception\MappingNotFoundException:
Mapping not found for field "custom_question_51_response"

  at vendor/vich/uploader-bundle/src/Storage/AbstractStorage.php:126
  at Vich\UploaderBundle\Storage\AbstractStorage->getFilename(object(BoardMember), 'custom_question_51_response', null)
     (vendor/vich/uploader-bundle/src/Storage/AbstractStorage.php:88)
  at Vich\UploaderBundle\Storage\AbstractStorage->resolveUri(object(BoardMember), 'custom_question_51_response', null)
     (vendor/vich/uploader-bundle/src/Storage/FlysystemStorage.php:84)
  at Vich\UploaderBundle\Storage\FlysystemStorage->resolveUri(object(BoardMember), 'custom_question_51_response')
     (vendor/vich/uploader-bundle/src/Form/Type/VichFileType.php:86)
  at Vich\UploaderBundle\Form\Type\VichFileType->Vich\UploaderBundle\Form\Type\{closure}(object(PreSetDataEvent), 'form.pre_set_data', object(EventDispatcher))
     (vendor/symfony/event-dispatcher/EventDispatcher.php:206)
  at Symfony\Component\EventDispatcher\EventDispatcher->callListeners(array(object(Closure)), 'form.pre_set_data', object(PreSetDataEvent))
…etc …etc …etc

I looked into various solutions like overriding the AnnotationDriver class to load the metadata in a different manner, but couldn't do that. I also looked at adding another driver to add the additional fields, but I don't think this is possible.

It may be that what I am attempting to do is impossible, but I thought I would post here to see if you had any ideas.

thanks!

garak commented 2 months ago

I would suggest that you avoid using entities in your forms. I understand it could be a big paradigm shift, but using entities for forms is just causing big headaches, mainly in complex business logic. Use some kind of data transfer object (DTO), apply your validation there and, if everything is valid, build your entity.

craigh commented 2 months ago

I do indeed use DTOs in many instances so I am not unaware of the benefits of doing so. In this case, the legacy code is using the entity in the form, but I do not see how you have connected validation to the problem? Could you please explain further?

garak commented 2 months ago

The question is quite simple (in theory, at least): the entity should be always valid, from the moment you construct it. This implies you can't link a form to an entity, but you need to link it to a DTO instead. As a positive side-effect, you solve a lot of problems related to the possible different ways to build the same entity (so, with possible different original data), because you can just create as many DTOs as needed.

craigh commented 2 months ago

I do appreciate your taking the time to respond. As I said, I understand that a DTO is a good practice. In this case, I don't think that alone will be a solution. It would not remove the dynamic nature of the form/fields. Perhaps if you explained how using a DTO would address the issue of not finding the mapping I would understand better.

garak commented 2 months ago

The key point is that you can make your DTO Uploadable, just mapping one of its properties with UploadableField. This will allow you to simplify the Vich mapping, and, once you get your UploadedFile from the bundle, you can easily instantiate your entity by passing it in its constructor.

This will imply a minor duplication, since your entity will still need to be mapped, but it should simplify the form handling.

craigh commented 1 month ago

OK, so I have implemented a DTO for my use case. It didn't change anything. I still get the same error.

Do you have any more ideas here?

FYI - I am on symfony slack as craigh also - feel free to ping me there if you wish.

garak commented 1 month ago

I'm afraid it's not easy to help you more without seeing some code. I'd love to see it directly with you on slack, but - you know - time is a constraint