Doctrine 2: Understand how entities works with doctrine 2 - php

I have 3 Entities: Person, Affiliation and PersonAffiliation.
In my form, I will display each affiliation as a checked checkbox.
Nowm when the user uncecks the checkbox and click submit, this affiliation should be removed from the PersonAffiliation table.
The problem is that when I submit without unchecking, my data are duplicated.
Example: aff1 and aff2. When both checked and submit, I will then get aff1 aff1 aff2 aff2.
The same, If I uncheck aff2, I will then have aff1 aff1.
The error is probably somewhere in using the doctrine:
Here is how I am managing that:
Entity Persom
#ORM\OneToMany(targetEntity="PersonAffiliation", mappedBy="person", cascade={"persist", "remove"})
protected $affiliations;
Entity Affiliation:
#ORM\OneToMany(targetEntity="PersonAffiliation", mappedBy="affiliation")
protected $person_affiliations;
Entity PersonAffiliation
#ORM\ManyToOne(targetEntity="Person", inversedBy="affiliations")
#ORM\JoinColumn(name="person_id", referencedColumnName="id")
protected $person;
#ORM\ManyToOne(targetEntity="Affiliation", inversedBy="person_affiliations")
#ORM\JoinColumn(name="affiliation_id", referencedColumnName="id")
protected $affiliation;
An idea on how to resolve that?
Thank you.
EDIT:
Cotroller part:
foreach( $enquiry->getAffiliations() as $aff )
{
$pAff = new PersonAffiliation();
$pAff->setPersonId( $person->getId() );
$pAff->setAffiliationId( $aff->getAffiliation()->getId() );
$pAff->setPerson( $person );
$pAff->setAffiliation( $aff->getAffiliation() );
$em->persist($pAff);
$em->flush();
}
Form Part:
public function buildForm(FormBuilder $builder, array $options)
{
$person = $this->person;
$user = $this->user;
$builder->add('firstname', 'text');
$builder->add('middlename', 'text', array('required'=>false));
$builder->add('lastname', 'text');
$builder->add('sex', 'choice', array( 'choices' => array('m' => 'Male', 'f' => 'Female'),
'required' => true,
'multiple' => false,
'expanded' => true));
$builder->add('email', 'text', array('required'=>false));
if( $this->addAffiliations ) {
$builder->add('affiliations', 'entity', array(
'label' => 'Athor\'s affiliations',
'class' => 'SciForumVersion2Bundle:PersonAffiliation',
'query_builder' => function($em) use ($person, $user){
return $em->createQueryBuilder('pa')
->where('pa.person_id = :pid')
->setParameter('pid', $person->getId());
},
'property' => 'affiliation',
'multiple' => true,
'expanded' => true,
));
}
}

In the Person entity :
/**
* #ORM\ManyToMany(targetEntity="Affiliation", inversedBy="people")
* #ORM\JoinTable(name="PersonAffiliation")
*/
protected $affiliations;
And in the Affiliation entity :
/**
* #ORM\ManyToMany(targetEntity="Person", mappedBy="affiliations")
*/
protected $people;

You made a $em->flush() on each iteration of the foreach. It should be done after the end of foreach isnt'it ?
Moreover, you may use a ManyToMany relation.

Related

Product and Sizes symfony2 doctrine example

I'm new to symfony2 and I'm building my first online store with it. I have products and I want to add product sizes, one product can have many sizes and one size can have many products. For example: two products cat have 'M' size.
class Product {
...
/**
* #ORM\ManyToMany(targetEntity="Size", inversedBy="products", cascade={"persist", "merge"})
* #ORM\JoinTable(name="sizes")
*/
private $sizes;
}
//in another file
class Size {
/**
* #ORM\ManyToMany(targetEntity="Product", mappedBy="sizes")
*/
protected $products;
}
ProductController.php
...
->add('sizes', CollectionType::class, [
'entry_type' => SizeType::class,
'label' => 'Sizes',
'allow_add' => true,
])
...
SizeType.php
public function buildForm(FormBuilderInterface $builder, array $options) {
$repo = $this->em->getRepository('AppBundle:Size');
$q = $repo->createQueryBuilder('c')
->getQuery();
$sizes = $q->getResult();
$builder->add('name', EntityType::class, array(
'class' => 'AppBundle:Size',
'choice_label' => 'name',
));
}
Right now I'm getting
Catchable Fatal Error: Object of class AppBundle\Entity\Size could not be converted to string I can fix if I implement __toString() but I don't know if this is the right thing to do, and if I do this, when editing the product, the dropdown doesn't select the right size.
My question is, is this the right way to implement product - sizes function to online store?
Try with this code:
$builder->add('name', EntityType::class, array(
'class' => 'AppBundle:Size',
'choice_label' => 'name',
'property' => 'needed_property_name' //just write the needed property name there
));
So I've figured out better way to do it, 'entity' type with multiple => true
->add('sizes', 'entity', [
'class' => Size::class,
'label' => 'Размери',
'choice_label' => 'name',
'multiple' => true,
'expanded' => false,
//'allow_add' => true,
])
This way multiple sizes can be selected, with bootstrap-multiselect I've made it good looking and perfectly works for me now.
I'd love to hear if there is a better way.
Product annotations looks wrong. The JoinTable is a lookup table for many-to-many relation:
The convention is to name it after linked tables: products_sizes in your case:
class Product {
...
/**
* #ORM\ManyToMany(targetEntity="Size", inversedBy="products", cascade={"persist", "merge"})
* #ORM\JoinTable(name="products_sizes")
*/
private $sizes;
}

