Загрузка изображений VichUploader во встроенном виде (Symfony 3)

Как я могу успешно встроить форму загрузки запущенного изображения в секретную форму с отношениями OneToOne?

первый пост когда-либо о Stackru, поэтому, пожалуйста, будьте осторожны (и я французский...^^)

У меня есть проект Symfony 3.3, работает отлично. Я использую VichUploaderBundle для загрузки и LiipImagine для рендеринга. В настоящее время НЕТ ПРОБЛЕМЫ для загрузки одного файла изображения! У меня есть страница загрузки, я показываю одну форму загрузки файла изображения, я загружаю файл... он проходит через контроллер, и все в порядке. Файл загружен и перемещен в каталог, на странице отображается новое изображение со всеми фильтрами. Большой!

Моя настоящая проблема возникает при встраивании формы в другую форму.

Я хотел бы встроить свою "форму изображения" в секретную форму. Я начал с создания поля mainImage внутри этой секретной формы. Отношение из классифицированного является OneToOne, и, конечно, оно имеет каскад ="сохраняться" и "удалить".

Но при проверке ничего не сработало, и я получил следующую ошибку на image_name:

--- Возникла исключительная ситуация при выполнении операции "INSERT INTO vehicle_ad" (созданный, обновленный, опубликованный, опубликованный, друзья, приоритет, друзья, приоритет, начальная покупка, дата покупки, цена на продажу, валюта, одометр, тип, текущий измеритель, энергия, коробка передач, mainColor, псевдоним, описание, идентификатор создателя, Vehicle_model_id, vehicle_brand_id, main_image_id, vehicle_id) ЗНАЧЕНИЯ (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)' с параметрами ["2018-01-23 17:15:39", "2018-01-23 17:15:39", 0, ноль, 0, ноль, ноль, 2650, "евро", "километры", 17340, нуль, ноль, ноль, ноль, ноль, 1, 3, 1, ноль, ноль]:

SQLSTATE [23000]: нарушение ограничения целостности: 1048 Le champ

'main_image_id' ne peut être vide (null)

Файл пуст, имя пользователя vichuploader не запущено... и я застрял с ним несколько дней. Я провел много исследований... поэтому любая помощь будет принята с благодарностью.

Я стараюсь дать свой код как можно более четко. Во-первых, что в порядке (загрузка одного изображения):

Класс изображения

<?php
 
namespace Gac\MediasBundle\Entity;
 
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
 
/**
 * Image
 *
 * @ORM\Table(name="image")
 * @ORM\Entity(repositoryClass="Gac\MediasBundle\Repository\ImageRepository")
 * @Vich\Uploadable
 */
