How to create a symfony2 form collection with fixed subforms - symfony

I am trying to create a form which will collect a list of facilities and contact information:
The issue is I would like to have the facility, and exactly 4 contacts of different types. I realize I could make this work separately by making each contact a property on the Facility entity but it feels like it would be cleaner and easier to use the data later if it is in a collection.
You'll notice in the FacilityType class, I use $builder->create to add a sub-form which gives me the structure I'm expecting but I get an error when I try to persist.
"A new entity was found through the relationship 'AppBundle\Entity\Facility#contacts' that was not configured to cascade persist operations for entity:...."
Thanks. My code is below.
The Facility entity:
<?php
namespace AppBundle\Form;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
class Facility
{
/**
* #ORM\Column(length=200)
*/
public $name;
/**
* #ORM\OneToMany(targetEntity="FacilityContact", mappedBy="facility")
*/
public $contacts;
public function __construct()
{
$this->contacts = new ArrayCollection;
}
}
The FacilityContact entity:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
class FacilityContact
{
/**
* #ORM\ManyToOne(targetEntity="facility", inversedBy="contacts")
*/
public $facility;
/**
* #ORM\Column(type="string", length=50)
*/
public $contactType;
/**
* #ORM\Column(type="string", length=200)
*/
public $name;
/**
* #ORM\Column(type="string", length=100)
*/
public $email;
/**
* #ORM\Column(type="string", length=15)
*/
public $phone;
}
Facility Form
class FacilityType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$centre = $this->centre;
$builder->add('name', 'text', array("label"=>"Facility Name");
$contacts = $builder->create("contacts", "form");
$contacts
->add("radonc", new FacilityContactType("radonc"), array("label" => "Radiation Oncologist"))
->add("physicist", new FacilityContactType("physicist"), array("label" => "Physicist Responsible"))
->add("radtherapist", new FacilityContactType("radtherapist"), array("label" => "Radiation Therapists"))
->add("datamanager", new FacilityContactType("datamanager"), array("label" => "Data manager"))
;
$builder->add($contacts);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Facility'
));
}
public function getName()
{
return 'appbundle_facility';
}
}
FacilityContact form
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class FacilityContactType extends AbstractType
{
private $contactType;
public function __construct($contactType)
{
$this->contactType = $contactType;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('contactType', 'hidden', array("read_only" => true, "data"=>$this->contactType))
->add('name', 'text', array("required" => false))
->add('phone', 'text', array("required" => false))
->add('email', 'email', array("required" => false))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\FacilityContact'
));
}
/**
* #return string
*/
public function getName()
{
return 'appbundle_facilitycontact';
}
}

Doctrine is trying to tell you that there are one or more objects, that it does not know about, because they were not persisted into the database.
This happens, because in the FacilityForm you are using the FacilityContactForm, that creates 4 new FacilityContact entities. You are trying to persist only the Facility entity without the other 4 entities, it wouldn't be any problem, but there is a relationship between them and in the database the Facility entity doesn't have its FacilityContacts.
You can resolve this in two ways:
using the cascade options, something like this: #ORM\OneToMany(targetEntity="FacilityContact", mappedBy="facility", cascade={"persist"}), basically this tells doctrine that it should persist also the FacilityContacts entities, you can read more at: http://doctrine-orm.readthedocs.org/en/latest/reference/working-with-associations.html, and you also have here some explanation about cascade: Doctrine Cascade Options for OneToMany.
another option is to create before the Facility entity with the contact collection empty, persist it to the database, and then to persist the FacilityContacts to the database and add them to the contact array with the add method, and then only call $em->update(), because doctrine knows and is monitoring any changes about the Facility entity.

Related

Symfony 5.0.8 weird issue when updating a simple entity

