Symfony2: One To Many Relationship - php

Experiencing some serious problems in getting a OneToMany relationship to work. The error happens when trying to add "Children" to Beauties in Sonata Admin.
Using:
Symfony2
Doctrine
Sonata Admin
Been trying to follow the guide at: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#one-to-many-bidirectional
and
http://sonata-project.org/bundles/doctrine-orm-admin/2-2/doc/reference/form_field_definition.html
Error:
"CRITICAL - Uncaught PHP Exception
Doctrine\ORM\ORMInvalidArgumentException: "A new entity was found
through the relationship
'Beautify\BeautiesBundle\Entity\Beauty#children' that was not
configured to cascade persist operations for entity:
Beautify\BeautiesBundle\Entity\Children#000000000eff91d5000000017f8ca53b.
To solve this issue: Either explicitly call EntityManager#persist() on
this unknown entity or configure cascade persist this association in
the mapping for example #ManyToOne(..,cascade={"persist"}). If you
cannot find out which entity causes the problem implement
'Beautify\BeautiesBundle\Entity\Children#__toString()' to get a clue."
at
/Users/gmf/symfony/vendor/doctrine/orm/lib/Doctrine/ORM/ORMInvalidArgumentException.php
line 91 "
.
.
BeautyAdmin.php
<?php
// src/Beautify/BeautiesBundle/Admin/BeautyAdmin.php
namespace Beautify\BeautiesBundle\Admin;
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Knp\Menu\ItemInterface as MenuItemInterface;
use Beautify\CategoryBundle\Entity\Category;
use Beautify\CategoryBundle\Entity\Children;
class BeautyAdmin extends Admin
{
// Fields to be shown on create/edit forms
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('General')
->add('name', 'text', array('label' => 'Name'))
->add('content', 'textarea', array('label' => 'Content'))
->add('imageFile', 'file', array('label' => 'Image file', 'required' => false))
->end()
->with('Category')
->add('category', 'sonata_type_model', array('expanded' => true))
->end()
->with('Children')
->add('children', 'sonata_type_collection', array(), array(
'edit' => 'inline',
'inline' => 'table',
'sortable' => 'position'
))
->end()
;
}
// Fields to be shown on filter forms
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('name')
->add('content')
->add('category')
;
}
// Fields to be shown on lists
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('name')
->add('content')
->add('category')
->add('imageName')
->add('_action', 'actions', array(
'actions' => array(
'show' => array(),
'edit' => array(),
'delete' => array(),
)
))
;
}
}
Beauty.php Entity
<?php
// src/Beautify/BeautiesBundle/Entity/Beauty.php
namespace Beautify\BeautiesBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="beauties")
* #ORM\HasLifecycleCallbacks
* #Vich\Uploadable
*/
class Beauty
{
/**
* #ORM\OneToMany(targetEntity="Children", mappedBy="beauties")
**/
private $children;
public function __construct()
{
$this->children = new ArrayCollection();
}
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=100)
*/
protected $name;
/**
* #ORM\Column(type="text")
*/
protected $content;
/**
* #Vich\UploadableField(mapping="beauty_image", fileNameProperty="imageName")
* #var File $imageFile
*/
protected $imageFile;
/**
* #ORM\Column(type="string", length=255, nullable=true, name="image_name")
* #var string $imageName
*/
protected $imageName;
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="beauties")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
protected $category;
/**
* If manually uploading a file (i.e. not using Symfony Form) ensure an instance
* of 'UploadedFile' is injected into this setter to trigger the update. If this
* bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
* must be able to accept an instance of 'File' as the bundle will inject one here
* during Doctrine hydration.
*
* #param File|\Symfony\Component\HttpFoundation\File\UploadedFile
*/
public function setImageFile(File $image = null)
{
$this->imageFile = $image;
if ($image) {
// 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
$this->updatedAt = new \DateTime('now');
}
}
/**
* #return File
*/
public function getImageFile()
{
return $this->imageFile;
}
/**
* #param string $imageName
*/
public function setImageName($imageName)
{
$this->imageName = $imageName;
}
/**
* #return string
*/
public function getImageName()
{
return $this->imageName;
}
public function __toString()
{
return ($this->getName()) ? : '';
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Beauty
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set content
*
* #param string $content
* #return Beauty
*/
public function setContent($content)
{
$this->content = $content;
return $this;
}
/**
* Get content
*
* #return string
*/
public function getContent()
{
return $this->content;
}
/**
* Set category
*
* #param \Beautify\BeautiesBundle\Entity\Category $category
* #return Beauty
*/
public function setCategory(\Beautify\BeautiesBundle\Entity\Category $category = null)
{
$this->category = $category;
return $this;
}
/**
* Get category
*
* #return \Beautify\BeautiesBundle\Entity\Category
*/
public function getCategory()
{
return $this->category;
}
/**
* Set children
*
* #param \Beautify\BeautiesBundle\Entity\Children $children
* #return Beauty
*/
public function setChildren(\Beautify\BeautiesBundle\Entity\Children $children = null)
{
$this->children = $children;
return $this;
}
/**
* Get children
*
* #return \Beautify\BeautiesBundle\Entity\Children
*/
public function getChildren()
{
return $this->children;
}
/**
* Add children
*
* #param \Beautify\BeautiesBundle\Entity\Children $children
* #return Beauty
*/
public function addChild(\Beautify\BeautiesBundle\Entity\Children $children)
{
$this->children[] = $children;
return $this;
}
/**
* Remove children
*
* #param \Beautify\BeautiesBundle\Entity\Children $children
*/
public function removeChild(\Beautify\BeautiesBundle\Entity\Children $children)
{
$this->children->removeElement($children);
}
}
Children.php Entity
<?php
namespace Beautify\BeautiesBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Children
*
* #ORM\Table(name="children")
* #ORM\Entity
*/
class Children
{
/**
* #ORM\ManyToOne(targetEntity="Beauty", inversedBy="children")
* #ORM\JoinColumn(name="beauty_id", referencedColumnName="id")
**/
private $beauties;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="image", type="string", length=255)
*/
private $image;
/**
* #var string
*
* #ORM\Column(name="content", type="text")
*/
private $content;
/**
* #var integer
*
* #ORM\Column(name="priority", type="integer")
*/
private $priority;
/**
* #var integer
*
* #ORM\Column(name="parent_id", type="integer")
*/
private $parentId;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set image
*
* #param string $image
* #return Children
*/
public function setImage($image)
{
$this->image = $image;
return $this;
}
/**
* Get image
*
* #return string
*/
public function getImage()
{
return $this->image;
}
/**
* Set content
*
* #param string $content
* #return Children
*/
public function setContent($content)
{
$this->content = $content;
return $this;
}
/**
* Get content
*
* #return string
*/
public function getContent()
{
return $this->content;
}
/**
* Set priority
*
* #param integer $priority
* #return Children
*/
public function setPriority($priority)
{
$this->priority = $priority;
return $this;
}
/**
* Get priority
*
* #return integer
*/
public function getPriority()
{
return $this->priority;
}
/**
* Set parentId
*
* #param integer $parentId
* #return Children
*/
public function setParentId($parentId)
{
$this->parentId = $parentId;
return $this;
}
/**
* Get parentId
*
* #return integer
*/
public function getParentId()
{
return $this->parentId;
}
/**
* Set beauties
*
* #param \Beautify\BeautiesBundle\Entity\Beauty $beauties
* #return Children
*/
public function setBeauties(\Beautify\BeautiesBundle\Entity\Beauty $beauties = null)
{
$this->beauties = $beauties;
return $this;
}
/**
* Get beauties
*
* #return \Beautify\BeautiesBundle\Entity\Beauty
*/
public function getBeauties()
{
return $this->beauties;
}
}
Very thankful for any help...