How to set the last assigned value when it is in an intermediate table

I have a form with this field:
class OrdersType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Others form fields
if ($options['curr_action'] !== NULL)
{
$builder
->add('status', 'entity', array(
'class' => 'CommonBundle:OrderStatus',
'property' => 'name',
'required' => TRUE,
'label' => FALSE,
'mapped' => FALSE
));
}
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired(array(
'register_type'
));
$resolver->setOptional(array(
'curr_action'
));
$resolver->setDefaults(array(
'data_class' => 'Tanane\FrontendBundle\Entity\Orders',
'render_fieldset' => FALSE,
'show_legend' => FALSE,
'intention' => 'orders_form'
));
}
public function getName()
{
return 'orders';
}
}
And have this entity:
class OrderHasStatus {
use IdentifiedAutogeneratedEntityTrait;
/**
* Hook timestampable behavior
* updates createdAt, updatedAt fields
*/
use TimestampableEntity;
/**
* #ORM\ManyToOne(targetEntity="\Tanane\FrontendBundle\Entity\Orders")
* #ORM\JoinColumn(name="general_orders_id", referencedColumnName="id")
*/
protected $order;
/**
* #ORM\ManyToOne(targetEntity="\Tanane\CommonBundle\Entity\OrderStatus")
* #ORM\JoinColumn(name="status_id", referencedColumnName="id")
*/
protected $status;
public function setOrder(\Tanane\FrontendBundle\Entity\Orders $order)
{
$this->order = $order;
}
public function getOrder()
{
return $this->order;
}
public function setStatus(\Tanane\CommonBundle\Entity\OrderStatus $status)
{
$this->status = $status;
}
public function getStatus()
{
return $this->status;
}
}
Where I store the latest status asigned to the order. I need to set that status on the form when I'm editing or showing the order but don't know how to achieve this, can any give me some help?
So, this looks related to your other question concerning n:m relationships. Your existing solution might work if you had a Many-to-Many relationship, but won't with three Entities.
I think the solution is similar:
OrdersType
$builder->add('orderHasStatus', 'collection', array(
'type' => new OrderHasStatusType(),
'allow_add' => true,
'allow_delete' => true
));
OrderHasStatusType
$builder->add('status', 'collection', array(
'type' => new StatusType(),
'allow_add' => true,
'allow_delete' => true
));
StatusType
$builder->add('status', 'entity', array(
'class' => 'CommonBundle:Status'
));
As your schema stands^, your OrdersType needs to contain a Collection of OrderHasStatus types, which themselves have StatusTypes containing the statuses themselves.
^One thing I would query, and maybe should have in my other answer, is whether your schema is correct - do you need each Order to have multiple Statuses? If not, then what you really want is a Many-to-One between Order and Status, which would remove the intermediate OrderHasStatus layer. If you want the extra data about OrderHasStatus (the timestamp?) then you could put that info on the Order table. But if you need multiple Statuses, or want to keep the extra data separate from the Orders table (in which case Orders and OrderHasStatus are actually one-to-one), fine.

Processing of non-existent collection fields in SonataAdminBundle forms

