Create form with two choices fileds - symfony

I want create two choces field like that, because if i generate form i have problem with __toString() method, because i need to fields passed to the function.
/**
* #Route("/transport", name="transportAction")
* #Template("CoreBundle:Goods:transport.html.twig")
*/
public function transportAction()
{
$storageItems = new StorageItems();
$form = $this->createFormBuilder($storageItems)
->add('storageitems_to_deliveries', 'entity', [
'class' => 'ModelBundle:Deliveries',
'choices' => $storageItems->getStorageitemsToDeliveries()
])
->add('storageitems_to_strorage', 'entity', [
'class' => 'ModelBundle:Storages',
'choices' => $this->getDoctrine()->getRepository('ModelBundle:Storages')->findAll(),
//'property'=> 'secondStorage'
])
->getForm();
return array(
'form' => $form->createView()
);
}
In result i have error
Warning: Illegal offset type
What i do wrong? How fixed it?
P.S
class Deliveries
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Items", inversedBy="items_to_deliveries")
* #ORM\JoinColumn(name="items_to_deliveries_id", referencedColumnName="id", nullable=false)
*/
private $deliveries_to_items;
/**
* #ORM\ManyToOne(targetEntity="Workers", inversedBy="workers_to_deliveries")
* #ORM\JoinColumn(name="workers_to_deliveries_id", referencedColumnName="id", nullable=false)
*/
private $deliveries_to_workers;
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="StorageItems", mappedBy="storageitems_to_deliveries", cascade={"remove"})
*/
private $deliveries_to_storageitems;
public function __construct()
{
$this->deliveries_to_storageitems = new ArrayCollection();
}
public function __toString()
{
return $this->deliveries_to_workers;
}

I see three issues here (2 major, 1 minor):
$this->getDoctrine()->getRepository('ModelBundle:Deliveries')
This will return an array of Deliveries objects. Symfony2 has no way of determining the textual representation needed for label, unless you implement __toString() (major)
In addition, array will be numerically indexed. The values for form elements will be 0, 1, etc... This can cause some inexplicable behaviors and headaches along the way. You should consider returning array which uses object ID as it's key. (minor issue)
'data' => ''
Hit this wall a few times. data attribute will override everything and anything, be it a default entity member value or the value that came from Request. Be careful when using it ;) (major)
Hope this helps...

Related

Symfony 3.1 Getting Id from another entity in a form modifier

I have this in a form builder:
$form->add('region', EntityType::class, array(
'class' => 'AppBundle:Regions',
'placeholder' => '',
'required' => false,
'query_builder' => function (EntityRepository $er) use ($country_2a) {
return $er->createQueryBuilder('s')
->where('s.countryId= :country_id')
->setParameter('country_id', $country_2a)
->addOrderBy('s.name');
}
));
The case is that in the entity Regions I have the country_id but I receive the country_2a, then before running the createBuilder I need to search in the entity Countries which is the id for the code_2a.
I have declared in the entities this relations:
Countries Entity:
/**
* #ORM\OneToMany(targetEntity="Regions", mappedBy="country_id")
*/
private $regions;
public function __construct() {
$this->regions = new ArrayCollection();
}
Regions Entity:
/**
* #var int
*
* #ORM\Column(name="country_id", type="integer")
* #ORM\ManyToOne(targetEntity="Countries", inversedBy="id")
* #ORM\JoinColumn(name="country_id", referencedColumnName="id")
*/
private $countryId;
I don't know how to manage to use this relationship between entities, I've tried a JOIN LEFT and a subselect but nothing worked.
Perhaps my problem is that I need to access to the country entity but the $er is connected to the Regions entity, or how to create an createQueryBuilder sentence to exploit the relationship shown.
Any suggestion?.
you Regions Entity should be like this:
/**
* #var int
*
* #ORM\Column(name="country_id", type="integer")
* #ORM\ManyToOne(targetEntity="Countries", inversedBy="regions")
* #ORM\JoinColumn(name="country_id", referencedColumnName="id")
*/
private $countryId;
Try that change.

symfony2 twig template load checkboxes from many to many entity relationship