The error says it all. You need to add cascade="persist" to the children property.
/**
* #ORM\OneToMany(targetEntity="Children", mappedBy="beauties", cascade={"persist"})
**/
private $children;
See http://doctrine-orm.readthedocs.org/en/latest/reference/working-with-associations.html#transitive-persistence-cascade-operations

Related

Neither the property "parent" build form Symfony 2 Self-Referenced mapping

I have an Entity containing Self-Referenced mapping. I would like to add new categories and subcategories to the systems but I do not know how to build the add form correctly. Gets are generated and setters are generated in Entity. I'm getting an error:
Neither the property "parent" nor one of the methods
"addParent()"/"removeParent()", "setParent()", "parent()", "__set()"
or "__call()" exist and have public access in class
"Adevo\ClassifiedsBundle\Entity\ClassifiedsCategory".
namespace XXX\ClassifiedsBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="XXX\ClassifiedsBundle\Repository\ClassifiedsCategoryRepository")
* #ORM\Table(name="classifieds_categories")
*/
class ClassifiedsCategory extends ClassifiedsAbstractTaxonomy {
/**
* #ORM\OneToMany(
* targetEntity = "Classifieds",
* mappedBy = "category"
* )
*/
protected $classifieds;
/**
* #ORM\ManyToMany(targetEntity="ClassifiedsCategory", mappedBy="parent")
*/
private $children;
/**
*
* #ORM\ManyToMany(targetEntity="ClassifiedsCategory", inversedBy="children")
* #ORM\JoinTable(name="subCategory",
* joinColumns={#ORM\JoinColumn(name="category_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="parent_id", referencedColumnName="id")}
* )
*/
private $parent;
/**
* Constructor
*/
public function __construct() {
$this->children = new \Doctrine\Common\Collections\ArrayCollection();
$this->parent = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add classified
*
* #param \XXX\ClassifiedsBundle\Entity\Classifieds $classified
*
* #return ClassifiedsCategory
*/
public function addClassified(\XXX\ClassifiedsBundle\Entity\Classifieds $classified) {
$this->classifieds[] = $classified;
return $this;
}
/**
* Remove classified
*
* #param \XXX\ClassifiedsBundle\Entity\Classifieds $classified
*/
public function removeClassified(\XXX\ClassifiedsBundle\Entity\Classifieds $classified) {
$this->classifieds->removeElement($classified);
}
/**
* Get classifieds
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getClassifieds() {
return $this->classifieds;
}
/**
* Add child
*
* #param \XXX\ClassifiedsBundle\Entity\ClassifiedsCategory $child
*
* #return ClassifiedsCategory
*/
public function addChild(\XXX\ClassifiedsBundle\Entity\ClassifiedsCategory $child) {
$this->children[] = $child;
return $this;
}
/**
* Remove child
*
* #param \XXX\ClassifiedsBundle\Entity\ClassifiedsCategory $child
*/
public function removeChild(\XXX\ClassifiedsBundle\Entity\ClassifiedsCategory $child) {
$this->children->removeElement($child);
}
/**
* Get children
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getChildren() {
return $this->children;
}
/**
* Add parent
*
* #param \XXX\ClassifiedsBundle\Entity\ClassifiedsCategory $parent
*
* #return ClassifiedsCategory
*/
public function addParent(\XXX\ClassifiedsBundle\Entity\ClassifiedsCategory $parent) {
$this->parent[] = $parent;
return $this;
}
/**
* Remove parent
*
* #param \XXX\ClassifiedsBundle\Entity\ClassifiedsCategory $parent
*/
public function removeParent(\XXX\ClassifiedsBundle\Entity\ClassifiedsCategory $parent) {
$this->parent->removeElement($parent);
}
/**
* Get parent
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getParent() {
return $this->parent;
}
}
<pre>
namespace XXX\ClassifiedsBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\MappedSuperclass
* #ORM\HasLifecycleCallbacks
*/
abstract class ClassifiedsAbstractTaxonomy {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=120, unique=true)
*/
private $name;
/**
* #ORM\Column(type="string", length=120, unique=true)
*/
private $slug;
protected $classifieds;
/**
* Constructor
*/
public function __construct()
{
$this->classifieds = new \Doctrine\Common\Collections\ArrayCollection();
// $this->children = new \Doctrine\Common\Collections\ArrayCollection();
// $this->parent = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add classifieds
*
* #param \XXX\ClassifiedsBundle\Entity\Classifieds $classifieds
* #return ClassifiedsCategory
*/
public function addClassifieds(\XXX\ClassifiedsBundle\Entity\Classifieds $classifieds)
{
$this->classifieds[] = $classifieds;
return $this;
}
/**
* Remove classifieds
*
* #param \XXX\ClassifiedsBundle\Entity\Classifieds $classifieds
*/
public function removeClassifieds(\XXX\ClassifiedsBundle\Entity\Classifieds $classifieds)
{
$this->classifieds->removeElement($classifieds);
}
/**
* Get classifieds
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getCompanies()
{
return $this->classifieds;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return AbstractTaxonomy
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set slug
*
* #param string $slug
* #return AbstractTaxonomy
*/
public function setSlug($slug)
{
$this->slug = \XXX\ClassifiedsBundle\Libs\Utils::sluggify($slug);
return $this;
}
/**
* Get slug
*
* #return string
*/
public function getSlug()
{
return $this->slug;
}
/**
* #ORM\PrePersist
* #ORM\PreUpdate
*/
public function preSave(){
if(null === $this->slug){
$this->setSlug($this->getName());
}
}
}
namespace XXX\AdminBundle\Form\Type;
use XXX\AdminBundle\Form\Type\ClassifiedsTaxonomyType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ClassifiedsCategoryType extends ClassifiedsTaxonomyType {
public function getName() {
return 'taxonomy';
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name', 'text', array(
'label' => 'Tytuł'
))
->add('slug', 'text', array(
'label' => 'Alias'
))
->add('parent', 'entity', array(
'class' => 'XXX\ClassifiedsBundle\Entity\ClassifiedsCategory',
'property' => 'name',
'empty_value' => 'Choose a parent category',
'required' => false,
))
->add('save', 'submit', array(
'label' => 'Zapisz'
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'XXX\ClassifiedsBundle\Entity\ClassifiedsCategory'
));
}
}
Your FormType ClassifiedsCategoryType is expecting an instance of ClassifiedsCategory and this need the attribute parent so far so good. But when you handle the request from your form to the controller, the formbuilder component tries to set the entered value to the attribute via the set method on the entity. This set method (setParent) is missing in your entity class.
So you have to implement it like this:
public function setParent($parent)
{
$this->parent = $parent;
return $this;
}

