Bonjour,
Dans un projet Symfony, je tente d'uploader plusieurs photos depuis un formulaire en utilisant vichUloaderBundle et LiipBundle.
Voici la fonction dans mon controller permettant d'afficher et de traiter le formulaire :
/**
* Creating and updating advert photos
*
* @Route("/media/advert_photos/create/{id}", name="media.advert_photos.create")
*
* @param Advert $advert
* @param Request $request
* @param EntityManagerInterface $manager
*
* @return Response
*/
public function photosForm(Advert $advert, Request $request, EntityManagerInterface $manager): Response
{
$recordedPhotos = $advert->getPhotos();
$numberRecordedPhotos = count($recordedPhotos);
$editMode = false;
$current_menu = 'add_advert';
if ($numberRecordedPhotos > 0)
{
$editMode = true;
$current_menu = 'dashbord';
}
$form = $this->createForm(PhotosAdvertType::class, $advert);
$form->handleRequest($request);
if($form->isSubmitted())
{
\dump($form->isValid());
\dump($advert->getPhotos());
}
if($form->isSubmitted() && $form->isValid())
{
$photos = $advert->getPhotos();
$numberPhotos = $photos->count();
$checkedMain = 0;
if ($numberPhotos > 0)
{
foreach ($photos as $photo)
{
$photo->setAdvert($advert);
if ($photo->getMainPhoto())
{
$checkedMain++;
}
}
}
if ($numberPhotos > 0 && $checkedMain == 0)
{
$error = new FormError("At least one photo must be checked as the main: it will be displayed in the list of adverts.");
$form->addError($error);
}
elseif ($checkedMain > 1)
{
$error = new FormError("There can only be one photo checked as the main.");
$form->addError($error);
}
$manager->persist($advert);
$manager->flush();
if ($editMode)
{
$this->addFlash('success', 'The photos have been successfully updated.');
}
else
{
$this->addFlash('success', 'Photos have successfully been added to your advert.');
}
return $this->redirectToRoute('advert.periods.create', array('id' => $advert->getId()));
}
return $this->render('advert/photosCreation.html.twig', [
'form' => $form->createView(),
'recordedPhotos' => $recordedPhotos,
'bodyId' => 'photosCreation',
'editMode' => $editMode,
'current_menu' => $current_menu
]
)
;
}
Voici mon formulaire principal :
<?php
namespace App\Form\advert;
use App\Entity\advert\Advert;
use App\Form\media\PhotoType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
class PhotosAdvertType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('photos', CollectionType::class, array(
'entry_type' => PhotoType::class,
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'required' => false,
'constraints' => array(new Valid()),
'label' => false
)
)
->add('deletedPhotos', HiddenType::class, array('mapped' => false))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'data_class' => Advert::class,
'translation_domain' => 'forms'
]
)
;
}
}
Voici le formulaire embarqué pour chaque photo ajoutée à la volée via du javascript :
<?php
namespace App\Form\media;
use App\Entity\media\Photo;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\File;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
class PhotoType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('file', FileType::class, array(
'label' => false,
'required' => true,
'constraints' => array(new File(),),
)
)
;
if(! $options['profilePhoto'])
{
$builder->add('mainPhoto', CheckboxType::class);
}
$builder->add('name', HiddenType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Photo::class,
'profilePhoto' => false,
'translation_domain' => 'forms',
]);
}
}
Voici le template appelé dans mon controller :
{% extends 'base.html.twig' %}
{% block title %}
Roadtripr - Management of your vehicle photos
{% endblock %}
{% block body %}
<div class="container pb-5">
<div class="row">
<div class="col">
<h1>Add a vehicule</h1>
{{ include('_messages.html.twig') }}
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-8 order-2">
<h2>Photos of your vehicule</h2>
<p>Please add as many pictures as you want here. Don't forget to specify which one you want to be seen in the search results.</p>
{{ form_start(form) }}
<ul class= "photos" data-prototype= " {{ form_widget ( form.photos.vars.prototype )| e ( 'html_attr' ) }} " >
{% set i = 0 %}
{% if editMode %}
{% for photo in form.photos %}
{{ include('advert/_photoCreation.html.twig') }}
{% set i = i + 1 %}
{% endfor %}
{% endif %}
</ul>
{{ form_row(form.deletedPhotos) }}
<div class="text-left">
<a href="#" class="btn btn-secondary" id="add_photo_link">Add a photo</a>
</div>
<div class="text-right">
<button type="submit" class="btn btn-primary">Next →</button>
</div>
<div class="float-left">
<button type="submit" class="btn btn-link">← Previous</button>
</div>
{{ form_end(form) }}
</div>
<div class="col-md-4 order-1">
<ul class="timeline">
<li class="timeline-down">
<h5 class="mb-1">Step 1</h5>
<p>Describe your vehicule</p>
</li>
<li class="timeline-down">
<h5 class="mb-1">Step 2</h5>
<p>Technical informations of your vehicule</p>
</li>
<li class="timeline-active">
<h5 class="mb-1">Step 3</h5>
<p>Photos of your vehicule</p>
</li>
</ul>
</div>
</div>
</div>
</div>
{% block javascripts %}
{{ parent() }}
{{ encore_entry_script_tags('photosCreation') }}
{% endblock %}
{% endblock %}
Et enfin, voici le template imbriqué :
<li id="li_photo_{{ recordedPhotos[i].id }}">
<div style='display: none'>
{{ form_row(photo.file) }}
</div>
<img id="photo_{{ recordedPhotos[i].id }}" src="{{ vich_uploader_asset(recordedPhotos[i], 'file') | imagine_filter('thumb') }}"
width="100" alt="Photo{{ recordedPhotos[i].id }}">
{{ form_row ( photo.mainPhoto ) }}
{{ form_row ( photo.name ) }}
<a href="{{ path('media.photo.delete', {id: recordedPhotos[i].id}) }}" class="btn btn-danger" data-link-type="photoRemoving"
data-creation="no-dynamically" data-redirection="{{ path('media.advert_photos.create', {id: recordedPhotos[i].advert.id}) }}">
Remove this photo
</a>
</li>
Lorsque je soumets le formulaire contenant plusieurs photos, j'obtiens le message d'erreur suivant :
An exception has been thrown during the rendering of a template ("Parameter "path" for route "liip_imagine_filter" must match ".+" ("" given) to generate a corresponding URL.").
En cherchant, j'ai compris que mon formulaire est considéré comme invalide, car, dans mon controller, le code suivant :
if($form->isSubmitted())
{
\dump($form->isValid());
\dump($advert->getPhotos());
}
me donne false pour la première ligne.
Cependant, je ne comprends pas pourquoi.
Quelqu'un aurait une idée?
Merci d'avance pour votre aide!