New Symfony2 User here. I have 2 entities that are related, one to many that is unidirectional. I'm doing it as ManyToMany as the doctrine documentation suggests, Article(one) and Tags(many). I'd like to have checkboxes show up that show the tag names on the article.new page and the article.edit page. On form submission the id of the tag entity is stored in the article_tags side table that the entity generator created for me.
Posting only relevant code.
Tag Entity AppBundle/Entity/Tag.php
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=20)
*/
public $name;
Article Entity AppBundle/Entity/Article.php
/**
* #ORM\ManyToMany(targetEntity="Tag")
* #ORM\JoinTable(
* name="article_tags",
* joinColumns={#ORM\JoinColumn(name="article_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="tag_id", referencedColumnName="id", unique=true)}
* )
*/
protected $tags;
/**
* Add tag
*
* #param \AppBundle\Entity\Tag $tag
*
* #return Article
*/
public function addTag(\AppBundle\Entity\Tag $tag)
{
$this->tags[] = $tag;
return $this;
}
/**
* Remove tag
*
* #param \AppBundle\Entity\Tag $tag
*/
public function removeTag(\AppBundle\Entity\Tag $tag)
{
$this->tags->removeElement($tag);
}
/**
* Get tags
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTags()
{
return $this->tags;
}
Article Form Type AppBundle/Form/ArticleType
$builder->add('title')
->add('body')
->add('author')
->add('tags', 'entity', array(
'class' => 'AppBundle\Entity\Tag',
'property' => 'name',
'expanded' => 'true', ));
ArticleController AppBundle/Controller/ArticleController.php
* #Template()
*/
public function newAction()
{
$entity = new Article();
$tags = new Tag();
$entity->addTag($tags);
$form = $this->createCreateForm($entity);
return array('entity' => $entity,'form' => $form->createView(), );
}
As of now the error I receive is...
Entities passed to the choice field must be managed. Maybe persist
them in the entity manager?
I'm not entirely sure I'm on the right track. I just want to attach tags to articles!
Thanks
In the controller, you create a blank Tag and add it to the new Article before creating the form. That doesn't make sense to me, and I suspect that's where the error is coming from.
If there are any tags in the database, Symfony will automatically get them and display them with a checkbox in the form. If the user checks a checkbox, this Tag will be added to the Article.
Just delete these two lines and you should be fine:
$tags = new Tag();
$entity->addTag($tags);

Symfony Forms OneToMany/ManyToOne selector problems

I am trying to create a Form with a Category selector for a Page entity - Pages can have many categories in multiple Registries.
The relationship from Page --> Category is a OneToMany/ManyToOne with an intermediate PagesCategoryEntity that has a discriminator property (categoryRegistryId).
I have successfully figured out how to use the 'entity' Type in the FormBuilder to create ONE multiple select box. But in the end, I will need to have multiple select-boxes (for each registry) with a discriminator value in the html somewhere.
So, I need to know how to get the additional properties of PagesCategoryEntity into the Form and then how I can access them in the getters/setters of the PageEntity.
Surely, I cannot be the only person to have values in the intermediate entity that need to be accessible in the form and persistence layer?
I appreciate you taking the time to look at this!
craig
truncated Entity and Form classes for Brevity.
class CategoryEntity
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
// other properties, getters, setters, etc...
}
class PageEntity
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="PagesCategoryEntity",
* mappedBy="page", cascade={"all"},
* orphanRemoval=true, indexBy="categoryRegistryId")
*/
private $categories;
// other properties, getters, setters, etc...
}
class PagesCategoryEntity
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\Column(type="integer")
*/
private $categoryRegistryId;
/**
* #ORM\ManyToOne(targetEntity="CategoryEntity")
* #ORM\JoinColumn(name="categoryId", referencedColumnName="id")
*/
private $category;
/**
* #ORM\ManyToOne(targetEntity="PageEntity", inversedBy="categories")
* #ORM\JoinColumn(name="entityId", referencedColumnName="pageid")
*/
private $page;
}
class PageType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('categories', 'entity', array(
'class' => 'MyCoolBundle:CategoryEntity',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->where('c.parent = :parent')
->setParameter('parent', 19)
->orderBy('c.name', 'ASC');
},
'property' => "name",
'multiple' => true,
'required' => false,
'empty_data' => null,
));
}
}
Try this then:
->add('categories',
'entity',
array(
'class'=>'Acme\MycoolBundle\Entity\Category',
'property'=>'name',
'query_builder' => function (\Acme\MycoolBundle\Entity\CategoryRepository $repository)
{
return $repository->createQueryBuilder('c')
->where('c.parent = :parent')
->setParameter('parent', 19)
->add('c.name', 'ASC');
}
)
);
try to create a category Repository if u don't have and adapt the scripts for ur needs it works for me!
Try to use Collection instead of using Entity!
Collection is used in one to many/ many to one
you can try the tutorial of symfony cookbook for collection forms
http://symfony.com/doc/current/cookbook/form/form_collections.html
i hope it helps ;)