Symfony Catchable Fatal Error: Object of class DataBundle\Entity\Location could not be converted to string

I am trying to create a little CRUD system with 2 entities and a OneToMany relationship between these 2 entities.
My entities are Location & Job. I created the entities and created the crud controller via doctrine for both.
Location CRUD works great but Job only Read & Delete works. When I try to create a new Job it throws the following exception:
Catchable Fatal Error: Object of class DataBundle\Entity\Location
could not be converted to string
I have no idea what it means or what is causing it.
If anyone could help me out that would be realy awesome!
Many thanks in advance!
Here is my code.
Entities:
Location
<?php
namespace DataBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Location
*
* #ORM\Table(name="location")
* #ORM\Entity(repositoryClass="DataBundle\Repository\LocationRepository")
*/
class Location
{
/**
* #var int
*
* #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;
/**
* #var string
*
* #ORM\Column(name="street", type="string", length=255)
*/
private $street;
/**
* #var string
*
* #ORM\Column(name="number", type="string", length=25)
*/
private $number;
/**
* #var string
*
* #ORM\Column(name="postalcode", type="string", length=4)
*/
private $postalcode;
/**
* #var string
*
* #ORM\Column(name="city", type="string", length=255)
*/
private $city;
/**
* #var Job[] Available jobs for this location.
*
* #ORM\OneToMany(targetEntity="Job", mappedBy="location")
*/
private $jobs;
public function __construct()
{
$this->jobs = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Location
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set street
*
* #param string $street
*
* #return Location
*/
public function setStreet($street)
{
$this->street = $street;
return $this;
}
/**
* Get street
*
* #return string
*/
public function getStreet()
{
return $this->street;
}
/**
* Set number
*
* #param string $number
*
* #return Location
*/
public function setNumber($number)
{
$this->number = $number;
return $this;
}
/**
* Get number
*
* #return string
*/
public function getNumber()
{
return $this->number;
}
/**
* Set postalcode
*
* #param string $postalcode
*
* #return Location
*/
public function setPostalcode($postalcode)
{
$this->postalcode = $postalcode;
return $this;
}
/**
* Get postalcode
*
* #return string
*/
public function getPostalcode()
{
return $this->postalcode;
}
/**
* Set city
*
* #param string $city
*
* #return Location
*/
public function setCity($city)
{
$this->city = $city;
return $this;
}
/**
* Get city
*
* #return string
*/
public function getCity()
{
return $this->city;
}
/**
* Add job
*
* #param \DataBundle\Entity\Job $job
*
* #return Location
*/
public function addJob(\DataBundle\Entity\Job $job)
{
$this->jobs[] = $job;
return $this;
}
/**
* Remove job
*
* #param \DataBundle\Entity\Job $job
*/
public function removeJob(\DataBundle\Entity\Job $job)
{
$this->jobs->removeElement($job);
}
/**
* Get jobs
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getJobs()
{
return $this->jobs;
}
}
Job:
<?php
namespace DataBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Job
*
* #ORM\Table(name="job")
* #ORM\Entity(repositoryClass="DataBundle\Repository\JobRepository")
*/
class Job
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255)
*/
private $title;
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=255)
*/
private $description;
/**
* #var Location
*
* #ORM\ManyToOne(targetEntity="Location", inversedBy="jobs")
* #ORM\JoinColumn(name="location_id", referencedColumnName="id")
*/
private $location;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set title
*
* #param string $title
*
* #return Job
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set description
*
* #param string $description
*
* #return Job
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set location
*
* #param \DataBundle\Entity\Location $location
*
* #return Job
*/
public function setLocation(\DataBundle\Entity\Location $location = null)
{
$this->location = $location;
return $this;
}
/**
* Get location
*
* #return \DataBundle\Entity\Location
*/
public function getLocation()
{
return $this->location;
}
}
My new action in the JobController:
/**
* Creates a new job entity.
*
*/
public function newAction(Request $request)
{
$job = new Job();
$form = $this->createForm('DataBundle\Form\JobType', $job);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($job);
$em->flush($job);
return $this->redirectToRoute('job_show', array('id' => $job->getId()));
}
return $this->render('job/new.html.twig', array(
'job' => $job,
'form' => $form->createView(),
));
}
DataBundle/Form/JobTypep
<?php
namespace DataBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class JobType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title')->add('description')->add('location') ;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'DataBundle\Entity\Job'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'databundle_job';
}
}
You can't use location as text field in your form, location is object.
You can fix this error by using form data transformers that will convert location into text on form output and text in location object on form submit.
http://symfony.com/doc/current/form/data_transformers.html
Or by using form type EnityType for location:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title')->add('description');
$builder
->add('location', Symfony\Bridge\Doctrine\Form\Type\EntityType::class, [
'required' => false,
'expanded' => false,
'multiple' => false,
'class' => 'DataBundle\\Entity\\Location',
'empty_data' => '',
'choice_label' => 'name',
'label' => 'Location',
]);
}
http://symfony.com/doc/current/reference/forms/types/entity.html

