Error when embedding form in Symfony whith collection - forms

I have 2 "simple" entities, and i want to do the classical form embedding
but i have this error : "Neither the property "itemcode" nor one of the methods "getItemcode()", "itemcode()", "isItemcode()", "hasItemcode()", "__get()" exist and have public access in class "NWA\ItemSelectorBundle\Entity\ItemSelector"."
I've seen many posts with this error, but none provided the solution
In the entities i have getItemCode() but why would it be public ?
What is wrong with my construction?
Thank you in advance
Here are my entities (parts relevant to the properties at fault)
class ItemSelector
{
/**
* #var Items[]
*
* #ORM\OneToMany(targetEntity="NWA\ItemSelectorBundle\Entity\Item", mappedBy="itemselector", cascade={"all"})
*/
protected $items;
/**
* Class constructor
*/
public function __construct()
{
$this->items = new ArrayCollection();
}
/**
* Add item
*
* #param \NWA\ItemSelectorBundle\Entity\Item $item
*
* #return ItemSelector
*/
public function addItem(\NWA\ItemSelectorBundle\Entity\Item $item)
{
$this->items[] = $item;
//$item->setItemselector($this);
return $this;
}
/**
* Remove item
*
* #param \NWA\ItemSelectorBundle\Entity\Item $item
*/
public function removeItem(\NWA\ItemSelectorBundle\Entity\Item $item)
{
$this->items->removeElement($item);
}
/**
* Get items
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getItems()
{
return $this->items;
}
}
and
class Item
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="itemcode", type="string", length=255)
*/
protected $itemcode;
/**
* #var ItemSelector
*
* #ORM\ManyToOne(targetEntity="NWA\ItemSelectorBundle\Entity\ItemSelector", inversedBy="items")
* #ORM\JoinColumn(name="itemselector_id", referencedColumnName="id")
*/
protected $itemselector;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set itemcode
*
* #param string $itemcode
*
* #return Item
*/
public function setItemcode($itemcode)
{
$this->itemcode = $itemcode;
return $this;
}
/**
* Get itemcode
*
* #return string
*/
public function getItemcode()
{
return $this->itemcode;
}
/**
* Set itemselector
*
* #param \NWA\ItemSelectorBundle\Entity\ItemSelector $itemselector
*
* #return Item
*/
public function setItemselector(\NWA\ItemSelectorBundle\Entity\ItemSelector $itemselector = null)
{
$this->itemselector = $itemselector;
return $this;
}
/**
* Get itemselector
*
* #return \NWA\ItemSelectorBundle\Entity\ItemSelector
*/
public function getItemselector()
{
return $this->itemselector;
}
}
Then the Form constructors
class ItemSelectorType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'itemcode', 'collection', array(
'type' => new ItemType(),
'prototype' => true,
'allow_add' => true,
'allow_delete' => true
)
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'NWA\ItemSelectorBundle\Entity\ItemSelector',
'translation_domain' => 'resource'
));
}
/**
* #return string
*/
public function getName()
{
return 'nwa_itemselector';
}
}
and
class ItemType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'itemcode', 'text', array(
'label' => 'Code'
)
);
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'NWA\ItemSelectorBundle\Entity\Item'
));
}
/**
* #return string
*/
public function getName()
{
return 'nwa_itemselectorbundle_item';
}
}
And finally the call in the Controller
public function chooseAction(Request $request, ItemSelector $itemSelector)
{
$form = $this->get('form.factory')
->create(new ItemSelectorType(), $itemSelector);
$form->handleRequest($request);
if ($form->isValid()) {
}
return array(
'_resource' => $itemSelector,
'form' => $form->createView(),
);
}

Maybe you need to rename your field name itemcode to items in ItemSelectorType.
->add(
'items', 'collection', array(
'type' => new ItemType(),
'prototype' => true,
'allow_add' => true,
'allow_delete' => true
)
);

Related

Symfony: unable to handle request in form