Here is my entity:
<?php
namespace App\Entity\Contact;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="contact_contact")
*/
class Contact
{
/**
* #ORM\Id
* #ORM\Column(type="integer", options={"unsigned":true})
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #Assert\NotBlank
* #ORM\Column(type="string", length=40, nullable=true)
*/
private $fname;
/**
* #ORM\Column(type="string", length=40, nullable=true)
*/
private $lname;
public function getId(): ?int
{
return $this->id;
}
public function getFname(): ?string
{
return $this->fname;
}
public function setFname(string $fname): self
{
$this->fname = $fname;
return $this;
}
public function getLname(): ?string
{
return $this->lname;
}
public function setLname(?string $lname): self
{
$this->lname = $lname;
return $this;
}
}
Here is the edit controller action code:
/**
* #Route("/{id}/edit", name="contact_contact_edit", methods={"GET","POST"})
*/
public function edit(Request $request, Contact $contact): Response
{
$form = $this->createForm(ContactType::class, $contact);
$form->handleRequest($request);
if ($form->isSubmitted()) {
if ($form->isValid()) {
$this->getDoctrine()->getManager()->flush();
}
}
return $this->render('contact/contact/edit.html.twig', [
'contact' => $contact,
'form' => $form->createView(),
]);
}
When I post the form but leave the fname (first name) field empty...I get this error (Symfony\Component\PropertyAccess\Exception\InvalidArgumentException)
Expected argument of type "string", "null" given at property path
"fname".
When creating the entity, the #Assert works as expected and the message says so...but if I leave it blank and update post...bzzzt error.
What am I missing?
EDIT | Here is the form class incase thats doing something?
<?php
namespace App\Form\Contact;
use App\Entity\Contact\Contact;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ContactType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('fname', TextType::class, ['label' => 'First Name'])
->add('lname', TextType::class, ['label' => 'Last Name']);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Contact::class,
'min_entry' => false,
// NOTE: Must be added to every form class to disable HTML5 validation
'attr' => ['novalidate' => 'novalidate']
]);
$resolver->setAllowedTypes('min_entry', 'bool');
}
}
That's one of the reasons you should avoid allowing the form component changing your entities directly. It will set the data, and then validate it. So it's totally possible for the entity to be in an invalid state after the form has been submitted.
Anyway, you can specify what an empty value should be:
->add('fname', TextType::class, ['label' => 'First Name', 'empty_data' => ''])

Symfony - Group sequence defined part in entity and in form type

It looks like there's something I can't understand. It will be a long post, so I'll try to be concise.
I try to use GroupSequence in Symfony 4.2, but I'm facing an issue. Goal I want to achieve is adding an unmapped field and define GroupSequence in FormType (because I want to use another FormType on this entity without this unmapped field). I want this unmapped field "verified" to be check before other constraints on the form.
Problem is I can't manage to enable part of the validation using Group Sequence in this particular case.
Sample code : https://github.com/sarramegnag/groupsequence-issue
Here's my entity code :
<?php // src/Entity/Product.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="App\Repository\ProductRepository")
*/
class Product
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*
* #Assert\NotBlank()
*/
private $name;
/**
* #ORM\Column(type="text")
*
* #Assert\NotBlank()
*/
private $description;
/**
* #ORM\Column(type="integer")
*
* #Assert\NotBlank()
*/
private $price;
...
And my form type code :
<?php // src/Form/ProductType.php
namespace App\Form;
use App\Entity\Product;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\GroupSequence;
use Symfony\Component\Validator\Constraints\NotBlank;
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('description')
->add('price')
->add('verified', CheckboxType::class, [
'mapped' => false,
'constraints' => [
new NotBlank(['groups' => ['Strict']]),
]
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Product::class,
'validation_groups' => new GroupSequence(['Strict', 'Product']),
]);
}
}
It works if I set attribute and the assertion in the entity and map it as a normal field.
// src/Entity/Product.php
/**
* #Assert\NotBlank(groups={"Strict"})
*/
private $verified = false;
...
public function isVerified(): ?bool
{
return $this->verified;
}
public function setVerified(bool $verified): self
{
$this->verified = $verified;
return $this;
}
// src/Form/ProductType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('description')
->add('price')
->add('verified', CheckboxType::class)
;
}
In fact, it works as soon I set a group on an assertion in the entity.
Thank you for your time and let me know if I'm not being very clear.
Guillaume

How to access one to one embedded form symfony2

