Sonata admin with a2lix_translatable. How to do relationships? - symfony

I want to create table Pracownik which can contain zero or few instances of Zaklady and control it using Sonata Admin. Both of tables are translated using a2lix_translatable.
So I have classes:
<?php
namespace JCuryllo\InstituteBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
/**
* Pracownik
*
* #ORM\Table()
* #ORM\Entity
*/
class Pracownik
{
use ORMBehaviors\Translatable\Translatable;
public function __call($method, $arguments)
{
return $this->proxyCurrentLocaleTranslation($method, $arguments);
}
/**
* #ORM\ManyToOne(targetEntity="ZakladyTranslation")
**/
private $zaklady;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* and other properties
*/
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/* and other getters and setter */
}
And Translation:
<?php
namespace JCuryllo\InstituteBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
/**
* #ORM\Entity
*/
class PracownikTranslation
{
use ORMBehaviors\Translatable\Translation;
public function __toString()
{
return $this->getTitle();
}
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/* and other properties */
/**
* Set name
*
* #param string $name
* #return Pracownik
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/* and other getters and setter *.
}
I've used very similar code in Zaklady and ZakladyTranslation. Then in PracownikAdmin I try to do sth like:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('zaklady', 'many_to_one',array(
'required' => false,
))
->add('translations', 'a2lix_translations')
;
}
but it doesn't work (error: Could not load type "many_to_one").

many_to_one is not an available form type for Sonata, you have to use sonata_type_model or sonata_type_model_list for a many to one relation.
Example:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('zaklady', 'sonata_type_model_list',array(
'required' => false,
))
->add('translations', 'a2lix_translations');
}
Documentation : http://sonata-project.org/bundles/doctrine-orm-admin/master/doc/reference/form_field_definition.html#advanced-usage-many-to-one

Ok, I have done it.
In Zaklady I added:
public function __toString()
{
return $this->getTitle(); // getTitle is in ZakladyTranslation!
}
In Pracownik I changed:
/**
* #ORM\ManyToOne(targetEntity="ZakladyTranslation")
**/
private $zaklady;
to:
/**
* #ORM\ManyToOne(targetEntity="Zaklady")
**/
private $zaklady;
and PracownikAdmin:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('translations', 'a2lix_translations')
->add('zaklady', 'sonata_type_model', array(
'multiple' => true,
'by_reference' => false,
'required' => false
))
;
}

Related

Symfony 4 - Can't create Entity