class Image
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;
     
    /* ----------------------------------------------------------------------
    ---------   Connection with other entities
    ------------------------------------------------------------------------- */
     
    /**
     * @ORM\ManyToOne(targetEntity="Gac\UserBundle\Entity\User", inversedBy="images")
     * @ORM\JoinColumn(nullable=false)
     */
    private $creator;
     
    /**
     * @ORM\ManyToOne(targetEntity="Gac\AdsBundle\Entity\VehicleAd", inversedBy="images")
     * @ORM\JoinColumn(nullable=true)
     */
    private $vehicleAd;
     
    // -------------------------------------------------------------------------------
 
    /**
     * @var string
     *
     * @ORM\Column(name="imageName", type="string", length=255)
     */
    private $imageName;
 
    /**
     * @Vich\UploadableField(mapping="users_images", fileNameProperty="imageName")
     * @var File
     */
    private $imageFile;
 
    /**
     * @var \DateTime
     *
     * @ORM\Column(name="createdAt", type="datetime")
     * @Assert\DateTime()
     */
    private $createdAt;
 
    /**
     * @var \DateTime
     *
     * @ORM\Column(name="updatedAt", type="datetime")
     * @Assert\DateTime()
     */
    private $updatedAt;
 
    /**
     * @var \DateTime
     *
     * @ORM\Column(name="hiddenAt", type="datetime", nullable=true)
     * @Assert\DateTime()
     */
    private $hiddenAt;
     
     
    public function __construct($thisuser)
    {
        $this->creator = $thisuser;
        $this->createdAt = new \Datetime();
        $this->updatedAt = new \Datetime();
    }
     
    /**
     * Get id
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }
     
    /**
     * Set creator
     *
     * @param \Gac\UserBundle\Entity\User $creator
     *
     * @return Image
     */
    public function setCreator(\Gac\UserBundle\Entity\User $creator)
    {
        $this->creator = $creator;
 
        return $this;
    }
 
    /**
     * Get creator
     *
     * @return \Gac\UserBundle\Entity\User
     */
    public function getCreator()
    {
        return $this->creator;
    }
 
    /**
     * Set imageName
     *
     * @param string $imageName
     *
     * @return Image
     */
    public function setImageName($imageName)
    {
        $this->imageName = $imageName;
 
        return $this;
    }
 
    /**
     * Get imageName
     *
     * @return string
     */
    public function getImageName()
    {
        return $this->imageName;
    }
     
    public function setImageFile(File $image = null)
    {
        $this->imageFile = $image;
 
        // VERY IMPORTANT:
        // It is required that at least one field changes if you are using Doctrine,
        // otherwise the event listeners won't be called and the file is lost
        if ($image) {
            $this->updatedAt = new \DateTime();
        }
    }
     
    /**
     * Get imageFile
     */
    public function getImageFile()
    {
        return $this->imageFile;
    }
 
    /**
     * Set createdAt
     *
     * @param \DateTime $createdAt
     *
     * @return Image
     */
    public function setCreatedAt($createdAt)
    {
        $this->createdAt = $createdAt;
 
        return $this;
    }
 
    /**
     * Get createdAt
     *
     * @return \DateTime
     */
    public function getCreatedAt()
    {
        return $this->createdAt;
    }
 
    /**
     * Set updatedAt
     *
     * @param \DateTime $updatedAt
     *
     * @return Image
     */
    public function setUpdatedAt($updatedAt)
    {
        $this->updatedAt = $updatedAt;
 
        return $this;
    }
 
    /**
     * Get updatedAt
     *
     * @return \DateTime
     */
    public function getUpdatedAt()
    {
        return $this->updatedAt;
    }
 
    /**
     * Set hiddenAt
     *
     * @param \DateTime $hiddenAt
     *
     * @return Image
     */
    public function setHiddenAt($hiddenAt)
    {
        $this->hiddenAt = $hiddenAt;
 
        return $this;
    }
 
    /**
     * Get hiddenAt
     *
     * @return \DateTime
     */
    public function getHiddenAt()
    {
        return $this->hiddenAt;
    }
 
    /**
     * Set vehicleAd
     *
     * @param \Gac\AdsBundle\Entity\VehicleAd $vehicleAd
     *
     * @return Image
     */
    public function setVehicleAd(\Gac\AdsBundle\Entity\VehicleAd $vehicleAd = null)
    {
        $this->vehicleAd = $vehicleAd;
 
        return $this;
    }
 
    /**
     * Get vehicleAd
     *
     * @return \Gac\AdsBundle\Entity\VehicleAd
     */
    public function getVehicleAd()
    {
        return $this->vehicleAd;
    }
}

Тогда связанная ФОРМА ИЗОБРАЖЕНИЯ:

<?php
 
namespace Gac\MediasBundle\Form;
 
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Vich\UploaderBundle\Form\Type\VichImageType;
 
class ImageType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('imageFile',       VichImageType::class, [
                    'label' => false,
                    'required' => false,
                    'allow_delete' => false,
                ]);
    }
     
    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Gac\MediasBundle\Entity\Image'
        ));
    }
 
    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        //return 'gac_mediasbundle_image';
        return 'ImageType';
    }
 
 
}

И контроллер

{% extends 'medias/layout.html.twig' %}
 
{% block bundle_content %}
 
<div class="jumbotron text-center">
    <h1>Testing user's images UPLOAD</h1>
</div>
 
<p>-----------------</p>
{{ form_start(form) }}
{{ form_errors(form) }}
{{ form_row(form) }}
<button type="submit">Upload</button>
{{ form_end(form) }}
<p>-----------------</p>
 