Cannot get related entity field to save in Symfony Admin Form

I'm working my way through Symfony trying to learn how it all fits together and I'm working on the admin section.
Right now I'm putting together an admin form for a Show Entity which will reference a section entity (so this show belongs in that section, etc). Every other field in the form saves EXCEPT for the related entity choice field.
This is the ShowAdmin class
<?php
namespace AppBundle\Admin;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Nelmio\ApiDocBundle\Tests\Fixtures\Form\EntityType;
class ShowAdmin extends AbstractAdmin {
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper->add('title', 'text')
->add('shortname', 'text')
->add('section_id', EntityType::class, array(
'class' => 'AppBundle:SectionEntity',
'choice_label' => 'section_title',
))
->add('logo', 'text')
->add('description', 'textarea')
->add('status', 'integer');
}
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper->add('title');
$datagridMapper->add('shortname');
}
protected function configureListFields(ListMapper $listMapper)
{
$listMapper->addIdentifier('title');
$listMapper->add('shortname', 'text');
}
}
This is the ShowEntity
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="shows")
*/
class ShowEntity {
function __construct() {
$this->children = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $title;
/**
* #ORM\Column(type="string", length=100)
*/
private $shortname;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\SectionEntity")
*/
private $section;
/**
* #ORM\Column(type="string", length=255)
*/
private $logo;
/**
* #ORM\Column(type="text")
*/
private $description;
/**
* #ORM\Column(type="integer")
*/
private $status;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set title
*
* #param string $title
*
* #return ShowEntity
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set sectionId
*
* #param integer $sectionId
*
* #return ShowEntity
*/
public function setSectionId($sectionId)
{
$this->section_id = $sectionId;
return $this;
}
/**
* Get sectionId
*
* #return integer
*/
public function getSectionId()
{
return $this->section_id;
}
/**
* Set logo
*
* #param string $logo
*
* #return ShowEntity
*/
public function setLogo($logo)
{
$this->logo = $logo;
return $this;
}
/**
* Get logo
*
* #return string
*/
public function getLogo()
{
return $this->logo;
}
/**
* Set description
*
* #param string $description
*
* #return ShowEntity
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set status
*
* #param integer $status
*
* #return ShowEntity
*/
public function setStatus($status)
{
$this->status = $status;
return $this;
}
/**
* Get status
*
* #return integer
*/
public function getStatus()
{
return $this->status;
}
/**
* Set shortname
*
* #param string $shortname
*
* #return ShowEntity
*/
public function setShortname($shortname)
{
$this->shortname = $shortname;
return $this;
}
/**
* Get shortname
*
* #return string
*/
public function getShortname()
{
return $this->shortname;
}
}
And this is the SectionEntity
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* SectionEntity
*
* #ORM\Table(name="section_entity")
* #ORM\Entity(repositoryClass="AppBundle\Repository\SectionEntityRepository")
*/
class SectionEntity
{
protected $section_id;
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="text")
*/
private $section_title;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set sectionTitle
*
* #param string $sectionTitle
*
* #return SectionEntity
*/
public function setSectionTitle($sectionTitle)
{
$this->section_title = $sectionTitle;
return $this;
}
/**
* Get sectionTitle
*
* #return string
*/
public function getSectionTitle()
{
return $this->section_title;
}
/**
* Get string
*/
public function __toString() {
return $this->section_title;
}
function __construct() {
$this->section_id = new \Doctrine\Common\Collections\ArrayCollection();
}
}
Any help would be greatly appreciated, I know it's probably something super simple that I'm just not seeing.
Thanks.
(optional) Rename ShowEntity::$section into ShowEntity::$sections to highlight sections is a collection but not a single entity.
Set ShowEntity __construct method body to:
$this->sections = new \Doctrine\Common\Collections\ArrayCollection();
At ShowAdmin::configureFormFields rename
->add('section_id', EntityType::class, array(
into
->add('section', EntityType::class, array(
You should use direct reference to the relation instead of id.
Remove SectionEntity::__construct method, it has no sense.
Remove protected $section_id; from SectionEntity.
Change public function setSectionId($sectionId) into public function setSection(Section $section).
Perhaps you also need to rename section_title into sectionTitle or simply title, not sure about that.

CollectionType entities issue

Well I'm trying to create a form which can handle multiple file uploads. So far so good. I have two tables image and product which has ManyToOne relationship. I'm using CollecitonType field for the images so I can dynamically add/remove fields on the fly. The problem is that I get this exception on form rendering
Neither the property "imageFile" nor one of the methods "getImageFile()", "imageFile()", "isImageFile()", "hasImageFile()", "__get()" exist and have public access in class "AppBundle\Entity\Product".
What I'm doing wrong?
AppBundle\Entity\Image
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* Product
*
* #ORM\Table()
* #ORM\Entity
*/
class Image
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Product")
*/
protected $product;
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* #Vich\UploadableField(mapping="product_image", fileNameProperty="imageName")
*
* #var File
*/
private $imageFile;
/**
* #ORM\Column(type="string", length=255)
*
* #var string
*/
private $imageName;
/**
* #ORM\Column(type="datetime")
*
* #var \DateTime
*/
private $updatedAt;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* If manually uploading a file (i.e. not using Symfony Form) ensure an instance
* of 'UploadedFile' is injected into this setter to trigger the update. If this
* bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
* must be able to accept an instance of 'File' as the bundle will inject one here
* during Doctrine hydration.
*
* #param File|\Symfony\Component\HttpFoundation\File\UploadedFile $image
*/
public function setImageFile(File $image = null)
{
$this->imageFile = $image;
if ($image) {
// 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
$this->updatedAt = new \DateTime('now');
}
}
/**
* #return File
*/
public function getImageFile()
{
return $this->imageFile;
}
/**
* #param string $imageName
*/
public function setImageName($imageName)
{
$this->imageName = $imageName;
}
/**
* #return string
*/
public function getImageName()
{
return $this->imageName;
}
/**
* 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 product
*
* #param \AppBundle\Entity\Product $product
*
* #return Image
*/
public function setProduct(\AppBundle\Entity\Product $product = null)
{
$this->product = $product;
return $this;
}
/**
* Get product
*
* #return \AppBundle\Entity\Product
*/
public function getProduct()
{
return $this->product;
}
}
AppBundle\Entity\Product
<?php
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection ;
use Doctrine\ORM\Mapping as ORM;
/**
* Product
*
* #ORM\Table()
* #ORM\Entity
*/
class Product
{
/**
* #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;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Product
*/
private $image;
/**
* Constructor
*/
public function __construct()
{
$this->image = new ArrayCollection;
}
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
public function getImages()
{
return $this->images;
}
public function addImage(ImageInterface $image)
{
if (!$this->images->contains($image)) {
$this->images->add($image);
}
return $this;
}
public function removeImage(ImageInterface $image)
{
$this->images->remove($image);
return $this;
}
public function setImages(Collection $images)
{
$this->images = $images;
}
}
AppBundle\Form\ImageType
..............
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('imageFile', 'file')
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Image',
'attr'=> array('novalidate'=>'novalidate')
));
}
.............
AppBundle\Form\ProductType
...........
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'text')
->add('imageFile', 'collection', array(
'allow_add' => true,
'allow_delete' => true,
'required' => false,
'type' => new ImageType(),
'prototype' => true,
'attr' => array(
'class' => 'selection',
),
))
->add('upload', 'submit')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Product',
'attr'=> array('novalidate'=>'novalidate')
));
}
......
First :
In your Product class:
Your variable is named private $image, it should be private $images; with the 'S' for plurial.
Second :
In your product form builder, you ask for a field named ImageFile, but it doesn't exist in Product class (but exists on Image class), it should be images.