I have 3 form types (SearchForm - SearchField - SearchFieldType), each one including next like this:
SearchFormType:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SearchFormType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('fields', 'collection', array('type' => new SearchFieldType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false))
->add('submit', 'submit', array('label' => "Buscar"))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\SearchForm',
'allow_extra_fields' => true,
'csrf_protection' => false,
'validation_groups' => false,
));
}
/**
* #return string
*/
public function getName()
{
return 'appbundle_searchform';
}
}
SearchFieldType:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SearchFieldType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'hidden')
->add('slug', 'hidden')
->add('value')
->add('choices')
->add('type', new SearchFieldTypeType())
->add('actionFilter')
->add('actionHighlight')
->add('actionShow')
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\SearchField'
));
}
/**
* #return string
*/
public function getName()
{
return 'appbundle_searchfield';
}
}
SearchFieldTypeType:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use AppBundle\Entity\SearchOperator;
class SearchFieldTypeType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$entity = $builder->getData();
$builder
->add('name', 'hidden')
->add('operators', 'entity', array('class' => 'AppBundle:SearchOperator',
'multiple' => false,
'expanded' => false))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\SearchFieldType'
));
}
/**
* #return string
*/
public function getName()
{
return 'appbundle_searchfieldtype';
}
}
The form renders properly, but when I submit and try to do $form->handleRequest($request) I get an exception:
Neither the property "operators" nor one of the methods "addOperator()"/"removeOperator()", "setOperators()", "operators()", "__set()" or "__call()" exist and have public access in class "AppBundle\Entity\SearchFieldType"
That's not true actually, as those methods exist and work correctly:
AppBundle\Entity\SearchFieldType:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\SearchOperator;
/**
* SearchField
*
* #ORM\Table()
* #ORM\Entity
*/
class SearchFieldType
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=100, nullable=true)
*/
private $name;
/**
* #ORM\ManyToMany(targetEntity="SearchOperator", cascade={"persist", "remove"})
* #ORM\JoinTable(
* joinColumns={#ORM\JoinColumn(name="type_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="operator_id", referencedColumnName="id")}
* )
**/
private $operators;
/**
* Constructor
*/
public function __construct()
{
$this->operators = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return SearchFieldType
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Add operator
*
* #param SearchOperator $operator
*
* #return SearchFieldType
*/
public function addOperator(SearchOperator $operator)
{
$this->operators[] = $operator;
return $this;
}
/**
* Remove operator
*
* #param SearchOperator $operator
*/
public function removeOperator(SearchOperator $operator)
{
$this->operators->removeElement($operator);
}
/**
* Get operator
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getOperators()
{
return $this->operators;
}
public function __toString()
{
return $this->name;
}
}
Stack trace:
in vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php at line 460 +
at PropertyAccessor ->writeProperty (object(SearchFieldType), 'operators', object(SearchOperator))
in vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php at line 104 +
at PropertyAccessor ->setValue (object(SearchFieldType), object(PropertyPath), object(SearchOperator))
in vendor/symfony/symfony/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php at line 93 +
at PropertyPathMapper ->mapFormsToData (object(RecursiveIteratorIterator), object(SearchFieldType))
in vendor/symfony/symfony/src/Symfony/Component/Form/Form.php at line 633 +
at Form ->submit (array('operators' => '156', 'name' => 'string'), true)
in vendor/symfony/symfony/src/Symfony/Component/Form/Form.php at line 577 +
at Form ->submit (array('type' => array('operators' => '156', 'name' => 'string'), 'value' => 'felipe', 'name' => 'Nombre', 'slug' => 'nombre'), true)
in vendor/symfony/symfony/src/Symfony/Component/Form/Form.php at line 577
EDIT:
Controller Code :
$searchFormEntity = new SearchForm();
$searchFormWithValues = $this->createForm(new SearchFormType(), $searchFormEntity, array(
'action' => $this->generateUrl('candidato'),
'method' => 'POST'
));
$searchFormWithValues->add('submit', 'submit', array('label' => 'Buscar'));
$searchFormWithValues->handleRequest($request);
Well you have a ManyToMany relation, so it would make sense to have the operators field be a collection. However you defined it as an entity, so now the form expects to have the setOperators and getOperators methods as entity implies a ManyToOne or OneToOne relationship.
I think you need to change the statement in the class SearchFieldTypeType where adding the operators attribute to be the same as what you did before for fields in SearchFormType if you want to keep the ManyToMany relationship.

Error using A2lix as an embedded form

I'm using A2lix Translation Form Bundle and Doctrine Behaviors Translatable in a project where I have two entities: company and files. Company has some translatable fields so I have a CompanyTranslations Entity for that. One company can have one file so Company and file are mapped with an OneToOne unidirectional reference. The company file is translatable so the property is in the CompanyTranslation file.
CompanyTranslation:
class CompanyTranslation
{
use ORMBehaviors\Translatable\Translation;
/**
* #ORM\OneToOne(targetEntity="File", cascade={"persist"})
* #ORM\JoinColumn(name="translatable_file_id", referencedColumnName="id")
* #Assert\Valid()
* #Assert\Type(type="MyApp\CoreBundle\Entity\File")
**/
private $translatableFile;
/**
* Set translatableFile
*
* #param $translatableFile
*/
public function setTranslatableFile(File $translatableFile = null)
{
$this->translatableFile = $translatableFile;
}
/**
* Get translatableFile
*
* #return $translatableFile
*/
public function getTranslatableFile()
{
return $this->translatableFile;
}
}
File:
class File
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
public $filePath;
/**
* #Assert\File()
*/
private $file;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set filePath
*
* #param string $filePath
*/
public function setFilePath($filePath)
{
$this->filePath = $filePath;
}
/**
* Get filePath
*
* #return string
*/
public function getFilePath()
{
return $this->filePath;
}
/**
* Set file
*
* #param UploadedFile $file
*/
public function setFile(UploadedFile $file = null)
{
$this->file = $file;
}
/**
* Get file
*
* #return UploadedFile
*/
public function getFile()
{
return $this->file;
}
}
File Form Type:
class FileType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('file', 'file', array(
'label' => false
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MyApp\CoreBundle\Entity\File'
));
}
public function getName()
{
return 'file_form';
}
}
Company Form Type:
class CompanyType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('translations', 'a2lix_translationsForms', array(
'locales' => $this->languages,
'form_type' => new FileType(),
'form_options' => array(
'data_class' => 'MyApp\CoreBundle\Entity\File',
'required' => false,
'validation_groups' => array('file_upload')
)
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
parent::setDefaultOptions($resolver);
$resolver->setDefaults(array(
'data_class' => 'MyApp\CoreBundle\Entity\Company'
));
}
}
The error is this one:
The form's view data is expected to be an instance of class MyApp\CoreBundle\Entity\File, but is an instance of class MyApp\CoreBundle\Entity\CompanyTranslation. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms an instance of class MyApp\CoreBundle\Entity\CompanyTranslation to an instance of MyApp\CoreBundle\Entity\File.
I already set the data_class of the File Type Form and the data_class of the field to null but also to MyApp\CoreBundle\Entity\File. Both send me errors. I don't know what's happening.
Could anyone help?
Thanks!