I used symfony 2 only few days.
I got two entities and i want to create one form and save data from this form into database.
UserDetails and Contract have relations OneToOne.
I embed a form of Contract into UserDetails form (form apper on web) but when i set some data into form and click button "save" i get a error. as i notice a "try" to assign an array instead of Contract i don't know how to access this new contract entite in Controler.
example ERRORS:
ContextErrorException: Catchable Fatal Error: Argument 1 passed to Leave\DatabaseBundle\Entity\UserDetails::setContract() must be an instance of Leave\DatabaseBundle\Entity\Contract, array given, called in /var/www/nowyUrlop/vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php on line 360 and defined in /var/www/nowyUrlop/src/Leave/DatabaseBundle/Entity/UserDetails.php line 165
at UserDetails->setContract(array('start_contr' => object(DateTime), 'end_contr' => object(DateTime), 'hours_per_week' => '6')) in /var/www/nowyUrlop/vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php line 360
UserDetails entity:
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Annotations\AnnotationReader;
use Leave\DatabaseBundle\Entity\User;
/**
* #ORM\Entity
* #ORM\Table(name="userDetails")
*/
class UserDetails {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="Contract", mappedBy="user_details", cascade={"persist"})
*/
protected $contract;
/**
* #ORM\OneToOne(targetEntity="User", inversedBy="userDetails")
* #ORM\JoinColumn(name="user_id", nullable = true, referencedColumnName="id")
* */
protected $user;
Contract Entity:
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="contract")
*/
class Contract {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="date")
*/
protected $start_contr;
/**
* #ORM\Column(type="date")
*/
protected $end_contr;
/**
* #ORM\Column(type="integer")
*/
protected $type ;
/**
* #ORM\Column(type="integer")
*/
protected $hours_per_week;
/**
* #ORM\OneToOne(targetEntity="UserDetails", inversedBy="contract")
* #ORM\JoinColumn(name="user_details_id", nullable = true, referencedColumnName="id")
* */
protected $user_details;
public function setUserDetails(\Leave\DatabaseBundle\Entity\UserDetails $userDetails = null)
{
$this->user_details = $userDetails;
return $this;
}
/**
* Get user_details
*
* #return \Leave\DatabaseBundle\Entity\UserDetails
*/
public function getUserDetails()
{
return $this->user_details;
}
UserDetals Form:
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Leave\DatabaseBundle\Form\Type\ContractFormType;
class UserDetailsFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('exp', 'text')
->add('total_leave', 'text')
->add('days_left', 'text')
->add('contract', new contractFormType())
->add('save', 'submit');
}
public function getName()
{
return 'userDetails';
}
}
Contract Form:
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class ContractFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('start_contr', 'date', array('widget' => 'single_text'))
->add('end_contr', 'date', array('widget' => 'single_text'))
->add('hours_per_week', 'text');
}
public function getName()
{
return 'contractForm';
}
}
Controller:
public function editUserAction(Request $request) {
$user = $this->get('security.context')->getToken()->getUser();
$userDetails = new UserDetails();
$form = $this->createForm(new UserDetailsFormType(), $userDetails);
$userDetails->setUser($user);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($userDetails);
$em->flush();
return $this->render('LeaveEmployeeBundle:Employee:editUser.html.twig', array(
'formEditUser' => $form->createView(),
'userDetails' => $userDetails,
'user' => $user
));
}
return $this->render('LeaveEmployeeBundle:Employee:editUser.html.twig', array(
'formEditUser' => $form->createView()
));
}
You have to pass data_class as second parameter.
In your UserDetals Form: do the following
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Leave\DatabaseBundle\Form\Type\ContractFormType;
class UserDetailsFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('exp', 'text')
->add('total_leave', 'text')
->add('days_left', 'text')
->add('contract', new contractFormType(), array(
'data_class' => 'Leave\DatabaseBundle\Entity\Contract')
)
->add('save', 'submit');
}
public function getName()
{
return 'userDetails';
}
}

Multiple, dependent one-to-many relations in Symfony forms