Symfony2 entity field type query_builder innerJoin

I have 3 Entities (Group, GroupCategory and GroupLanguage)
Group Table
id (pk)
id_group_category (fk)
GroupCategory Table
id (pk)
GroupCategoryLanguage Table
id (pk)
id_language (fk)
id_group_category (fk)
I have created a GroupType which takes in GroupCategory as a subform.
$builder->add('id_group_category', 'entity', array(
'class' => 'BazaarBundle:GroupCategory',
'property' => 'id',
'query_builder' => function(EntityRepository $a) {
return $a->createQueryBuilder('a')
->innerJoin('BazaarBundle:GroupCategoryLanguage', 'b')
->where('b.id_group_category = a.id')
->orderBy('a.id', 'ASC');
}
)
)
->add('Add', 'submit');
I'm trying to innerJoin the language table so that the dropdownlist would be populated with text and not the ids of the category.
I'm quite new to Symfony2 and have already looked up to their documentation and sorry to say it was quite puzzling for me. Am I doing it right because i'm having some errors with the code.
The error message:
[Semantical Error] line 0, col 111 near 'id_group_category': Error: Class Karl\BazaarBundle\Entity\GroupCategoryLanguage has no field or association named id_group_category
GroupCategory.php
class GroupCategory
{
public function __construct()
{
$this->groupCategoryLanguage = new ArrayCollection();
}
public function __toString(){
return $this->groupCategoryLanguage->getName();
}
/**
* #ORM\OneToMany(targetEntity="GroupCategoryLanguage", mappedBy="idGroupCategory")
* #ORM\JoinColumn(nullable=false,referencedColumnName="id_group_category")
*/
protected $groupCategoryLanguage;
}
GroupCategoryLanguage.php
class GroupCategoryLanguage
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer
*
* #ORM\Column(name="id_language", type="integer")
*/
private $idLanguage;
/**
* #var integer
*
* #ORM\JoinColumn(name="id_group_category", nullable=false)
* #ORM\ManyToOne(targetEntity="Karl\BazaarBundle\Entity\GroupCategory", inversedBy="groupCategoryLanguage")
*/
private $idGroupCategory;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=32)
*/
private $name;
}
i think you need to add GroupCategoryLanguage data into your query by adding:
->addSelect('b')
to your query builer object.
Exemple below.
Please note i have deleted the where condition because it seems to be a join condition, adn this is not needed bacause Doctrine is suposed to know all about relations. If i'm wrong, don't delete it...
$builder->add('id_group_category', 'entity', array(
'class' => 'BazaarBundle:GroupCategory',
'property' => 'id',
'query_builder' => function(EntityRepository $a) {
return $a->createQueryBuilder('a')
->innerJoin('a.languages', 'b')
->addSelect('b')
->orderBy('a.id', 'ASC');
}
))
->add('Add', 'submit');
EDIT:
Regarding our discussions, i update my answer:.
Let's start from the beginning:
You have a relation between GroupCategory and GroupCategoryLanguage and the GroupCategoryLanguage is the owner of this relation (it have to FK).
Here you want to get languages from the GroupCategory so it's $owner->getSlave() and you need a bidirectionnal relation.
For that you need to add a field into the slave entity:
So in GroupCategory entity:
/**
* #ORM\OneToMany(targetEntity="Karl\BazaarBundle\Entity\GroupCategoryLanguage",referencedColumnName="id_group_category", mappedBy="category")
* #ORM\JoinColumn(nullable=false)
*/
private $languages;
And i assume that in GroupCategoryLanguages you have:
/**
* #ORM\ManyToOne(targetEntity="Karl\BazaarBundle\Entity\GroupCategory", inversedBy="languages")
* #ORM\JoinColumn(name="id_group_category", nullable=false)
*/
private $category;
I think one of your problems is that you think in terms of tables, am i wrong ?
You really need to think in term of objects (entities) and let Doctrine manage the boring things :)
Display language in place of id
You can totally delete the 'property' option and add a __toString method into your GroupCategory entity, which one will be called and the returned value will appear in your form.
I think we are good :)
Cheers