{% for picture in pictures %}
    <div class="row">
        <div class="media">
            <div class="media-left media-middle">
                <img class="media-object" src="{{ picture.imageName | imagine_filter('tiny_test') }}" alt="{{ 'GearsAndCoffee-'~picture.imageName }}" /><br/>
            </div>
            <div class="media-body">
                <h4 class="media-heading">Filter name :</h4>
                <em>tiny_test</em>
            </div>
        </div>
        <div class="media">
            <div class="media-left media-middle">
                <a href="{{ asset('uploads/images/'~picture.imageName) }}" />
                    <img class="media-object" src="{{ picture.imageName | imagine_filter('std_display') }}" alt="{{ 'GearsAndCoffee-'~picture.imageName }}" /><br/>
                </a>
            </div>
            <div class="media-body">
                <h4 class="media-heading">Filter name :</h4>
                <em>std_display</em>
            </div>
        </div>
        <p>
            <a href="{{ path('gac_medias_updatetest', {'id': picture.id}) }}"><strong>UPDATE IMAGE</strong></a>
        </p>
        <p>
            <a href="{{ path('gac_medias_removetest', {'id': picture.id}) }}"><strong>DELETE IMAGE</strong></a>
        </p>
    </div>
    <p>-----------------</p>
{% endfor %}
 
{% endblock bundle_content %}

В качестве информации, конфигурация VichUploader Bundle:

vich_uploader:
    db_driver: orm
    #templating: true
    #twig:       true               
    #form:       true
    mappings:
        users_images:
            uri_prefix:         '%app.path.users_images%'
            upload_destination: '%kernel.root_dir%/../web/uploads/images'
            namer:              vich_uploader.namer_uniqid
            inject_on_load:     true
            delete_on_update:   true
            delete_on_remove:   true

А теперь код того, что не работает в моем приложении:

Классифицированный класс

<?php
 
namespace Gac\AdsBundle\Entity;
 
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
 
/**
 * VehicleAd
 *
 * @ORM\Table(name="vehicle_ad")
 * @ORM\Entity(repositoryClass="Gac\AdsBundle\Repository\VehicleAdRepository")
 * @ORM\HasLifecycleCallbacks
 */
class VehicleAd
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;
     
    /* ----------------------------------------------------------------------
    ---------   Connection with other entities
    ------------------------------------------------------------------------- */
     
    /**
     * @ORM\ManyToOne(targetEntity="Gac\UserBundle\Entity\User")
     * @ORM\JoinColumn(nullable=false)
     */
    private $creator;
     
    /**
     * @ORM\ManyToOne(targetEntity="Gac\VehiclesBundle\Entity\VehicleBrand")
     * @ORM\JoinColumn(nullable=false)
     */
    private $vehicleBrand;
     
    /**
     * @ORM\ManyToOne(targetEntity="Gac\VehiclesBundle\Entity\VehicleModel")
     * @ORM\JoinColumn(nullable=false)
     */
    private $vehicleModel;
     
    /**
     * @ORM\OneToOne(targetEntity="Gac\MediasBundle\Entity\Image", cascade={"persist", "remove"})
     * @ORM\JoinColumn(nullable=false)
     */
    private $mainImage;
     
    // -------------------------------------------------------------------------------
 
    // ---------------- General status and dates for the AD -------------
     
    /**
     * @var \DateTime
     *
     * @ORM\Column(name="createdAt", type="datetime")
     * @Assert\DateTime()
     */
    private $createdAt;
 
    /**
     * @var \DateTime
     *
     * @ORM\Column(name="updatedAt", type="datetime")
     * @Assert\DateTime()
     */
    private $updatedAt;
     
    // ---------------------- AD main content -----------------------
     
    /**
     * @var string
     *
     * @ORM\Column(name="description", type="text", nullable=true)
     * @Assert\Type("string")
     */
    private $description;
     
     
    // ************************* FUNCTIONS ***************************
     
    public function __construct($thisuser)
    {
        $this->creator = $thisuser;
        $this->createdAt = new \Datetime();
        $this->updatedAt = new \Datetime();
    }
     
    /**
     * Change updatedAt field just before ORM update
     *
     * @ORM\PreUpdate
     */
    public function changeUpdatedAt()
    {
        $this->updatedAt = new \Datetime();
    }
     
    /**
     * Get id
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }
 
    /**
     * Set createdAt
     *
     * @param \DateTime $createdAt
     *
     * @return VehicleAd
     */
    public function setCreatedAt($createdAt)
    {
        $this->createdAt = $createdAt;
 
        return $this;
    }
 
    /**
     * Get createdAt
     *
     * @return \DateTime
     */
    public function getCreatedAt()
    {
        return $this->createdAt;
    }
 
    /**
     * Set updatedAt
     *
     * @param \DateTime $updatedAt
     *
     * @return VehicleAd
     */
    public function setUpdatedAt($updatedAt)
    {
        $this->updatedAt = $updatedAt;
 
        return $this;
    }
 
    /**
     * Get updatedAt
     *
     * @return \DateTime
     */
    public function getUpdatedAt()
    {
        return $this->updatedAt;
    }
 
    /**
     * Set creator
     *
     * @param \Gac\UserBundle\Entity\User $creator
     *
     * @return VehicleAd
     */
    public function setCreator(\Gac\UserBundle\Entity\User $creator)
    {
        $this->creator = $creator;
 
        return $this;
    }
 
    /**
     * Get creator
     *
     * @return \Gac\UserBundle\Entity\User
     */
    public function getCreator()
    {
        return $this->creator;
    }
 
    /**
     * Set description
     *
     * @param string $description
     *
     * @return VehicleAd
     */
    public function setDescription($description)
    {
        $this->description = $description;
 
        return $this;
    }
 
    /**
     * Get description
     *
     * @return string
     */
    public function getDescription()
    {
        return $this->description;
    }
     
    /**
     * Set vehicleBrand
     *
     * @param \Gac\VehiclesBundle\Entity\VehicleBrand $vehicleBrand
     *
     * @return VehicleAd
     */
    public function setVehicleBrand(\Gac\VehiclesBundle\Entity\VehicleBrand $vehicleBrand)
    {
        $this->vehicleBrand = $vehicleBrand;
         
        return $this;
    }
 
    /**
     * Get vehicleBrand
     *
     * @return \Gac\VehiclesBundle\Entity\VehicleBrand
     */
    public function getVehicleBrand()
    {
        return $this->vehicleBrand;
    }
     
    /**
     * Set vehicleModel
     *
     * @param \Gac\VehiclesBundle\Entity\VehicleModel $vehicleModel
     *
     * @return VehicleAd
     */
    public function setVehicleModel(\Gac\VehiclesBundle\Entity\VehicleModel $vehicleModel)
    {
        $linkedBrand = $vehicleModel->getModelBrand();
         
        $this->vehicleBrand = $linkedBrand;
        $this->vehicleModel = $vehicleModel;
 
        return $this;
    }
 
    /**
     * Get vehicleModel
     *
     * @return \Gac\VehiclesBundle\Entity\VehicleModel
     */
    public function getVehicleModel()
    {
        return $this->vehicleModel;
    }
 
    /**
     * Set mainImage
     *
     * @param \Gac\MediasBundle\Entity\Image $mainImage
     *
     * @return VehicleAd
     */
    public function setMainImage(\Gac\MediasBundle\Entity\Image $mainImage)
    {
        $this->mainImage = $mainImage;
 
        return $this;
    }
 
    /**
     * Get mainImage
     *
     * @return \Gac\MediasBundle\Entity\Image
     */
    public function getMainImage()
    {
        return $this->mainImage;
    }
}