I'm trying to get a multi-relation (multiple, dependent one-to-many relations) form working, but no success. I'm using Symfony 2.3 with FOSUserbundle.
Entity User
use FOS\UserBundle\Entity\User as BaseUser;
[...]
/**
* #ORM\Entity
* #Gedmo\Loggable
* #ORM\Table(name="ta_user", indexes={#ORM\Index(name="IDX_LOGIN_TOKEN", columns={"login_token"})})
*/
class User extends BaseUser
{
[...]
/**
* #ORM\OneToMany(targetEntity="UserLifestyle", mappedBy="user", fetch="LAZY", cascade={"persist", "remove"})
*/
protected $lifestyle;
UserManager
use Doctrine\ORM\EntityManager;
use FOS\UserBundle\Entity\UserManager as BaseUserManager;
use Acme\UserBundle\Entity\LifestyleQuestion;
use Acme\UserBundle\Entity\UserLifestyle;
[...]
class UserManager extends BaseUserManager {
public function createUser() {
$user = parent::createUser();
$lifestyle = new UserLifestyle();
$lifestyle->setQuestion($this->objectManager->getReference('Acme\UserBundle\Entity\LifestyleQuestion', 1));
$user->addLifeStyle($lifestyle);
$lifestyle = new UserLifestyle();
$lifestyle->setQuestion($this->objectManager->getReference('Acme\UserBundle\Entity\LifestyleQuestion', 2));
$user->addLifeStyle($lifestyle);
$lifestyle = new UserLifestyle();
$lifestyle->setQuestion($this->objectManager->getReference('Acme\UserBundle\Entity\LifestyleQuestion', 3));
$user->addLifeStyle($lifestyle);
return $user;
}
Entity UserLifestyle
/**
* #ORM\Entity
* #Gedmo\Loggable
* #ORM\Table(name="ta_user_lifestyle")
*/
class UserLifestyle
{
/**
* #ORM\Id
* #ORM\Column(type="smallint")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="lifestyle")
* #ORM\JoinColumn(name="user_id")
*/
protected $user;
/**
* #ORM\ManyToOne(targetEntity="LifestyleQuestion", inversedBy="answeredByUser")
* #ORM\JoinColumn(name="question_id")
*/
protected $question;
/**
* #ORM\ManyToOne(targetEntity="LifestyleAnswer", inversedBy="userAnswers")
* #ORM\JoinColumn(name="answer_id")
* #Gedmo\Versioned
*/
protected $answer;
Then, there's a form type
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Doctrine\ORM\EntityRepository;
class RegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', NULL, array('label' => 'E-Mail'))
[...]
->add('lifestyle', 'collection', array(
'type' => new RegistrationLifestyleType(),
'allow_add' => false,
'allow_delete' => false,
'label' => false,
))
and now there should be a related RegistrationLifestyleType. But I've no idea how it should look like. I expect, that there are three choice fields in my registration form, showing a question (as label) and bunch of answers (as choice field) related to these questions. The UserManager assigns three questions to a newly created user, so one can get a question with:
$lifestyles = $user->getLifestyles();
foreach ($lifestyles as $lifestyle) {
$question = $lifestyle->getQuestion(); // echo $question->getQuestion();
$answers = $lifestyle->getQuestion()->getAnswers(); // loop through $answers and echo $answer->getAnswer();
}
But how I can modify the form type, to get this working. Important: my intention is to use built-in functionality as most as possible and trying to avoid inflating form types and others by injecting service containers and entity managers.
Found a solution, perhaps someone can use it. The problem seems, that LifestyleQuestion and LifestyleAnswer are 1:n relations at the same object (UserLifestyle), so Symfony does not know how to deal with it, even if I set the LifestyleQuestion to a specific question in UserManager already. Regarding https://stackoverflow.com/a/9729888/672452 one has to use form listeners, so the parent object is available in sub form. So here is my "simple" RegistrationLifestyleType (without using any injected container or manager):
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Security\Core\SecurityContext;
class RegistrationLifestyleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($builder) {
$form = $event->getForm();
$lifestyle = $event->getData();
if (!($lifestyle instanceof \Acme\UserBundle\Entity\UserLifestyle) || !$lifestyle->getQuestion()) return;
$label = $lifestyle->getQuestion()->getQuestion();
$questionId = $lifestyle->getQuestion()->getId();
$form->add('answer', 'entity', array(
'class' => 'AcmeUserBundle:LifestyleAnswer',
'empty_value' => '',
'property' => 'answer',
'query_builder' => function(EntityRepository $er) use ($questionId) {
return $er
->createQueryBuilder('t1')
->andWhere('t1.question = :question')
->setParameter('question', $questionId)
->orderBy('t1.answer', 'ASC')
;
},
'label' => $label,
));
});
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\UserBundle\Entity\UserLifestyle',
'error_bubbling' => false,
));
}
}