I have an entity class ContactsPage to store some information about emails, phones etc. The problem is to get all this information into one field "contacts" in json-format defined within the ContactsPage entity:
class ContactsPage
{
...
/**
* #var string
*
* #ORM\Column(name="contacts", type="text", nullable=true)
*/
private $contacts;
...
}
ContactsPageAdmin form constructing example for emails:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('emails', 'collection',
array(
'mapped' => false,
'required' => false,
'type' => 'text',
'allow_add' => true,
'allow_delete' => true,
)
);
}
How and where can I get this "emails" array (or collection)?
Where can I handle this array to make json and push it into "contacts" field before saving the ContactsPage entity?
Where and how I can handle "contacts" field and transfer all decoded from json information into editing form (into "emails" collection)?
Thanks.
Spent almost the day but found answers by myself.
First and second questions.
I have to define prePersist and preUpdate methods within the admin-class with my Entity as an argument, where i can get and handle "emails" array:
class ContactsPageAdmin extends Admin
{
...
public function prePersist($contactsPage)
{
$emails = $this->getForm()->get('emails')->getData();
$contactsPage->setContacts(json_encode($emails));
}
public function Update($contactsPage)
{
$emails = $this->getForm()->get('emails')->getData();
$contactsPage->setContacts(json_encode($emails));
}
...
}
What about third question. I just have to use $this->getSubject() to get original Entity and 'data' property to load needed data:
class ContactsPageAdmin extends Admin
{
...
protected function configureFormFields(FormMapper $formMapper)
$entity = $this->getSubject(); // getting original Entity
$json = $entity->getContacts(); // field with json data
$array = json_decode($json, true);
// preparing data to fulfill necessary form fields
$array['emails'] = is_array($array['emails']) ? $array['emails'] : array();
$formMapper
->add('emails', 'collection',
array(
'mapped' => false,
'required' => false,
'type' => 'text',
'allow_add' => true,
'allow_delete' => true,
'data' => $array['emails'] // embeding prepared data into collection
)
);
}
...
}

Symfony2 Change checkbox values from 0/1 to 'no'/'yes'

I created a form with one checkbox.
UserSettingsType.php:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('newsletter', 'checkbox', array(
'label' => 'Newsletter erhalten',
'attr' => array(
'class' => 'form-control',
),
'required' => false,
));
}
In the UserSettings.php Entity:
/**
* #ORM\Column(name="newsletter", type="boolean")
*/
protected $newsletter;
In the User.php:
/**
* #ORM\Column(type="integer", nullable=true)
*/
protected $user_settings_id;
/**
* #ORM\OneToOne(targetEntity="UserSettings", cascade={"persist"})
* #ORM\JoinColumn(name="user_settings_id", referencedColumnName="id")
*/
protected $settings;
In the PageController.php i handle the settings action:
public function settingsAction() {
$user = $this->getUser();
if ($user->getSettings() !== null) {
$settings = $user->getSettings();
} else {
$settings = new UserSettings($user);
}
$settings_form = $this->createForm(new UserSettingsType(), $settings);
$request = $this->getRequest();
if ($request->getMethod() == 'POST') {
$em = $this->getDoctrine()->getManager();
$settings_form->bind($request);
if ($settings_form->isValid()) {
$user->setSettings($settings);
$em->persist($user);
$em->flush();
}
}
return $this->render('MyCompMyAppBundle:Page:settings.html.twig', array(
'settings_form' => $settings_form->createView(),
));
}
I want to change the checkbox values from false (unchecked) / true (checked) to 'no' / 'yes' and change the definition of the newsletter field to: * #ORM\Column(name="newsletter", type="string", columnDefinition="ENUM('yes', 'no')")
It would be nice if there would be 'yes' and 'no' enum values in the database.
Please correct me if i am wrong: There is no way to change this via form element attributes, right?
I heard something about a DataTransformer:. But is there any easier way to realize this?
do you want checkbox or radio button? for checkbox in peresentation use:
$builder->add('newsletter', 'choice', array(
'label' => 'Newsletter erhalten',
'attr' => array(
'class' => 'form-control',
),
'choices' => array(array('yes' => 'yes'), array('no' => 'no')),
'expanded' => true,
'multiple' => true,
'required' => false,
));
Don't use ENUM for this!
In MySQL, use either data type BIT(1) or TINYINT(1) (=same as BOOLEAN). See here: Which MySQL data type to use for storing boolean values
In PostgreSQL, there is a true BOOLEAN type. But no ENUM. So if you're ever thinking about migrating, better get rid of ENUM ;-)

Symfony2 Persisting form collection with a relational entities