I'm using Sonata Admin for the first time, I followed doc online and my admin works well, except I have an error when I try to create elements via my admin:
Failed to create object: App\Entity\HomeBlockElement
I have my HomeBlockElement
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\HomeBlockElementRepository")
* #ORM\Table(name="home_block_element")
*/
class HomeBlockElement
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(name="home_element_id",type="integer")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="home_element_title", type="string", length=255)
*/
protected $title;
/**
* #var string
*
* #ORM\Column(name="home_element_text_link", type="string", length=255)
*/
protected $textLink;
/**
* #var string
*
* #ORM\Column(name="home_element_shopping_link", type="string", length=255)
*/
protected $shoppingLink;
/**
* #var string
*
* #ORM\Column(name="home_element_marketing_etiquette", type="string", length=255)
*/
protected $marketingEtiquette;
/**
* #var string
*
* #ORM\Column(name="home_element_media", type="text")
*/
protected $media;
/**
* #var bool
*
* #ORM\Column(name="home_element_published", type="boolean")
*/
protected $published = false;
public function getId(): ?int
{
return $this->id;
}
/**
* #return string
*/
public function getTitle(): ?string
{
return $this->title;
}
/**
* #param string $title
* #return HomeBlockElement
*/
public function setTitle(?string $title): HomeBlockElement
{
$this->title = $title;
return $this;
}
/**
* #return string
*/
public function getTextLink(): ?string
{
return $this->textLink;
}
/**
* #param string $textLink
* #return HomeBlockElement
*/
public function setTextLink(?string $textLink): HomeBlockElement
{
$this->textLink = $textLink;
return $this;
}
/**
* #return string
*/
public function getShoppingLink(): ?string
{
return $this->shoppingLink;
}
/**
* #param string $shoppingLink
* #return HomeBlockElement
*/
public function setShoppingLink(?string $shoppingLink): HomeBlockElement
{
$this->shoppingLink = $shoppingLink;
return $this;
}
/**
* #return string
*/
public function getMarketingEtiquette(): ?string
{
return $this->marketingEtiquette;
}
/**
* #param string $categoryLink
* #return HomeBlockElement
*/
public function setMarketingEtiquette(?string $marketingEtiquette): HomeBlockElement
{
$this->marketingEtiquette = $marketingEtiquette;
return $this;
}
/**
* #return bool
*/
public function isPublished(): bool
{
return $this->published;
}
/**
* #param bool $published
* #return Page
*/
public function setPublished(bool $published): HomeBlockElement
{
$this->published = $published;
return $this;
}
}
And my HomeBlockElementAdmin:
<?php
namespace App\Admin;
use App\Entity\Page;
use FOS\CKEditorBundle\Form\Type\CKEditorType;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Route\RouteCollection;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
final class HomeBlockElementAdmin extends AbstractAdmin
{
protected $datagridValues = array(
'_sort_order' => 'ASC',
'_sort_by' => 'title',
);
/**
* #param $object
* #return string|null
*/
public function toString($object): ?string
{
return $object instanceof Page && $object->getTitle()
? $object->getTitle()
: 'Nouveau bloc élément';
}
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('Contenu')
->add('published', CheckboxType::class, ['required' => false, 'label' => 'Publier'])
->add('title', TextType::class, ['required' => true, 'label' => 'Titre'])
->add('marketingEtiquette', TextType::class, ['required' => false, 'label' => 'Etiquette Marketing'])
->add('textLink', TextType::class, ['required' => true, 'label' => 'Texte'])
->add('shoppinglink', TextType::class, ['required' => true, 'label' => 'Lien'])
->end();
}
protected function configureListFields(ListMapper $listMapper)
{
unset($this->listModes['mosaic']);
$listMapper
->addIdentifier('title')
->addIdentifier('marketingEtiquette')
->addIdentifier('textLink')
->addIdentifier('shoppinglink')
->addIdentifier('published')
;
}
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('title')
->add('marketingEtiquette')
->add('textLink')
->add('shoppinglink')
->add('published');
}
/**
* #param RouteCollection $collection
*/
protected function configureRoutes(RouteCollection $collection)
{
$collection
->remove('delete')
//->remove('create')
->add('move', $this->getRouterIdParameter() . '/move/{position}');
}
}
I then defined this in my services.yaml
admin.element:
class: App\Admin\HomeBlockElementAdmin
arguments: [~, App\Entity\HomeBlockElement, ~]
tags:
- { name: sonata.admin, manager_type: orm, label: 'Element blocs', group: 'app.admin.group.home' }
I've updated the data base with php bin/console doctrine:schema:update to make it match with my code and everything worked fine.From what I saw on the Internet, the error is related to SQL but
I can't see where the error comes from.
In the breadrcumb from the error I can see:
PDOException > PDOException > NotNullConstraintViolationException > ModelManagerException
So the error is because something is null when it shouldn't? But the only thing that is required is the id, and it should be generated automatically... I don't know what to do

Symfony 3 One To Many: how to add inverse side to owning side when persisting inverse side