Конечно, большая, моя секретная форма!

<?php
 
namespace Gac\AdsBundle\Form;
 
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Gac\MediasBundle\Form\ImageType;
use Gac\VehiclesBundle\Entity\VehicleBrand;
use Gac\VehiclesBundle\Entity\VehicleModel;
 
class VehicleAdType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $globalDatas = $options['globalDatas'];
         
        $builder->add('vehicleBrand', EntityType::class, array(
                            'class' => 'GacVehiclesBundle:VehicleBrand',
                            'choice_label' => 'brandName',
                            'placeholder' => 'Please select a brand :',
                        ))
                ->add('vehicleModel', EntityType::class, array(
                            'class' => 'GacVehiclesBundle:VehicleModel',
                            'choice_label' => 'modelName',
                        ))
                ->add('description', TextareaType::class, array('required' => false));
         
        $builder->add('mainImage', ImageType::class);
         
        $formModifier = function (FormInterface $form, VehicleBrand $vehicleBrand = null) {
            $models = null === $vehicleBrand ? array() : $vehicleBrand->getVehicleModels();
 
            $form->add('vehicleModel', EntityType::class, array(
                            'class' => 'GacVehiclesBundle:VehicleModel',
                            'choice_label' => 'modelName',
                            'choices' => $models,
                        ));
        };
         
        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function (FormEvent $event) use ($formModifier) {
                // this would be your entity, i.e. Brand
                $data = $event->getData();
                 
                $formModifier($event->getForm(), $data->getVehicleBrand());
            }
        );
         
        $builder->get('vehicleBrand')->addEventListener(
            FormEvents::POST_SUBMIT,
            function (FormEvent $event) use ($formModifier) {
                $vehicleBrand = $event->getForm()->getData();
                $formModifier($event->getForm()->getParent(), $vehicleBrand);
            }
        );
    }
     
    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Gac\AdsBundle\Entity\VehicleAd',
            'globalDatas' => null,
        ));
    }
 
    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'gac_adsbundle_vehiclead';
    }
 
 
}