Symfony2 translation form with a2lix_translations and Gedmo doctrine-extensions

I work in Symfony 2.5
gedmo/doctrine-extensions": "dev-master
a2lix/translation-form-bundle": "2.*#dev
I'm trying on adding a translation on my entity Collection with the values name and description.
Here is my entity collection:
/**
* Collection
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Angeli\AdminBundle\Entity\CollectionRepository")
* #Gedmo\TranslationEntity(class="Angeli\AdminBundle\Entity\CollectionTranslation")
*/
class Collection
{
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #Gedmo\Translatable
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #var string
*
* #Gedmo\Translatable
* #ORM\Column(name="description", type="text")
*/
private $description;
/**
* #ORM\OneToMany(targetEntity="CollectionTranslation", mappedBy="object", cascade={"persist", "remove"})
*/
protected $translations;
/**
* Required for Translatable behaviour
* #Gedmo\Locale
*/
protected $locale;
public function __construct()
{
$this->translations = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Collection
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set description
*
* #param string $description
* #return Collection
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
public function getTranslations()
{
return $this->translations;
}
public function addTranslation(CollectionTranslation $t)
{
$this->translations->add($t);
$t->setObject($this);
}
public function removeTranslation(CollectionTranslation $t)
{
$this->translations->removeElement($t);
}
public function setTranslations($translations)
{
$this->translations = $translations;
}
public function __toString()
{
return $this->getName();
}
}
Here is my CollectionTranslation Class:
/**
* #ORM\Entity
* #ORM\Table(name="collection_translations",
* uniqueConstraints={#ORM\UniqueConstraint(name="lookup_unique_idx", columns={
* "locale", "object_id", "field"
* })}
* )
*/
class CollectionTranslation extends AbstractPersonalTranslation
{
/**
* Convinient constructor
*
* #param string $locale
* #param string $field
* #param string $content
*/
public function __construct($locale = null, $field = null, $content = null)
{
$this->setLocale($locale);
$this->setField($field);
$this->setContent($content);
}
/**
* #ORM\ManyToOne(targetEntity="Collection", inversedBy="translations")
* #ORM\JoinColumn(name="object_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $object;
}
Now I'm trying on building a form:
class CollectionType extends AbstractType{
public function buildForm(FormBuilderInterface $builder, array $options)
{
//$builder->add('name','text', array('required' => true, 'label'=>'Name'));
//$builder->add('description','textarea', array('required' => true, 'label'=>'Description'));
$builder->add('translations', 'a2lix_translations');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Angeli\AdminBundle\Entity\Collection',
));
}
public function getName()
{
return 'Collection';
}
}
But my form exist of 3 language tabs
and the input fields
Field
Content
If I add
field = name
content = some text
I only translated the value "name"
I want a form with my language tabs and "name" and "description" as input fields in the translation form.
Somebody sees what I'm doing wrong?
Try this: For ex. you are using 3 languages English, French and German.
Add in your form:
$builder->add('translations', 'a2lix_translations', array(
'locales' => array('en_US','fr_FR', 'de'),
'required_locales'=>array('en_US'),
'fields' => array(
'name' => array(
'field_type' => 'text',
'label' => 'Name',
'locale_options' => array(
'fr' => array(
'label' => 'nom'
),
'de' => array(
'label' => 'Name'
),
)),
'description' => array(
'field_type' => 'textarea',
'label' => 'Description',
'locale_options' => array(
'fr' => array(
'label' => 'description'
),
'de' => array(
'label' => 'Beschreibung'
),
)),
)));
And render in view like
form_label(form.translations)
form_widget(form.translations)
I had the same problem as yours.
Please try:
"gedmo/doctrine-extensions": "dev-master",
"a2lix/translation-form-bundle": "1.*#dev"
and
$builder->add('translations', 'a2lix_translations_gedmo', array(
'translatable_class' => 'Angeli\AdminBundle\Entity\Collection',
));
You also don't need __construct in CollectionTranslation.

How to edit embedded form with file upload in symfony2 and doctrine mongodb

I have document called aboutMe
and it has another embedded document called projects to add many projects (prototype)
The projects has project name and image for the project.
i created a formType for aboutMe document and i embedded the project form inside the aboutMe form to be able to add many projects prototype.
The problem is updating the project->image when the user didn't change the old project image.
doctrine updating the old embedded project document with a null image.
I Need to keep the old image name if the user didn't upload a new one
/**
* #MongoDB\Document
* #MongoDB\HasLifecycleCallbacks
*/
class AboutMeIndex {
/**
* #var integer
*
* #MongoDB\Id(strategy="INCREMENT")
*/
protected $id;
/**
* #var array
*
* #MongoDB\EmbedMany(targetDocument="AboutMeProjects", strategy="set")
*/
protected $projects = array();
public function __construct()
{
$this->projects = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Remove project
*
* #param TimesSell\CoreBundle\Document\Profile\AboutMe\AboutMeProjects $project
*/
public function removeProject(\TimesSell\CoreBundle\Document\Profile\AboutMe\AboutMeProjects $project)
{
$this->projects->removeElement($project);
}
/**
* Get projects
*
* #return Doctrine\Common\Collections\Collection $projects
*/
public function getProjects()
{
return $this->projects;
}
/**
* Add certification
*
* #param TimesSell\CoreBundle\Document\Profile\AboutMe\AboutMeCertifications $certification
*/
public function addCertification(\TimesSell\CoreBundle\Document\Profile\AboutMe\AboutMeCertifications $certification)
{
$this->certifications[] = $certification;
}
//=================================================================================//
public function fileGetter($file){
if(method_exists($this, 'get' . ucfirst($file))) {
return call_user_func(array($this, 'get' . ucfirst($file)));
}
else {
throw new \Exception("Couldn't Find Method name get" . ucfirst($file));
}
}
protected function getUploadRootDir($uploadDir)
{
return __DIR__.'/../../../../../../web/uploads/'.$this->getUploadDir($uploadDir);
}
protected function getUploadDir($uploadDir)
{
return $uploadDir;
}
public function uploadEmbeddedPhotos($file, $uploadDir)
{
if (null === $this->fileGetter($file)) {
return;
}
foreach ($this->fileGetter($file) as $galleryPhoto){
$pictureName = uniqid().'.'.$galleryPhoto->getImage()->guessExtension();
$galleryPhoto->getImage()->move($this->getUploadRootDir($uploadDir),$pictureName);
$this->path = $galleryPhoto->getImage()->getClientOriginalName();
$galleryPhoto->setImage($pictureName);
}
}
public function deleteImage($image, $uploadDir){
#unlink($this->getUploadRootDir($uploadDir).$image);
}
//=================================================================================//
/**
* #MongoDB\EmbeddedDocument
*
*/
class AboutMeProjects {
/**
* #var integer
*
* #MongoDB\Id(strategy="INCREMENT")
*/
protected $id;
/**
* #var string
*
* #MongoDB\String
*/
protected $projectName;
/**
* #var string
*
* #Assert\Image(
* maxSize = "20000k",
* mimeTypes = {"image/gif", "image/jpeg", "image/png"},
* mimeTypesMessage = "Please upload a valid picture"
* )
* #Assert\Regex(
* pattern="/[a-zA-Z0-9]+/",
* match=true,
* message="Special characters are not allowed"
* )
*
* #MongoDB\String
*/
protected $image;
/**
* #var string
*
* #MongoDB\String
*/
protected $desc;
/**
* Get id
*
* #return int_id $id
*/
public function getId()
{
return $this->id;
}
/**
* Set projectName
*
* #param string $projectName
* #return self
*/
public function setProjectName($projectName)
{
$this->projectName = $projectName;
return $this;
}
/**
* Get projectName
*
* #return string $projectName
*/
public function getProjectName()
{
return $this->projectName;
}
/**
* Set image
*
* #param string $image
* #return self
*/
public function setImage($image)
{
$this->image = $image;
return $this;
}
/**
* Get image
*
* #return string $image
*/
public function getImage()
{
return $this->image;
}
/**
* Set desc
*
* #param string $desc
* #return self
*/
public function setDesc($desc)
{
$this->desc = $desc;
return $this;
}
/**
* Get desc
*
* #return string $desc
*/
public function getDesc()
{
return $this->desc;
}
}
class AboutMeIndexType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstName')
->add('projects', 'collection', array(
'type' => new ProjectsType(),
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'required' => false
))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AboutMeIndex'
));
}
/**
* #return string
*/
public function getName()
{
return 'AbourMe';
}
}
class ProjectsType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('projectName','text',array('attr'=> array('class'=>'form-control', 'placeholder' => 'Project name') ))
->add('image','file',array('data_class' => null,'attr'=> array('class'=>'form-control col-lg-2 file-inputs') ))
->add('desc','textarea',array('attr'=> array('class'=>'form-control', 'data-provide' => 'markdown', 'placeholder' => 'Description') ))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AboutMeProjects'
));
}
/**
* #return string
*/
public function getName()
{
return 'ProjectsType';
}
}
And here's the controller that i want to be able to keep the old image
/**
* Edits an existing aboutMeIndex document.
*
* #Route("/profile/about-me/update", name="profile_about_me_update")
* #Method("PUT")
* #Template()
*/
public function updateAction(Request $request)
{
$dm = $this->get('doctrine.odm.mongodb.document_manager');
$user = $this->getUser();
$entity = $dm->getRepository('AboutMeIndex')->findOneBy(array('user.$id' => (int)$user->getId()));
if (!$entity) {
throw $this->createNotFoundException('Unable to find entity Document.');
}
$editForm = $this->createForm(new AboutMeIndexType(), $entity);
$editForm->submit($request);
if ($editForm->isValid()) {
if($entity->getProjects()->getImage() is newImage){
$entity->uploadEmbeddedPhotos('projects', 'profile/aboutMe/');
}else{
// Keep the old Image
}
$dm->persist($entity);
$dm->flush();
}
}