Haven't found a solution to this problem through research yet but I am trying to save into the database with two forms in one (embedded/collection). I have entities that are related to each other and I want the form to submit and persist both entities into the database.
Main entity:
/**
* #var integer
* #ORM\OneToMany(targetEntity="Sub", mappedBy="mainId", cascade={"persist"})
*/
protected $sub;
public function __construct() {
$this->sub = new ArrayCollection();
}
Sub entity:
/**
* #var integer
*
* #ORM\Column(name="main_id", type="integer")
*/
protected $mainId;
.......
/**
* #ORM\ManyToOne(targetEntity="Main", inversedBy="sub")
* #ORM\JoinColumn(name="main_id", referencedColumnName="id")
*/
protected $main;
Here's my MainType form:
class MainType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('dano', 'text', array(
'label' => 'DA: ',
'disabled' => true
))
->add('partno','text', array(
'label' => 'Part: ',
'disabled' => true
))
->add('batchno', 'text', array(
'label' => 'Batch: ',
'disabled' => true
))
->add('sub', 'collection', array('type' => new SubType()))
->add('submit', 'submit');
}......
And my SubType form:
class SubType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('main_id','hidden')
->add('rackno','text', array(
'label' => 'Rack No(s) '
))
->add('diecode','text', array(
'label' => 'Die Code '
))
->add('heatcode','text', array(
'label' => 'Heat Code '
))
->add('inqty','integer', array(
'label' => 'Qty In '
))
->add('onhold','choice', array(
'label' => 'Hold',
'choices' => array(
'1' => 'On Hold',
'0' => 'Released'
),
'multiple' => false,
'expanded' => true
));
And my controller:
/**
* #param Request $request
* #Route("/{dano}", name="subpart_part")
*/
public function submitPartByDAAction(Request $request, $dano) {
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('Bundle:Main')
->findOneByDano($dano);
$partno = $entity->getPartno();
$batchno = $entity->getBatchno();
$mainid = $entity->getId();
$main1 = new Main();
$main1->setDano($dano);
$main1->setPartno($partno);
$main1->setBatchno($batchno);
$sub1 = new Sub();
$sub1->setMainId($mainid);
$main1->getSub()->add($sub1);
$form = $this->createForm(new MainType(), $main1, array(
'method' => 'POST'
));
$form->handleRequest($request);
if($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($main1);
$em->flush();
return $this->redirect($this->generateUrl('subpart_home'));
}
return $this->render('Bundle:Parts:addparts.html.twig', array(
'form' => $form->createView()
));
}
Let me explain what I did here, at first I did not have the Sub's "main_id" field (which is related to Main's id) on but when I tried persisting the data it gave me the error:
An exception occurred while executing 'INSERT INTO sub
(main_id, rackno, heatcode, diecode, inqty, onhold) VALUES
(?, ?, ?, ?, ?, ?)' with params [null, "46", "eterte", "seteter", 3, 0]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column
'main_id' cannot be null
So then I made a field "main_id" with it being hidden, grabbed the id from Main by getId(); and passed it into the Sub's form's setMainId(); to persist and it still gives me the same error that "main_id" cannot be null.
What am I missing? Thanks!
You are defining your entities wrong. First understand the concept of ORM and relations. Your Sub entity does not need to have the integer main_id. Simply map it to Main entity. Your Main entity should look like
/**
* #var Sub
* this value is just integer in database, but doc should point it to Sub
* #ORM\OneToMany(targetEntity="Sub", mappedBy="mainId", cascade={"persist"})
*/
protected $sub;
public function __construct() {
$this->sub = new ArrayCollection();
}
And your Sub entity
/**
* #ORM\ManyToOne(targetEntity="Main", inversedBy="sub")
* #ORM\JoinColumn(name="main_id", referencedColumnName="id")
*/
protected $main;
You dont need main_id. The ORM will handle that for you. The MainType form is good. Just get rid of the main_id in SubType form.
You should reference to entities by object rather than their IDs. In your controller also rather than using
$sub1->setMainId($mainid);
You should set the object.
$sub1->setMain($main1);
Your main form is also a little weird. I do not say it is not valid, but you should consider replacing this line:
->add('sub', 'collection', array('type' => new SubType()))
With something like this:
->add('sub', new SubType(), array())
I think it is way more appropriate if you have only "ONE" item. You use collection when you want many items.
I would suggest you look into the form component... how the form is represented as a tree...
Also never make fields like "main_id", unless it is necessary. Try not to work id's and work with associations.

Categories