Вот и все, у меня проблема с загрузкой фото в форму.
Я могу сохранить свою форму с помощью Doctrine в SQL, но когда я хочу сохранить одно или несколько изображений с помощью VichUploaderBundle, это сообщение об ошибке появляется:
Не удалось определить тип доступа к свойству «фотографии» в классе
«MO \ WebAppBundle \ Entity \ WebApp».
Вот мой код:
Моя первая сущность:
<?php
namespace MO\WebAppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* Photo
*
* @ORM\Table()
* @ORM\Entity(repositoryClass="MO\WebAppBundle\Repository\PhotoRepository")
* @Vich\Uploadable
*/
class Photo
{
/**
* @ORM\ManyToOne(targetEntity="MO\WebAppBundle\Entity\WebApp")
* @ORM\JoinColumn(nullable=false)
*/
private $travel;
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(type="text")
*/
private $image;
/**
* @ORM\Column(type="text")
*/
private $imagenom;
/**
* @ORM\Column(type="datetime")
*
* @var \Datetime
*/
private $updatedAt;
/**
* @ORM\Column(type="text")
*/
private $imagedescription;
/**
* @Vich\UploadableField(mapping="photo_images", fileNameProperty="image")
* @var File
*/
private $imageFile;
/**
* @ORM\ManyToOne(targetEntity="WebApp", inversedBy="photos")
*/
private $webapp;
/**
* Set travel
*
* @param \MO\WebAppBundle\Entity\WebApp $travel
*
* @return Photo
*/
public function setTravel(\MO\WebAppBundle\Entity\WebApp $travel)
{
$this->travel = $travel;
return $this;
}
/**
* Get travel
*
* @return \MO\WebAppBundle\Entity\WebApp
*/
public function getTravel()
{
return $this->travel;
}
/**
* Get id
*
* @return int
*/
public function getId()
{
return $this->id;
}public function setImage($image)
{
$this->image = $image;
}
public function getImage()
{
return $this->image;
}public function getImageFile()
{
return $this->imageFile;
}
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) {
// if 'updatedAt' is not defined in your entity, use another property
$this->updatedAt = new \DateTime('now');
}
}
/**
* @return mixed
*/
public function getWebApp()
{
return $this->webapp;
}
/**
* @param mixed $webapp
*/
public function setPage($webapp)
{
$this->webapp = $webapp;
}function __toString()
{
$val=$this->id.' - '.$this->imagenom;
return $val;
}/**
* Set imagenom
*
* @param string $imagenom
*
* @return Photo
*/
public function setImagenom($imagenom)
{
$this->imagenom = $imagenom;
return $this;
}
/**
* Get imagenom
*
* @return string
*/
public function getImagenom()
{
return $this->imagenom;
}
/**
* Set updatedAt
*
* @param \DateTime $updatedAt
*
* @return Photo
*/
public function setUpdatedAt($updatedAt)
{
$this->updatedAt = $updatedAt;
return $this;
}
/**
* Get updatedAt
*
* @return \DateTime
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
/**
* Set imagedescription
*
* @param string $imagedescription
*
* @return Photo
*/
public function setImagedescription($imagedescription)
{
$this->imagedescription = $imagedescription;
return $this;
}
/**
* Get imagedescription
*
* @return string
*/
public function getImagedescription()
{
return $this->imagedescription;
}
}
Моя вторая сущность:
<?php
namespace MO\WebAppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
//pour le slug
use Gedmo\Mapping\Annotation as Gedmo;/**
* WebApp
*
* @ORM\Table(name="mo_travel")
* @ORM\Entity(repositoryClass="MO\WebAppBundle\Repository\WebAppRepository")
*
*/
class WebApp
{
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;/**
* @ORM\OneToMany(targetEntity="Photo", mappedBy="webapp")
*/
private $photos;
/**
* @var \DateTime
*
* @ORM\Column(name="date", type="datetime")
*/
private $date;
/**
* @var string
*
* @ORM\Column(name="title", type="string", length=255)
*/
private $title;
/**
* @var string
*
* @ORM\Column(name="author", type="string", length=255)
*/
private $author;
/**
* @var string
*
* @ORM\Column(name="content", type="text")
*/
private $content;
/**
* @ORM\Column(name="published", type="boolean")
*/
private $published = true;
/**
* @ORM\ManyToMany(targetEntity="MO\WebAppBundle\Entity\Category", cascade={"persist"})
* @ORM\JoinTable(name="mo_travel_category")
*/
private $categories;
/**
* @ORM\Column(name="updated_at", type="datetime", nullable=true)
*
* @var \DateTime
*/
private $updatedAt;
/**
* @Gedmo\Slug(fields={"title"})
* @ORM\Column(name="slug", type="string", length=255, unique=true)
*/
private $slug;
/**
* WebApp constructor.
*/
public function __construct()
{
// Par défaut, la date de l'annonce est la date d'aujourd'hui
$this->date = new \Datetime();
$this->categories = new ArrayCollection();
$this->images = new ArrayCollection();
}
/**
* @ORM\PreUpdate
*/
public function updateDate()
{
$this->setUpdatedAt(new \Datetime());
}
/**
* Get id
*
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* Set date
*
* @param \DateTime $date
*
* @return WebApp
*/
public function setDate($date)
{
$this->date = $date;
return $this;
}
/**
* Get date
*
* @return \DateTime
*/
public function getDate()
{
return $this->date;
}
/**
* Set title
*
* @param string $title
*
* @return WebApp
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Get title
*
* @return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set author
*
* @param string $author
*
* @return WebApp
*/
public function setAuthor($author)
{
$this->author = $author;
return $this;
}
/**
* Get author
*
* @return string
*/
public function getAuthor()
{
return $this->author;
}
/**
* Set content
*
* @param string $content
*
* @return WebApp
*/
public function setContent($content)
{
$this->content = $content;
return $this;
}
/**
* Get content
*
* @return string
*/
public function getContent()
{
return $this->content;
}
/**
* @param boolean $published
*
* @return WebApp
*/
public function setPublished($published)
{
$this->published = $published;
}
/**
* @return mixed
*/
public function getPublished()
{
return $this->published;
}
/**
* Add category
*
* @param \MO\WebAppBundle\Entity\Category $category
*
* @return WebApp
*/
public function addCategory(\MO\WebAppBundle\Entity\Category $category)
{
$this->categories[] = $category;
return $this;
}
/**
* Remove category
*
* @param \MO\WebAppBundle\Entity\Category $category
*/
public function removeCategory(\MO\WebAppBundle\Entity\Category $category)
{
$this->categories->removeElement($category);
}
/**
* Get categories
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getCategories()
{
return $this->categories;
}
/**
* @return mixed
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
/**
* @param mixed $updatedAt
*
* @return WebApp
*/
public function setUpdatedAt($updatedAt)
{
$this->updatedAt = $updatedAt;
}
/**
* Set slug
*
* @param string $slug
*
* @return WebApp
*/
public function setSlug($slug)
{
$this->slug = $slug;
return $this;
}
/**
* Get slug
*
* @return string
*/
public function getSlug()
{
return $this->slug;
}
/*----------------------------------*/
/**
* Add photo
*
* @param \MO\WebAppBundle\Entity\Photo $photo
*
* @return WebApp
*/
public function addPhoto(\MO\WebAppBundle\Entity\Photo $photo)
{
$this->photos[] = $photo;
return $this;
}
/**
* Remove photo
*
* @param \MO\WebAppBundle\Entity\Photo $photo
*/
public function removePhoto(\MO\WebAppBundle\Entity\Photo $photo)
{
$this->photos->removeElement($photo);
}
/**
* Get photos
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getPhotos()
{
return $this->photos;
}
}
Моя 1-я форма:
<?php
namespace MO\WebAppBundle\Form;
use MO\WebAppBundle\Entity\Image;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class WebAppType extends AbstractType
{
/**
* Réutilisabilité de notre formulaire
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('date', DateTimeType::class)
->add('title', TextType::class)
->add('author', TextType::class)
->add('content', TextareaType::class)
->add('photos', PhotoType::class)//->add('images', ImageType::class)
/*->add('images', FileType::class, array(
'multiple' => true,
'label' => false,
'label_attr' => array('class' => 'MOWebAppBundle:Image'),
'required' => true,
))/*
* Rappel :
** - 1er argument : nom du champ, ici « categories », car c'est le nom de l'attribut
** - 2e argument : type du champ, ici « CollectionType » qui est une liste de quelque chose
** - 3e argument : tableau d'options du champ
*/
->add('categories', EntityType::class, array(
'class' => 'MOWebAppBundle:Category',
'choice_label' => 'name',
'expanded' => true,
'multiple' => true,
))
->add('save', SubmitType::class);
// On ajoute une fonction qui va écouter un évènement de formulaire
$builder->addEventListener(
FormEvents::PRE_SET_DATA, // 1er argument : L'évènement qui nous intéresse : ici, PRE_SET_DATA
function(FormEvent $event) { // 2e argument : La fonction à exécuter lorsque l'évènement est déclenché
// On récupère notre objet WebApp (travel) sous-jacent
$travel = $event->getData();
// Cette condition est importante, on en reparle plus loin
if (null === $travel) {
return; // On sort de la fonction sans rien faire lorsque $advert vaut null
}
// Si l'annonce n'est pas publiée, ou si id null
if (!$travel->getPublished() || null === $travel->getId()) {
// Alors on ajoute le champ published
$event->getForm()->add('published', CheckboxType::class, array('required' => false));
} else {
// Sinon, on le supprime
$event->getForm()->remove('published');
}
}
);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MO\WebAppBundle\Entity\WebApp'
));
}
}
Моя вторая форма:
<?php
namespace MO\WebAppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Vich\UploaderBundle\Form\Type\VichFileType;class PhotoType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
//->add('name')
->add('imageFile', VichFileType::class, array(
'required' => false,
'allow_delete' => true, // not mandatory, default is true
'download_link' => true, // not mandatory, default is true
));
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MO\WebAppBundle\Entity\Photo',
));
}
}
Моя веточка для формы:
{{ form_start(form, {'attr': {'class': 'form-horizontal'}}) }}
...
{{ form_row(form.photos) }}
...
{{ form_end(form) }}
Мой config.yml:
imports:
- { resource: parameters.yml }
- { resource: security.yml }
- { resource: services.yml }
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: fr
app.path.photo_images: /uploads/img
image_directory: /uploads/img
framework:
#esi: ~
#translator: { fallbacks: ['%locale%'] }
secret: '%secret%'
router:
resource: '%kernel.project_dir%/app/config/routing.yml'
strict_requirements: ~
form: ~
csrf_protection: ~
validation: { enable_annotations: true }
#serializer: { enable_annotations: true }
templating:
engines: ['twig']
default_locale: '%locale%'
trusted_hosts: ~
session:
# https://symfony.com/doc/current/reference/configuration/framework.html#handler-id
handler_id: session.handler.native_file
save_path: '%kernel.project_dir%/var/sessions/%kernel.environment%'
fragments: ~
http_method_override: true
assets: ~
php_errors:
log: true
# Twig Configuration
twig:
debug: '%kernel.debug%'
strict_variables: '%kernel.debug%'
# Doctrine Configuration
doctrine:
dbal:
driver: pdo_mysql
host: '%database_host%'
port: '%database_port%'
dbname: '%database_name%'
user: '%database_user%'
password: '%database_password%'
charset: UTF8
# if using pdo_sqlite as your database driver:
# 1. add the path in parameters.yml
# e.g. database_path: "%kernel.project_dir%/var/data/data.sqlite"# 2. Uncomment database_path in parameters.yml.dist
# 3. Uncomment next line:
#path: '%database_path%'
orm:
auto_generate_proxy_classes: '%kernel.debug%'
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
# Swiftmailer Configuration
swiftmailer:
transport: '%mailer_transport%'
host: '%mailer_host%'
username: '%mailer_user%'
password: '%mailer_password%'
spool: { type: memory }
# Stof\DoctrineExtensionsBundle configuration
stof_doctrine_extensions:
orm:
default:
sluggable: true
# VichUploaderBundle
vich_uploader:
db_driver: orm
mappings:
photo_images:
uri_prefix: /uploads/img
upload_destination: '%kernel.root_dir%/../web/uploads/img'
namer: vich_uploader.namer_uniqid
inject_on_load: true
delete_on_update: true
delete_on_remove: true
Мой AppKernel.php:
<?php
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;
class AppKernel extends Kernel
{
//pour éviter l'erreur dans bin/console "Warning: date_default_timezone_get()"public function __construct($environment, $debug)
{
date_default_timezone_set('Europe/Paris');
parent::__construct($environment, $debug);
}
public function registerBundles()
{
$bundles = [
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
new Symfony\Bundle\TwigBundle\TwigBundle(),
new Symfony\Bundle\MonologBundle\MonologBundle(),
new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
//mon application
new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
new MO\WebAppBundle\MOWebAppBundle(),
new MO\CoreBundle\MOCoreBundle(),
//images multiples
new EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle,
new Vich\UploaderBundle\VichUploaderBundle(),
];
if (in_array($this->getEnvironment(), ['dev', 'test'], true)) {
$bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle();
$bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
$bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
$bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle();
if ('dev' === $this->getEnvironment()) {
$bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
$bundles[] = new Symfony\Bundle\WebServerBundle\WebServerBundle();
}
}
return $bundles;
}
public function getRootDir()
{
return __DIR__;
}
public function getCacheDir()
{
return dirname(__DIR__).'/var/cache/'.$this->getEnvironment();
}
public function getLogDir()
{
return dirname(__DIR__).'/var/logs';
}
public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load($this->getRootDir().'/config/config_'.$this->getEnvironment().'.yml');
}
}
Мой контроллер:
$travel = new WebApp();
$form = $this->createForm(WebAppType::class, $travel);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($travel);
$em->flush();
return $this->redirectToRoute('mo_webapp_travel', array('id' => $travel->getId()));
}
return $this->render('MOWebAppBundle:WebApp:add.html.twig', array(
'form' => $form->createView(),
));
Я был бы очень признателен, если бы кто-то пришел, чтобы помочь мне …
Вам нужно объявить тип формы для photos
поле как Collection
:
$builder
...
->add('photos', CollectionType::class, array(
'entry_type' => PhotoType::class
));
Других решений пока нет …