Symfony 2 Exception: Call to a member function on a non object when using Form Events

I'm trying to create a form which dinamically load all "sites" related to a "project", It seems like this would be of use, so I tried it:
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class EngineeringType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('project','entity',array(
'class' => 'tBundle:Project',
'label' => 'Project'
;
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function(FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
$sites = $data->getProject()->getSites();
$form->add('site', 'entity', array('choices' => $sites));
}
);
}
My problem comes when I try to access the form, I get:
FatalErrorException: Error: Call to a member function getSites() on a non-object in ... tBundle\Form\EngineeringType.php line 41
Here are my entities:
namespace tBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Engineering
*
* #ORM\Table(name="engineerings")
* #ORM\Entity
*/
class Engineering
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="tBundle\Entity\Project")
* #ORM\JoinColumn(name="project_id", referencedColumnName="id",nullable=false)
*/
private $project;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set project
*
* #param string $project
* #return Engineering
*/
public function setProject(\tBundle\Entity\Project $project)
{
$this->project = $project;
return $this;
}
/**
* Get project
*
* #return string
*/
public function getProject()
{
return $this->project;
}
Project:
namespace tBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Project
*
* #ORM\Table(name="projects")
* #ORM\Entity
*/
class Project
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #ORM\ManyToMany(targetEntity="tBundle\Entity\Site")
* #ORM\JoinTable(name="project_sites",
* joinColumns={#ORM\JoinColumn(name="site_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="project_id", referencedColumnName="id")}
* )
*/
private $sites;
public function __construct()
{
$this->sites = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Project
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Get Sites
*
* #return array
*/
public function getSites()
{
return $this->sites;
}
/* Returns Project's Name */
public function __toString()
{
return $this->name;
}
What am I doing wrong?
EDIT
Controller:
/**
* Creates a form to create a Engineering entity.
*
* #param Engineering $entity The entity
*
* #return \Symfony\Component\Form\Form The form
*/
private function createCreateForm(Engineering $entity)
{
$form = $this->createForm(new EngineeringType(), $entity, array(
'action' => $this->generateUrl('engineering_create'),
'method' => 'POST',
));
$form->add('submit', 'submit', array('label' => 'Create'));
return $form;
}
/**
* Creates a form to edit a Engineering entity.
*
* #param Engineering $entity The entity
*
* #return \Symfony\Component\Form\Form The form
*/
private function createEditForm(Engineering $entity)
{
$form = $this->createForm(new EngineeringType(), $entity, array(
'action' => $this->generateUrl('engineering_update', array('id' => $entity->getId())),
'method' => 'POST',
));
$form->add('submit', 'submit', array('label' => 'Update'));
return $form;
}
The PRE_SET_DATA event is actually fired twice. The first time will not have any data. There used to be a blurb in the manual explaining why but I could not find it again.
So just:
function(FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
if ($data)
{
$sites = $data->getProject()->getSites();
$form->add('site', 'entity', array('choices' => $sites));
}
}
=======================================================
Updated answer to show how to handle non-existent $project:
if ($data)
{
$project = $data->getProject();
$sites = $project ? $project->getSites() : array();
$form->add('site', 'entity', array('choices' => $sites));
}
There is an easy solution, why don't you use the "property" property on the form builder?
$builder
->add('project','entity',array(
'class' => 'tBundle:Project',
'label' => 'Project',
'property' => 'sites');
Or even you can use a query builder if this is not enough:
$builder->add('users', 'entity', array(
'class' => 'AcmeHelloBundle:User',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('u')
->orderBy('u.username', 'ASC');
},
));
You can find here more description, if this is not enough:
Symfony documentation
EDIT:
So your problem that with the first solution, that it's going to be an array, so use my second option, and in the query builder, specify what will reflect your needs.
Or use it like the class is not Project but Sites.