Select categories related to current Store entity

I have two entities Store and Category and each Store has it's own categories.
I'd like that when a Store owner's try to add a new category and category_parent, just the categories related to current Store will be displayed.
Right now, all categories are displayed in the select-option.
I'm using Tree Gedmo extension to manage Category entity and I use getChildrenQueryBuilder method to select categories.
How can I modify this method and add my specific constraint ?
$store which is the constraint is declared in the controller action down.
I'd like te set current Category option disabled when he try to add a category_parent, so category and category parent must be differnt.
I hope it's clear
CategoryType.php
<?php
namespace Project\StoreBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
class CategoryType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
//..........
//..........
->add('category', 'entity', array(
'required' => false,
'label' => 'Category parent',
'class' => 'ProjectStoreBundle:Category',
'attr' => array('class' => 'col-sm-8'),
'empty_value' => 'Select one category',
'property' => 'indentedName',
'multiple' => false,
'expanded' => false ,
'query_builder' => function (\Project\StoreBundle\Entity\CategoryRepository $r)
{
return $r->getChildrenQueryBuilder(null, null, 'root', 'asc', false);
}
))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Project\StoreBundle\Entity\Category'
));
}
/**
* #return string
*/
public function getName()
{
return 'project_storebundle_category';
}
}
Entity/Category.php
<?php
namespace Project\StoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Category
* #Gedmo\Tree(type="nested")
* #ORM\Table()
* #ORM\Entity(repositoryClass="Project\StoreBundle\Entity\CategoryRepository")
* #ORM\HasLifeCycleCallbacks()
*/
class Category
{
/**
* #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)
*
*#Assert\NotBlank(message="Please enter the name of categorie.")
*/
private $name;
/**
* #Gedmo\slug(fields={"name"}, unique_base="uniqueBase")
* #ORM\Column(name="slug",length=255, unique=false)
*/
private $slug ;
/**
* #ORM\Column(name="uniqueBase", type="integer")
*/
private $uniqueBase ;
/**
* #ORM\Column(name="description", type="text", nullable=true)
*/
private $description;
/**
* #ORM\Column(name="metaDescription", type="string", length=255, nullable=true)
*
* #Assert\Length(
* max=255,
* maxMessage="message"
* )
*/
private $metaDescription;
/**
* #ORM\Column(name="metaKeywords", type="string", length=255, nullable=true)
*
* #Assert\Length(
* max=255,
* maxMessage="message"
* )
*/
private $metaKeywords;
/**
* #ORM\Column(name="enabled", type="boolean", nullable=false)
*/
private $enabled;
/**
* #Gedmo\TreeLeft
* #ORM\Column(name="lft", type="integer")
*/
private $lft;
/**
* #Gedmo\TreeLevel
* #ORM\Column(name="lvl", type="integer")
*/
private $lvl;
/**
* #Gedmo\TreeRight
* #ORM\Column(name="rgt", type="integer")
*/
private $rgt;
/**
* #Gedmo\TreeRoot
* #ORM\Column(name="root", type="integer", nullable=true)
*/
private $root;
/**
* #Gedmo\TreeParent
* #ORM\ManyToOne(targetEntity="Category", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $parent;
/**
* #ORM\OneToMany(targetEntity="Category", mappedBy="parent")
* #ORM\OrderBy({"lft" = "ASC"})
*/
private $children;
/**
*non mapped property
*/
private $indentedName;
/**
*non mapped property
*/
private $category;
/**
* #ORM\ManyToOne(targetEntity="Project\StoreBundle\Entity\Store", inversedBy="categories", cascade={"persist"})
* #ORM\JoinColumn(nullable=false)
*/
private $store ;
/**
* Constructor
*/
public function __construct()
{
$this->children = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Category
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set slug
*
* #param string $slug
* #return Category
*/
public function setSlug($slug)
{
$this->slug = $slug;
return $this;
}
/**
* Get slug
*
* #return string
*/
public function getSlug()
{
return $this->slug;
}
/**
* Set uniqueBase
*
* #param integer $uniqueBase
* #return Category
*/
public function setUniqueBase($uniqueBase)
{
$this->uniqueBase = $uniqueBase;
return $this;
}
/**
* Get uniqueBase
*
* #return integer
*/
public function getUniqueBase()
{
return $this->uniqueBase;
}
/**
* Set description
*
* #param string $description
* #return Category
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set metaDescription
*
* #param string $metaDescription
* #return Category
*/
public function setMetaDescription($metaDescription)
{
$this->metaDescription = $metaDescription;
return $this;
}
/**
* Get metaDescription
*
* #return string
*/
public function getMetaDescription()
{
return $this->metaDescription;
}
/**
* Set metaKeywords
*
* #param string $metaKeywords
* #return Category
*/
public function setMetaKeywords($metaKeywords)
{
$this->metaKeywords = $metaKeywords;
return $this;
}
/**
* Get metaKeywords
*
* #return string
*/
public function getMetaKeywords()
{
return $this->metaKeywords;
}
/**
* Set enabled
*
* #param boolean $enabled
* #return Category
*/
public function setEnabled($enabled)
{
$this->enabled = $enabled;
return $this;
}
/**
* Get enabled
*
* #return boolean
*/
public function getEnabled()
{
return $this->enabled;
}
/**
* Set parent
*
* #param \Project\StoreBundle\Entity\Category $parent
* #return Category
*/
public function setParent(\Project\StoreBundle\Entity\Category $parent = null)
{
$this->parent = $parent;
return $this;
}
/**
* Get parent
*
* #return \Project\StoreBundle\Entity\Category
*/
public function getParent()
{
return $this->parent;
}
/**
* Set lft
*
* #param integer $lft
* #return Category
*/
public function setLft($lft)
{
$this->lft = $lft;
return $this;
}
/**
* Get lft
*
* #return integer
*/
public function getLft()
{
return $this->lft;
}
/**
* Set lvl
*
* #param integer $lvl
* #return Category
*/
public function setLvl($lvl)
{
$this->lvl = $lvl;
return $this;
}
/**
* Get lvl
*
* #return integer
*/
public function getLvl()
{
return $this->lvl;
}
/**
* Set rgt
*
* #param integer $rgt
* #return Category
*/
public function setRgt($rgt)
{
$this->rgt = $rgt;
return $this;
}
/**
* Get rgt
*
* #return integer
*/
public function getRgt()
{
return $this->rgt;
}
/**
* Set root
*
* #param integer $root
* #return Category
*/
public function setRoot($root)
{
$this->root = $root;
return $this;
}
/**
* Get root
*
* #return integer
*/
public function getRoot()
{
return $this->root;
}
/**
* Add children
*
* #param \Project\StoreBundle\Entity\Category $children
* #return Category
*/
public function addChild(\Project\StoreBundle\Entity\Category $children)
{
$this->children[] = $children;
return $this;
}
/**
* Remove children
*
* #param \Project\StoreBundle\Entity\Category $children
*/
public function removeChild(\Project\StoreBundle\Entity\Category $children)
{
$this->children->removeElement($children);
}
/**
* Get children
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getChildren()
{
return $this->children;
}
/**
* Get IndentedName
*
*/
public function getIndentedName()
{
return str_repeat("-----", $this->lvl).$this->name;
}
/**
* Get category
*
*/
public function getCategory()
{
return $this->category;
}
/**
* Set store
*
* #param \Project\StoreBundle\Entity\Store $store
* #return Category
*/
public function setStore(\Project\StoreBundle\Entity\Store $store = null)
{
$this->store = $store;
return $this;
}
/**
* Get store
*
* #return \Project\StoreBundle\Entity\Store
*/
public function getStore()
{
return $this->store;
}
}
Controller
/**
* Create a new Category entity.
*
*/
/**
* #ParamConverter("store", options={"mapping": {"store_id":"id"}})
*/
public function newAction(Store $store)
{
// keep in mind, this will call all registered security voters
if (false === $this->get('security.context')->isGranted('edit', $store)) {
throw new AccessDeniedException('Unauthorised access!');
}
$category = new Category();
$category->setStore($store);
$category->setUniqueBase($store->getId());
$form = $this->createForm(new CategoryType(), $category);
$request = $this->getRequest();
if ($request->getMethod() == 'POST')
{
$form->bind($request);
if ($form->isValid())
{
$em = $this->getDoctrine()->getManager();
$em->persist($category);
$em->flush();
$this->get('session')->getFlashBag()->add('message', 'Category enregistred');
return $this->redirect( $this->generateUrl('dashboard_category_index', array('store_id' => $store->getId())));
}
}
return $this->render('ProjectDashboardBundle:Category:new.html.twig',
array(
'form' => $form->createView() ,
'store' =>$store,
));
}
I managed to pass the parameter $store to the form, but I don't know how to use it as a constraint in getChildrenQueryBuilder method.
Should I create a new custom method? I prefer to use getChildrenQueryBuilder if it is possible.
Here is the new code
CategoryType.php
class CategoryType extends AbstractType
{
private $store;
public function __construct($store)
{
$this->store = $store;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$store = $this->store;
$builder
//...........
//...........
->add('parent', 'entity', array(
'required' => false,
'label' => 'Category parent',
'class' => 'ProjectStoreBundle:Category',
'attr' => array('class' => 'col-sm-8'),
'empty_value' => 'Select one category',
'property' => 'indentedName',
'multiple' => false,
'expanded' => false ,
'query_builder' => function (\Project\StoreBundle\Entity\CategoryRepository $r) use ($store)
{
return $r->getChildrenQueryBuilder(null, null, 'root', 'asc', false);
}
))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Project\StoreBundle\Entity\Category'
));
}
/**
* #return string
*/
public function getName()
{
return 'project_storebundle_category';
}
}
Controller
/**
* #ParamConverter("store", options={"mapping": {"store_id":"id"}})
*/
public function newAction(Store $store)
{
// keep in mind, this will call all registered security voters
if (false === $this->get('security.context')->isGranted('edit', $store)) {
throw new AccessDeniedException('Unauthorised access!');
}
$category = new Category();
$category->setStore($store);
$category->setUniqueBase($store->getId());
$form = $this->createForm(new CategoryType($store), $category);
$request = $this->getRequest();
if ($request->getMethod() == 'POST')
{
$form->bind($request);
if ($form->isValid())
{
$em = $this->getDoctrine()->getManager();
$em->persist($category);
$em->flush();
$this->get('session')->getFlashBag()->add('message', 'Category enregistred');
return $this->redirect( $this->generateUrl('dashboard_category_index', array('store_id' => $store->getId())));
}
}
return $this->render('ProjectDashboardBundle:Category:new.html.twig',
array(
'form' => $form->createView() ,
'store' =>$store,
));
}
You're not saying which Symfony2 version you're using, but generically speaking, you just need to find a way to pass the current store to your form builder and use it in the method that filters the possible categories.
Take a look at this one, or this one, basically you just need to inject the $store into your form builder, and then you (almost) have it :-)

Categories