Collection Prototype and Doctrine Persistance ManyToOne Relation

Context : I am building my little TodoList bundle (which is a good exercice to go deep progressively with Symfony2), the difficulty comes with recursivity : each Task can has children and parent, so I used Gedmo Tree.
I have a collection of tasks each having a sub collection of children, children collection has prototype enabled so I can display a new sub task form when clicking "add sub task".
I wanted the default name of the subtask to be "New Sub Task" instead of "New Task" set in Task constructor, so I figured out how to pass a custom instance for the prototype and took some care for preventing infinite loop.
So I am almost done and my new task is added with the name I set when saving...
Problem : I am not able to persist the parent task to the new sub task, the new task persist the name well, but not the parentId, I probably forgot somewhere with Doctrine, here is some relevant parts :
// Entity Task
/**
* #Gedmo\Tree(type="nested")
* #ORM\Entity(repositoryClass="Gedmo\Tree\Entity\Repository\NestedTreeRepository")
* #ORM\HasLifecycleCallbacks
* #ORM\Table(name="task")
*/
class Task {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #Gedmo\Timestampable(on="create")
* #ORM\Column(type="datetime")
*/
protected $created;
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank(message="Name must be not empty")
*/
protected $name = 'New Task';
//....
/**
* #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="Task", inversedBy="children")
* #ORM\JoinColumn(name="parentId", referencedColumnName="id", onDelete="SET NULL")
*/
protected $parent = null;//
/**
* #ORM\Column(type="integer", nullable=true)
*/
protected $parentId = null;
/**
* #Assert\Valid()
* #ORM\OneToMany(targetEntity="Task", mappedBy="parent", cascade={"persist", "remove"})
* #ORM\OrderBy({"status" = "ASC", "created" = "DESC"})
*/
private $children;
public function __construct(){
$this->children = new ArrayCollection();
}
/**
* Set parentId
*
* #param integer $parentId
* #return Task
*/
public function setParentId($parentId){
$this->parentId = $parentId;
return $this;
}
/**
* Get parentId
*
* #return integer
*/
public function getParentId(){
return $this->parentId;
}
/**
* Set parent
*
* #param \Dmidz\TodoBundle\Entity\Task $parent
* #return Task
*/
public function setParent(\Dmidz\TodoBundle\Entity\Task $parent = null){
$this->parent = $parent;
return $this;
}
/**
* Get parent
*
* #return \Dmidz\TodoBundle\Entity\Task
*/
public function getParent(){
return $this->parent;
}
/**
* Add children
*
* #param \Dmidz\TodoBundle\Entity\Task $child
* #return Task
*/
public function addChild(\Dmidz\TodoBundle\Entity\Task $child){
$this->children[] = $child;
return $this;
}
/**
* Remove child
*
* #param \Dmidz\TodoBundle\Entity\Task $child
*/
public function removeChild(\Dmidz\TodoBundle\Entity\Task $child){
$this->children->removeElement($child);
}
}
// TaskType
class TaskType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options){
$builder
->add('name', null, ['label' => false])
->add('notes', null, ['label' => 'Notes'])
->add('status', 'hidden')
->add('parentId', 'hidden')
;
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) use ($builder){
$record = $event->getData();
$form = $event->getForm();
if(!$record || $record->getId() === null){// if prototype
$form->add('minutesEstimated', null, ['label' => 'Durée', 'attr'=>['title'=>'Durée estimée en minutes']]);
}elseif($record && ($children = $record->getChildren())) {
// this is where I am able to customize the prototype default values
$protoTask = new Task();
$protoTask->setName('New Sub Task');
// here I am loosely trying to set the parentId I want
// so the prototype form input has the right value
// BUT it goes aways when INSERT in mysql, the value is NULL
$protoTask->setParentId($record->getId());
$form->add('sub', 'collection', [// warn don't name the field 'children' or it will conflict
'property_path' => 'children',
'type' => new TaskType(),
'allow_add' => true,
'by_reference' => false,
// this option comes from a form type extension
// allowing customizing prototype default values
// extension code : https://gist.github.com/jumika/e2f0a5b3d4faf277307a
'prototype_data' => $protoTask
]);
}
});
}
public function setDefaultOptions(OptionsResolverInterface $resolver){
$resolver->setDefaults([
'data_class' => 'Dmidz\TodoBundle\Entity\Task',
'label' => false,
]);
}
public function getParent(){ return 'form';}
}
// my controller
/**
* #Route("/")
* #Template("DmidzTodoBundle:Task:index.html.twig")
*/
public function indexAction(Request $request){
$this->request = $request;
$repo = $this->doctrine->getRepository('DmidzTodoBundle:Task');
$em = $this->doctrine->getManager();
//__ list of root tasks (parent null)
$query = $repo->createQueryBuilder('p')
->select(['p','FIELD(p.status, :progress, :wait, :done) AS HIDDEN field'])
->addOrderBy('field','ASC')
->addOrderBy('p.id','DESC')
->andWhere('p.parent IS NULL')
->setParameters([
'progress' => Task::STATUS_PROGRESS,
'wait' => Task::STATUS_WAIT,
'done' => Task::STATUS_DONE
])
->setMaxResults(20)
->getQuery();
$tasks = $query->getResult();
//__ form building : collection of tasks
$formList = $this->formFactory->createNamed('list_task', 'form', [
'records' => $tasks
])
->add('records', 'collection', [
'type'=>new TaskType(),
'label'=>false,
'required'=>false,
'by_reference' => false,
])
;
//__ form submission
if ($request->isMethod('POST')) {
$formList->handleRequest($request);
if($formList->isValid()){
// persist tasks
// I thought persisting root tasks will persist their children relation
foreach($tasks as $task){
$em->persist($task);
}
$em->flush();
return new RedirectResponse($this->router->generate('dmidz_todo_task_index'));
}
}
return [
'formList' => $formList->createView(),
];
}
As mentionned in the comments in TaskType, the form prototype of the new sub task has the right value for parentId which is posted, BUT the value is gone and NULL on INSERT in db (looking at the doctrine log).
So do you think it is the right way of doing, and then what thing I forgot for persisting correctly the parent task of the new sub task ?
On your child setting you should set the parent when adding, like so..
/**
* Add children
*
* #param \Dmidz\TodoBundle\Entity\Task $children
* #return Task
*/
public function addChild(\Dmidz\TodoBundle\Entity\Task $children){
$this->children->add($children);
$children->setParent($this);
return $this;
}
/**
* Remove children
*
* #param \Dmidz\TodoBundle\Entity\Task $children
*/
public function removeChild(\Dmidz\TodoBundle\Entity\Task $children){
$this->children->removeElement($children);
$children->setParent(null);
}
When your prototype adds and deletes a row it calls addChild and removeChild but it doesn't call the setParent in the associated child.
This way any child that is added or removed/deleted get automatically set in the process.
Also you could change the $children to $child as it makes grammatical sense and it's really bugging me because I am a child(ren).
It seems weird to me that you try using the parentId field as a simple column, whereas it is a relation column. Theoretically, you should not:
$task->getParentId(); //fetching a DB column's value
but instead:
$task->getParent()->getId(); //walking through relations to find an object's attribute
However, if you really need this feature to avoid loading the full parent object and just get its ID, your setParentId method should be transparent (although, as mentionned, I'm not sure using the same DB field is valid):
public function setParent(Task $t = null) {
$this->parent = $t;
$this->parentId = null === $t ? null : $t->getId();
return $this;
}
Back to your issue: in the TaskType class, you should call:
$protoTask->setParent($record);
instead of:
$protoTask->setParentId($record->getId());
The reason:
you tell Doctrine parentId is a relation field (in the $parent attribute declaration), therefore Doctrine expects an object of the proper type
you also tell Doctrine to map this relation field directly to an attribute (the $parentId attribute declaration), I'm neither convinced this is valid, nor convinced this is good practice, but I guess you did some research before going for this structure
you set $parentId, but $parent has not been set (i.e. null), so Doctrine must erase the $parentId value with the $parent value: your code is proof that Doctrine handles attributes first, then computes relations ;)
Keep in mind Doctrine is an Object Relational Mapper, not a simple query helper: mapper is what it does (mapping persistence layer with your code), relational is how it does it (one-to-many and the like), object is what it does it on (therefore not directly using IDs).
Hope this helps!

Resources