Загрузка изображений 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