I have these entities with one to many relationships:
freightOrder has many shipments
shipment has many shipmentLines
The freightOrder entity looks like this:
<?php
namespace Fraktportalen\Bundle\ShipmentBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation\ExclusionPolicy;
use JMS\Serializer\Annotation\Expose;
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
* #ExclusionPolicy("all")
*/
class FreightOrder
{
/**
* #Expose
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #Expose
*
* #ORM\OneToMany(
* targetEntity="Fraktportalen\Bundle\ShipmentBundle\Entity\Shipment",
* mappedBy="freightOrder",
* cascade={"persist","remove"}
* )
*/
private $shipments;
/**
* #Expose
*
* #ORM\ManyToOne(targetEntity="Fraktportalen\Bundle\AccountBundle\Entity\Account")
* #ORM\JoinColumn(name="freighter_id", referencedColumnName="id")
*/
private $freighter;
/**
* Order constructor.
*/
public function __construct()
{
$this->shipments = new ArrayCollection();
}
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #return mixed
*/
public function getShipments()
{
return $this->shipments;
}
/**
* #param mixed $shipment
*/
public function addShipment(Shipment $shipment)
{
$shipment->setFreightOrder($this);
$this->shipments->add($shipment);
}
public function removeShipment(Shipment $shipment)
{
$this->shipments->removeElement($shipment);
}
/**
* #return mixed
*/
public function getFreighter()
{
return $this->freighter;
}
/**
* #param mixed $freighter
*/
public function setFreighter($freighter)
{
$this->freighter = $freighter;
}
}
The shipment entity:
<?php
namespace Fraktportalen\Bundle\ShipmentBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Fraktportalen\Bundle\AccountBundle\Entity\Account;
use Fraktportalen\Bundle\AddressBundle\Entity\Address;
use JMS\Serializer\Annotation\ExclusionPolicy;
use JMS\Serializer\Annotation\Expose;
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
* #ExclusionPolicy("all")
*/
class Shipment
{
/**
* #Expose
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #Expose
*
* #ORM\Column(type="datetime", nullable=true)
*/
private $pickup;
/**
* #Expose
*
* #ORM\Column(type="datetime", nullable=true)
*/
private $delivery;
/**
* #Expose
*
* #ORM\OneToMany(
* targetEntity="Fraktportalen\Bundle\ShipmentBundle\Entity\ShipmentLine",
* mappedBy="shipment",
* fetch="EAGER",
* cascade={"persist"}
* )
*/
private $shipmentLines;
/**
* #Expose
*
* #ORM\ManyToOne(targetEntity="Fraktportalen\Bundle\ShipmentBundle\Entity\FreightOrder", inversedBy="shipments")
* #ORM\JoinColumn(name="freight_order_id", referencedColumnName="id", nullable=false)
*/
private $freightOrder;
/**
* #Expose
*
* #ORM\ManyToOne(targetEntity="Fraktportalen\Bundle\AccountBundle\Entity\Account")
* #ORM\JoinColumn(name="sender_id", referencedColumnName="id")
*/
private $sender;
/**
* #Expose
*
* #ORM\ManyToOne(targetEntity="Fraktportalen\Bundle\AccountBundle\Entity\Account")
* #ORM\JoinColumn(name="receiver_id", referencedColumnName="id")
*/
private $receiver;
/**
* Shipment constructor.
*/
public function __construct()
{
$this->shipmentLines = new ArrayCollection();
}
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #return mixed
*/
public function getFreightOrder() {
return $this->freightOrder;
}
/**
* #param mixed $freightOrder
*
* #return Shipment
*/
public function setFreightOrder($freightOrder) {
$this->freightOrder = $freightOrder;
$freightOrder->addShipment($this);
return $this;
}
/**
* #return mixed
*/
public function getPickup()
{
return $this->pickup;
}
/**
* #param mixed $pickup
*/
public function setPickup(\DateTime $pickup = null)
{
$this->pickup = $pickup;
}
/**
* #return mixed
*/
public function getDelivery()
{
return $this->delivery;
}
/**
* #param mixed $delivery
*/
public function setDelivery(\DateTime $delivery = null)
{
$this->delivery = $delivery;
}
/**
* #return mixed
*/
public function getShipmentLines()
{
return $this->shipmentLines;
}
/**
* #param mixed $shipmentLine
*/
public function addShipmentLine(ShipmentLine $shipmentLine)
{
$shipmentLine->setShipment($this);
$this->shipmentLines->add($shipmentLine);
}
public function removeShipmentLine(ShipmentLine $shipmentLine)
{
$this->shipmentLines->removeElement($shipmentLine);
}
/**
* #return mixed
*/
public function getSender()
{
return $this->sender;
}
/**
* #param mixed $sender
*/
public function setSender(Account $sender)
{
$this->sender = $sender;
}
/**
* #return mixed
*/
public function getReceiver()
{
return $this->receiver;
}
/**
* #param mixed $receiver
*/
public function setReceiver(Account $receiver)
{
$this->receiver = $receiver;
}
}
The freightOrderType next:
<?php
namespace Fraktportalen\Bundle\ShipmentBundle\Form;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class FreightOrderType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('freighter', EntityType::class, array(
'class' => 'AccountBundle:Account',
))
->add('shipments', CollectionType::class, array(
'entry_type' => ShipmentType::class,
'allow_add' => true
))
;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Fraktportalen\Bundle\ShipmentBundle\Entity\FreightOrder',
'csrf_protection' => false
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'freightOrder';
}
}
When saving a freightOrder from an Ember app, it sends this json:
{
"freightOrder": {
"freighter": "3",
"shipments": [{
"pickup": "2017-03-22 12:32:00",
"delivery": "2017-03-23 12:32:00",
"sender": "1",
"receiver": "2",
"shipment_lines": [{
"package_weight": 45,
"package_length": 240,
"package_width": 120,
"package_height": 240,
"package_type": "3"
}]
}]
}
}
Everything works as expected except that freightOrder on shipments is not saved. Even though I add it on the freightOrder entity addShipment function:
/**
* #param mixed $shipment
*/
public function addShipment(Shipment $shipment)
{
$shipment->setFreightOrder($this);
$this->shipments->add($shipment);
}
As you can see in the shipment entity, I have even tried to add shipment to freightOrder there. But nothing works.
Shipment lines are added to shipment though, and that is also a one to many relationship.
Will I have to add a form event listener to the freightOrderType and manually add freightOrder to all shipments? I was under the impression that adding freightOrder to shipments when adding said shipments to freightOrder would solve it. At least that is what I have found when searching SO.
Thanks for your time,
Tommy
I feel your pain, I fight with this problem quite often.
It sounds like the setter isnt getting called. You can sometimes fix this by declaring by_reference to false.
try this..
$builder
->add('freighter', EntityType::class, array(
'class' => 'AccountBundle:Account',
))
->add('shipments', CollectionType::class, array(
'entry_type' => ShipmentType::class,
'allow_add' => true,
'by_reference' => false,
))
;
reading the docs:
Similarly, if you're using the CollectionType field where your underlying collection data is an object (like with Doctrine's ArrayCollection), then by_reference must be set to false if you need the adder and remover (e.g. addAuthor() and removeAuthor()) to be called.
Personally, I still think by_reference is some kind of hacky fix for a bug they had once.. I guess we'll never know..