Контроллер секретного создания

<?php
 
namespace Gac\AdsBundle\Controller;
 
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Gac\MediasBundle\Entity\Image;
use Gac\MediasBundle\Form\ImageType;
use Gac\AdsBundle\Entity\VehicleAd;
use Gac\AdsBundle\Form\VehicleAdType;
use Gac\VehiclesBundle\Entity\Vehicle;
use Gac\VehiclesBundle\Form\VehicleType;
use Gac\VehiclesBundle\Entity\VehicleModel;
use Gac\VehiclesBundle\Entity\VehicleBrand;
 
class ManageController extends Controller
{  
    public function sellvehicleAction(Request $request)
    {
        $this->denyAccessUnlessGranted('ROLE_USER');
        $thisuser = $this->get('security.token_storage')->getToken()->getUser();
        $em = $this->getDoctrine()->getManager();
         
        // Create new ad and load a "mainImage"
        $classified = new VehicleAd($thisuser);
        $oneImage = new Image($thisuser);
        $classified->setMainImage($oneImage);
         
        $globalDatas = $this->container->get('gac_general.globaldatas');  // Always pass globalDatas VAR to any created form in the application
        $form = $this->createForm(VehicleAdType::class, $classified, array(
            'globalDatas' => $globalDatas,
        ));
         
        $form->handleRequest($request);
        if ($request->isMethod('POST') && $form->isValid()) {
            $em->persist($classified);
            $em->flush();
             
            $id = $classified->getId();
             
            return $this->redirectToRoute('gac_ads_previewvehiclead', array('id' => $id));
        }
         
        return $this->render('ads/manage/sellvehicle.html.twig', array(
            'form' => $form->createView()
        ));
    }
}

И наконец вид моей секретной формы

{% extends 'ads/layout.html.twig' %}
 
{% block bundle_content %}
<form action="{{ path('gac_ads_sellvehicle') }}" method="post">
 
    <div class="row justify-content-center">
        <div class="col-md-8">
            {{ form_errors(form) }}
            <div class="card mb-3">
                <div class="card-body">
                    <p class="text-primary"><strong>{{ 'mandatory informations'|trans|capitalize }} :</strong></p>
                    {{ form_row(form.vehicleBrand, {'label': 'Vehicle Brand :'}) }}
                    {{ form_row(form.vehicleModel, {'label': 'Vehicle Model :'}) }}
                </div>
            </div>
             
            <div class="card mb-3">
                <div class="card-body">
                    <p class="text-primary">{{ 'image'|trans|capitalize }} :</p>
                    <div id="toto">
                        {{ form_row(form.mainImage.imageFile) }}
                    </div>
                </div>
            </div>
             
            <div class="card mb-3">
                <div class="card-body">
                    <p class="text-primary">{{ 'other parameters'|trans|capitalize }} ({{ 'optional'|trans }}) :</p>
                    <p class="text-primary">{{ 'tell us a few words about your vehicle'|trans|capitalize }} ({{ 'optional'|trans }}) :</p>
                    {{ form_row(form.description, {'label': 'Description :'}) }}
                </div>
            </div>
        </div>
        <div class="col-md-4">
            <div class="card mb-3">
                <div class="card-body">
                    {{ form_rest(form) }}
                </div>
            </div>
            <button type="submit" class="btn btn-block btn-primary">{{ 'preview your ad'|capitalize }}</button>
        </div>
    </div>
</form>
{% endblock bundle_content %}

Я должен сказать, что, прежде чем спрашивать, я сделал много поиска самостоятельно (тесты...) и тонны поиска Google. Я знаю, что с Vich Uploader есть некоторые хитрости, но я справился со всем, потому что... он работает для одного файла.

Итак, вопрос: можете ли вы помочь мне сделать мою загрузку и мою форму работоспособными, когда они встроены в другую?

Большое спасибо!

С уважением

1 ответ

Попробуйте сделать это в "секретной" форме: ...add('description', textAreaType::class) ->add('mainImage', ImageType::class); Возможно, использование дважды экземпляра FormBuilder "$builder" вызывает проблемы. В любом случае: см. Здесь Formulaires Imbriqués. Я так и сделал, и он отлично работает со встроенными формами, используя VICH

Другие вопросы по тегам