Cross reference in Symfony2 form not work as expected

This is my first question here, so please excuse any mistakes - I'll try to avoid them the next time. ;-)
I've written a custom RegistrationFormType for the FOSUserBundle. This form handles - in addition to the default fields of the bundle - a PlayerType. This PlayerType itself again contains a PlayerSkillsType. Here the classes:
class RegistrationFormType extends BaseType
{
public function buildForm(FormBuilder $builder, array $options)
{
parent::buildForm($builder, $options);
$builder->add('player', new PlayerType());
}
public function getName()
{
return 'signup_form';
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Acme\AcmeBundle\Entity\User',
);
}
}
class PlayerType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('firstname');
$builder->add('lastname');
$builder->add('age');
$builder->add('playerSkills', new PlayerSkillsType());
}
public function getName()
{
return 'player_form';
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Acme\AcmeBundle\Entity\Player',
);
}
}
class PlayerSkillsType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('tackling');
$builder->add('passing');
$builder->add('shooting');
}
public function getName()
{
return 'playerSkills_form';
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Acme\AcmeBundle\Entity\PlayerSkills',
);
}
}
/**
* #ORM\Entity
*/
class Player
{
/**
* #ORM\OneToOne(targetEntity="PlayerSkills", cascade={"persist"})
*
* #var PlayerSkills
*/
private $playerSkills;
}
/**
* #ORM\Entity
*/
class PlayerSkills
{
/**
* #ORM\OneToOne(targetEntity="Player", cascade={"persist"})
*
* #var Player
*/
private $player;
}
(I've left out getters and setters and unimportant properties and methods.)
This is working fine so far, the form is shown and persisted. Now, my problem is, that after persisting the data, the PlayerSkills entity in the data is missing the reference back to the Player entity.
I think it's something that I need to tell the PlayerSkillsType that it shall also add the reference in the form builder..? Or maybe this is issue in the Doctrine annotations?
Any hint is very appreciated! :-)
The problem could come from the initialization of your data and/or doctrine mapping.
The form will create the data_class if none is passed using $form->setData.
When you submit the form and bind data, It will call $player>setPlayerSkills($playerSkill),
but it won't call $playerSkill->setPlayer($player);
Depending of the owning side of your oneToOne association, you should call one of the two methods so that Doctrine will be aware of this association ( http://docs.doctrine-project.org/projects/doctrine-orm/en/2.0.x/reference/association-mapping.html#owning-side-and-inverse-side ).
Try to modify your annotation mapping in PlayerSkills to introduce the inversedBy information also ( http://docs.doctrine-project.org/projects/doctrine-orm/en/2.0.x/reference/association-mapping.html#one-to-one-bidirectional ).
It should be something like this:
/**
* #ORM\OneToOne(targetEntity="Player", mappedBy="playerSkills", cascade={"persist"})
*
* #var Player
*/
private $player;
Same thing for the Player class:
/**
* #ORM\OneToOne(targetEntity="PlayerSkills", inversedBy="player" cascade={"persist"})
*
* #var PlayerSkills
*/
private $playerSkills;
Last, you can code your methods to automatically synchronize inverse side, as explained here: http://docs.doctrine-project.org/projects/doctrine-orm/en/2.0.x/reference/association-mapping.html#picking-owning-and-inverse-side .

Resources