Symfony: ManyToMany relationship between Sonata User and my bundle Entity

I'm trying to create a ManyToMany relationship between Sonata's User and an entity called "Network" that resides in my bundle.
The idea is to be able to assign multiple Networks to each user via the User creation/edition of SonataAdmin.
Here's what I've got:
User Entity:
<?php
namespace Application\Sonata\UserBundle\Entity;
use Sonata\UserBundle\Entity\BaseUser as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
class User extends BaseUser
{
/**
* #var integer $id
*/
protected $id;
/**
* #ORM\ManyToMany(targetEntity="\Acme\TestBundle\Entity\Network", mappedBy="users", cascade={"persist", "remove"})
*/
protected $networks;
public function __construct()
{
parent::__construct();
$this->networks = new ArrayCollection();
}
/**
* Get id
*
* #return integer $id
*/
public function getId()
{
return $this->id;
}
/**
* Add network
*
* #param \Acme\TestBundle\Entity\Network $network
* #return User
*/
public function addNetwork($network)
{
$this->networks[] = $network;
$network->addUser($this);
return $this;
}
/**
* Remove network
*
* #param \Acme\TestBundle\Entity\Network $network
*/
public function removeNetwork($network)
{
$this->networks->removeElement($network);
$network->removeUser($this);
}
/**
* Get networks
*
* #return \Doctrine\Common\Collections\ArrayCollection
*/
public function getNetworks()
{
return $this->networks;
}
}
Network Entity:
<?php
namespace Acme\TestBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
*/
class Network
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255)
*/
protected $name;
/**
* #ORM\ManyToMany(targetEntity="\Application\Sonata\UserBundle\Entity\User", inversedBy="networks")
* #ORM\JoinTable(name="Networks_Users")
*/
protected $users;
public function __construct()
{
$this->users = new ArrayCollection();
}
public function __toString()
{
return $this->getName();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Network
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Add user
*
* #param \Application\Sonata\UserBundle\Entity\User $user
* #return Network
*/
public function addUser($user)
{
$this->users[] = $user;
return $this;
}
/**
* Remove user
*
* #param \Application\Sonata\UserBundle\Entity\User $user
*/
public function removeUser($user)
{
$this->users->removeElement($user);
}
/**
* Get users
*
* #return \Doctrine\Common\Collections\ArrayCollection
*/
public function getUsers()
{
return $this->users;
}
}
app\config.yml
sonata_user:
admin:
user:
class: Acme\TestBundle\Admin\UserAdmin
#...#
User Admin class:
<?php
namespace Acme\TestBundle\Admin;
use Sonata\UserBundle\Admin\Model\UserAdmin as SonataUserAdmin;
class UserAdmin extends SonataUserAdmin
{
/**
* {#inheritdoc}
*/
protected function configureFormFields(\Sonata\AdminBundle\Form\FormMapper $formMapper)
{
parent::configureFormFields($formMapper);
$formMapper
->with('Networks')
->add('networks', 'sonata_type_model', array(
'by_reference' => false,
'required' => false,
'expanded' => false,
'multiple' => true,
'label' => 'Choose the user Networks',
'class' => 'AcmeTestBundle:Network'
))
->end();
}
}
Here's the problem:
When I edit an existing User via SonataAdmin and assign certain Networks to it changes are persisted to the database but the selector appears empty, as if no Networks were ever assigned. If I try to assign Networks again, I get a database constraint violation message (as it would be expected).
I discovered that for some reason the $networks ArrayCollection is returning NULL, as if the relationship could not be established from the User side:
$usr = $this->getUser();
$selected_networks = $usr->getNetworks(); // returns NULL
If I attempt to manage Users from the Network side, everything works fine (with no additions or changes to the actual code):
<?php
namespace Acme\TestBundle\Admin;
use Sonata\AdminBundle\Form\FormMapper;
class NetworkAdmin extends Admin
{
/**
* #param \Sonata\AdminBundle\Form\FormMapper $formMapper
*
* #return void
*/
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('General')
->add('name')
->end()
->with('Users')
->add('users', 'sonata_type_model', array(
'by_reference' => false,
'required' => false,
'expanded' => false,
'multiple' => true,
'label' => 'Choose the network Users',
'class' => 'ApplicationSonataUserBundle:User'
))
;
}
}
I'm stumped. Any ideas?
Thank you all.

Embedded forms of inherited doctrine's entities

I'm building a form generator using Symfony 2.2 with Doctrine. The base principle is that the user create a new form by filling its name and selecting the widgets he'd likes to have in a select menu.
We can think of WidgetInputText, WidgetSelect, WidgetFile, etc.
Here is an example of my model:
<?php
namespace Ineat\FormGeneratorBundle\Entity\Widget;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
/**
* Widget
*
* #ORM\Table(name="widget")
* #ORM\Entity
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="discr", type="string")
* #ORM\DiscriminatorMap({"widget_text" = "WidgetText", "widget_input_text" = "WidgetInputText", "widget_select" = "WidgetSelect"})
*/
abstract class Widget
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Form", inversedBy="widgets")
*/
private $form;
/**
* #var integer
*
* #ORM\OneToOne(targetEntity="Question")
*/
private $question;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set form
*
* #param \Ineat\FormGeneratorBundle\Entity\Form $form
* #return Widget
*/
public function setForm(\Ineat\FormGeneratorBundle\Entity\Form $form = null)
{
$this->form = $form;
return $this;
}
/**
* Get form
*
* #return \Ineat\FormGeneratorBundle\Entity\Form
*/
public function getForm()
{
return $this->form;
}
/**
* Set question
*
* #param \Ineat\FormGeneratorBundle\Entity\Question $question
* #return Widget
*/
public function setQuestion(\Ineat\FormGeneratorBundle\Entity\Question $question = null)
{
$this->question = $question;
return $this;
}
/**
* Get question
*
* #return \Ineat\FormGeneratorBundle\Entity\Question
*/
public function getQuestion()
{
return $this->question;
}
}
<?php
namespace Ineat\FormGeneratorBundle\Entity\Widget;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
/**
* Widget
*
* #ORM\Entity
* #ORM\Table(name="widget_text")
*/
class WidgetText extends Widget
{
/**
* #var string
*
* #ORM\Column(type="text")
*/
private $text;
/**
* Set text
*
* #param string $text
* #return WidgetText
*/
public function setText($text)
{
$this->text = $text;
return $this;
}
/**
* Get text
*
* #return string
*/
public function getText()
{
return $this->text;
}
}
<?php
namespace Ineat\FormGeneratorBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Form
*
* #ORM\Table(name="form")
* #ORM\Entity(repositoryClass="Ineat\FormGeneratorBundle\Entity\FormRepository")
* #UniqueEntity("name")
* #UniqueEntity("slug")
*/
class Form
{
/**
* #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;
/**
* #var string
*
* #ORM\Column(name="slug", type="string", length=255)
*/
private $slug;
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Widget", mappedBy="form", cascade={"persist"})
*/
private $widgets;
public function __construct()
{
$this->widgets = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Form
*/
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 Form
*/
public function setSlug($slug)
{
$this->slug = $slug;
return $this;
}
/**
* Get slug
*
* #return string
*/
public function getSlug()
{
return $this->slug;
}
/**
* Add widgets
*
* #param \Ineat\FormGeneratorBundle\Entity\Widget\Widget $widget
* #return Form
*/
public function addWidget(\Ineat\FormGeneratorBundle\Entity\Widget\Widget $widget)
{
$this->widgets[] = $widget;
return $this;
}
/**
* Remove widgets
*
* #param \Ineat\FormGeneratorBundle\Entity\Widget\Widget $widget
*/
public function removeWidget(\Ineat\FormGeneratorBundle\Entity\Widget\Widget $widget)
{
$this->widgets->removeElement($widget);
}
/**
* Get widgets
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getWidgets()
{
return $this->widgets;
}
/**
* Set widgets
*
* #param \Doctrine\Common\Collections\Collection $widgets
*/
public function setWidget(\Doctrine\Common\Collections\Collection $widgets)
{
$this->widgets = $widgets;
}
public function __set($name, $obj)
{
if (is_a($obj, '\Ineat\FormGeneratorBundle\Entity\Widget\Widget')) {
$this->addWidget($obj);
}
}
}
As you can see a form can have multiple widgets attached to him.
I've made an abstract class Widget because all widgets have common fields and are of type Widget and because in the Form entity it seems really really bad to had one collection per Widget type (bad and boring).
This model works, I've unit tested it and I'm able to attach a WidgetText to a form and then retrieve it.
The issue comes when I try to use forms with it.
<?php
namespace Ineat\FormGeneratorBundle\Form;
use Ineat\FormGeneratorBundle\Entity\Widget\WidgetText;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class FormType extends AbstractType
{
protected $widgets;
public function __construct(array $widgets = array())
{
$this->widgets = $widgets;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'text')
->add('slug', 'text')
->add('WidgetText', 'collection', array(
'type' => new WidgetTextType(),
'allow_add' => true,
'attr' => array('class' => 'widget-text'),
'by_reference' => false
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Ineat\FormGeneratorBundle\Entity\Form',
));
}
public function getName()
{
return 'ineat_formgeneratorbundle_formtype';
}
}
<?php
namespace Ineat\FormGeneratorBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class WidgetTextType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('text', 'text')
;
}
public function getName()
{
return 'ineat_formgeneratorbundle_widgettexttype';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Ineat\FormGeneratorBundle\Entity\Widget\WidgetText',
));
}
}
When I try to display the form I've got the following error:
Neither property "WidgetText" nor method "getWidgetText()" nor method "isWidgetText()" exists in class "Ineat\FormGeneratorBundle\Entity\Form"
It's like Symfony doesn't know that my WidgetText is of type Widget too.
If in the controller (generated by Symfony) I change this line:
$this->createForm(new FormType(), new Form())
To:
$this->createForm(new FormType())
The form displays well but when submitting I've got no data binded.
I'm completly stuck there, from an OOP point of view I think this should work but I'm not sure if Symfony allows me to do what I want.
As mentioned in the comments of your question, you should change the name of the "WidgetText" field to "widgets". The reason behind that is that the names of the fields should match the accessors in your model (i.e. "name" for (set|get)Name(), "widgets" for (set|get)Widgets() etc.)
If you really want the name of a field to differ from the accessor in the model, you can also use the "property_path" option (which is set to the field's name by default):
$builder->add('WidgetText', ..., array(
...
'property_path' => 'widgets',
));

Symfony2 - Doctrine2 - ManyToOne relationship / Expected argument of type "Doctrine\Common\Collections\Collection", "Proxies\...Entity...Proxy" given

I'm trying to build the "edit" controller/view but i encounter this error. From i've read it could be an asociation problem, and i've tried to fix it but i don't know what it's wrong with it. It supposed to return a collection but i don't even know what "proxy" means and how it's getting it.
The entity for which i'm building the edit view:
<?php
namespace Monse\WebBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Monse\WebBundle\Entity\BasedeDatos
*
* #ORM\Table()
* #ORM\Entity
*/
class BasedeDatos
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Monse\WebBundle\Entity\ServidoresBD")
* #ORM\JoinColumn(name="servidores_id", referencedColumnName="id",nullable=false)
*
*/
private $servidorBD;
/**
* #var string $nombreBD
*
* #ORM\Column(name="nombreBD", type="string", length=255,unique=true)
*/
private $nombreBD;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set servidorBD
*
* #param integer $servidorBD
*/
public function setServidorBD(\Monse\WebBundle\Entity\ServidoresBD $servidorBD)
{
$this->servidorBD = $servidorBD;
}
/**
* Get servidorBD
*
* #return integer
*/
public function getServidorBD()
{
return $this->servidorBD;
}
/**
* Set nombreBD
*
* #param string $nombreBD
*/
public function setNombreBD($nombreBD)
{
$this->nombreBD = $nombreBD;
}
/**
* Get nombreBD
*
* #return string
*/
public function getNombreBD()
{
return $this->nombreBD;
}
public function __construct()
{
$this->servidorBD = new Doctrine\Common\Collections\ArrayCollection();
}
}
FormType:
<?php
namespace Monse\WebBundle\Form;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class BasedeDatosType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('ServidorBD','entity',
array ('class' => 'MonseWebBundle:ServidoresBD',
'multiple' => true,
'required' => true,
'label' => 'Servidor de Base de Datos: ',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('u')
->orderBy('u.url', 'ASC');
},
))
->add('nombreBD','text', array( 'required' => true, 'label' => 'Nombre Base de Datos: '))
->add('UsuarioBD','entity',array('class' => 'MonseWebBundle:UsuariosBD','multiple' => true,
'required' => true, 'label' => 'Usuario Asociado: ',
'property_path' => false,
'query_builder' => function (EntityRepository $er){
return $er->createQueryBuilder('s')
->orderBy ('s.usuario','ASC'); },))
;
}
public function getName()
{
return 'basededatos';
}
}
The entity responsible for the problem (i think):
<?php
namespace Monse\WebBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Monse\WebBundle\Entity\ServidoresBD
*
* #ORM\Table()
* #ORM\Entity
*/
class ServidoresBD
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $url
*
* #ORM\Column(name="url", type="string", length=255)
*/
private $url;
/**
*
* #ORM\OneToOne (targetEntity="Monse\WebBundle\Entity\Dominios")
* #ORM\JoinColumn(name="dominio_id", referencedColumnName="id",nullable=false,unique=true)
*/
private $dominio;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set dominio
*
* #param integer $dominio
*/
public function setDominio(\Monse\WebBundle\Entity\Dominios $dominio)
{
$this->dominio = $dominio;
}
/**
* Get dominio
*
* #return integer
*/
public function getDominio()
{
return $this->dominio;
}
/**
* Set url
*
* #param string $url
*/
public function setUrl($url)
{
$this->url = $url;
}
/**
* Get url
*
* #return string
*/
public function getUrl()
{
return $this->url;
}
public function __toString()
{
return $this->getUrl();
}
}
Form Type:
<?php
namespace Monse\WebBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class ServidoresBDType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('url')
->add('dominio', 'collection', array('type' => new DominiosType()));
;
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Monse\WebBundle\Entity\ServidoresBD'
);
}
public function getName()
{
return 'issue_selector';
}
}
Where should i add the arraycollection to be returned? How do i prevent this from happening again? I'm sorry if this is an stupid question, i'm trying to learn.
I changed the two "multiple" attributes to false in the first form type.

Resources