How to persist entities from an embedded form submission? - symfony

I have a Person entity and an Address entity, set up with a bi-directional one-to-one relationship, with the FK on Address:
...
class Person
{
...
/**
* #ORM\OneToOne(targetEntity="Address", mappedBy="person")
*/
protected $address;
...
}
...
class Address
{
...
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="Person", inversedBy="address")
* #ORM\JoinColumn(name="personID", referencedColumnName="id")
*/
protected $person;
}
The Address Entity does NOT have a dedicated primary key, it instead derives its identity through the foreign key relationship with Person, as explained here
The form I have for creating a new Person also embeds the form for Address. When the form is submitted, this controller action is executed:
public function createAction(Request $request)
{
$person = new Person();
$form = $this->createCreateForm($person);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($person);
$em->flush();
return $this->redirect($this->generateUrl('people'));
}
return array(
'person' => $person,
'form' => $form->createView(),
);
}
I've inspected the data, and $person has its $address property filled out with the proper form details as expected. However, once the $person object is persisted, I get the error:
A new entity was found through the relationship
'Acme\AcmeBundle\Entity\Person#address' that was not configured to
cascade persist operations for entity...
I've tried a couple of things and none seem to work:
Setting cascade={"persist"} on the OneToOne relationship definition on the Person object. Doing so results in error:
Entity of type Acme\AcmeBundle\Entity\Address is missing an assigned
ID for field 'person'...
On the Person#setAddress method, I've taken the $address parameter and manually called $address->setPerson($this) on it. Doesn't work either.
It seems like my problem is that Doctrine is trying to save the Address object before saving the Person object, and it can't because it needs to know the ID of the associated Person first.
For instance, If I alter the the persist code to something like this, it works:
...
// Pull out the address data and remove it from the Person object
$address = $person->getAddress();
$person->setAddress(null);
// Save the person object and flush so we get an ID
$em->persist($person);
$em->flush();
// Now set the person object on the address and save the address
$address->setPerson($person);
$em->persist($address);
$em->flush();
...
How can I do this properly? I want to retain the ability to embed forms with this type of one-to-one relationship, but things are starting to get complicated. How do I get Doctrine to flush the $person object before flushing the $address object, without manually doing it myself like above?

Keep the cascade=persist.
Then modify Person::setAddress
class Person
{
public function setAddress($address)
{
$this->address = $address;
$address->setPerson($this); //*** This is what you are missing ***
This is a very common question but it's hard to search for.

Your mapping is incorrect. You are using #ORM\Id incorrectly on $person.
If you haven't yet, add a real $id to Address and add again `cascade={"persist"}.
class Person
{
...
/**
* #ORM\OneToOne(targetEntity="Address", mappedBy="person", cascade={"persist"})
*/
protected $address;
...
}
...
class Address
{
...
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="Person", inversedBy="address")
* #ORM\JoinColumn(name="personID", referencedColumnName="id")
*/
protected $person;
}
If Person were the owning side, Address should also be automatically persisted by Doctrine, don't know your model but maybe you should consider changing it.

Related

The identifier generation strategy for this entity requires the ID field to be populated before EntityManager#persist() is called

I'm tying to create one to many relations
A have class
class Interview {
/**
* #OneToMany(targetEntity="Question", mappedBy="question")
*/
private $questions;
public function __construct() {
$this->questions = new ArrayCollection();
}
public function __toString() {
return $this->id;
}
/**
* #return Collection|Question[]
*/
public function getQuestions() {
return $this->questions;
}
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
......
}
another
class Question {
/**
* #ManyToOne(targetEntity="Interview", inversedBy="interview")
* #JoinColumn(name="interview_id", referencedColumnName="id")
*/
private $interview;
public function getInterview() {
return $this->interview;
}
public function setInterview(Interview $interview) {
$this->interview = $interview;
return $this;
}
/**
* #ORM\Column(type="integer")
* #ORM\Id
*/
private $interview_id;
......
}
and Controller for all this
if ($form->isSubmitted() && $form->isValid()) {
$interview = new Interview();
$question = new Question();
$em->persist($interview);
$question->setInterview($interview);
$question->setTitle($request->get('title'));
$em->persist($question);
$em->flush();
return $this->redirectToRoute('homepage');
}
i'm receiving an error:
Entity of type AppBundle\Entity\Question is missing an assigned ID for
field 'interview_id'. The identifier generation strategy for this
entity requires the ID field to be populated before
EntityManager#persist() is called. If you want automatically generated
identifiers instead you need to adjust the metadata mapping
accordingly.
Don't understand what the problem and how to fix it.
To enforce loading objects from the database again instead of serving them from the identity map. You can call $em->clear(); after you did $em->persist($interview);, i.e.
$interview = new Interview();
$em->persist($interview);
$em->clear();
It seems like your project config have an error in doctrine mapped part.
If you want automatically generated identifiers instead you need to
adjust the metadata mapping accordingly.
Try to see full doctrine config and do some manipulation with
auto_mapping: false
to true as example or something else...
Also go this , maybe it will be useful.
I am sure, its too late to answer but maybe someone else will get this error :-D
You get this error when your linked entity (here, the Interview entity) is null.
Of course, you have already instantiate a new instance of Interview.But, as this entity contains only one field (id), before this entity is persited, its id is equal to NULL. As there is no other field, so doctrine think that this entity is NULL. You can solve it by calling flush() before linking this entity to another entity

Symfony2 entity won't flush with custom primary key

I'm working on a project in Symfony2 with a legacy database, that uses a string to have a 6 digit 'franchisee_number' with floating 0's on the front - eg. 000123. When I try to flush the entity, I get an error that shows the SQL statement it tried to do, and it doesn't even try to insert to insert my primary key field. Up until the point of flushing the entity, the 'franchisee_id' is showing up in the entity, so I'm assuming there's a problem with Doctrine not wanting to set the Primary Key, but have it generated.
My problem is very similar to this question: when flush() primary key does not get inserted, but I've tried the answers listed, and they don't work.
Here is the code from my Entity for the 'franchisee_number' field:
/**
* #var string
* #ORM\Id
* #ORM\GeneratedValue(strategy="NONE")
* #ORM\Column(name="franchisee_number", type="string", length=255, nullable=false)
*
*/
private $franchiseeNumber;
I assumed that #ORM\GeneratedValue(strategy="NONE") would tell doctrine I'm inserting my own value into the field, and not trying to generate it.
And my controller code
public function createAction(Request $request)
{
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$entity = new Accounts();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager('inertia');
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('accounts_show', array('id' => $entity->getId())));
}
return $this->render('InertiaBundle:Accounts:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
I also made sure there were no xml config files in the Bundle/Resources/config/doctrine directory.
Thanks in advance for the help!
I wasted a lot of time trying to figure this out, and couldn't really find a great answer. I just decided to do it the hard way and wrote a custom query with Doctrine DBAL, inspired by this question/answer: https://stackoverflow.com/a/15650290/1647183
It works like a charm for me!
If you use strategy="NONE", you must create a identifier in __construct or another systems before persist/flush entity.
Example:
class MyEntity
{
/**
* #var string
*
* #ORM\Id
* #ORM\Column(type="string", name="id")
* #ORM\GeneratedValue(strategy="NONE")
*/
private $id;
public function __construct()
{
$this->id = 123; // Inject you custom ID here!
}
}

How to use the registration procedure of FOSUserBundle from a third controller

I have to persist an entity (let's call it Entity for simplicity) in the database that has to be referenced to a User handled with FOSUserBundle. To make this reference I have a column entity_table.userId.
When the new Entity is created, I have to:
Create the User through the registration procedure of FosUserBundle;
Get the ID of the new created User: [meta code] $userId = $get->newCreatedUserId();
Set this id in Entity: $entity->setUserId($userId);
Persist the Entity to the database.
How can I integrate the registration procedure of FosUserBundle into the controller that persists my Entity?
MORE DETAILS
In the first time I tried to simply copy the code from the method registerAction() of the RegistrationController of FOSUserBundle: a quick and dirty approach that, anyway didn't work as i get an error as the User class i passed was wrong (I passed my custom User entity I use to overwrite the bundle).
This kind of approach has also other drawbacks:
I cannot control the registration procedure (send or decide to not send confirmation e-mails, for example);
I cannot use the builtin checks on passed data;
I cannot be sure that on FOSUserBundles updates my custom method continue to work
Others I cannot imagine at the moment...
So, I'd like to create the user in the cleanest way possible: how can i do this? Which should be a good approach?
A controller forwarding?
Anyway, an "hardcoded" custom method that emulates the registerAction() method?
A custom registration form?
I have read a lot of discussions here at StackOverflow and on Internet, I read the documentation of FOSUserBundle and of Symfony too, but I cannot decide for the good approach, also because I'm not sure I have understood all the pros and cons of each method.
If someone can put me on the right way... :)
SOMETHING MORE ABOUT MY REGISTRATION FLOW
I have a getStarted procedure handled by the controller GetStarteController.
In it I have two methods:
indexAction(), that displays a registration form with only the field "email";
endAction(), that receive the form and creates a Company using the passed e-mail (it gets the domain part only of the email).
HERE IS A WORKING MESSY CODE (inside it for Companies and Stores are called some methods that exists in the source code but are not in the posted classes below, as setBrand() or setUrl(), for example).
// AppBundle/Controller/getStartedController.php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;
use MyVendor\UserBundle\Entity\User;
use AppBundle\Entity\Companies;
use AppBundle\Entity\Stores;
class GetStartedController extends Controller
{
/**
* #Route("getstarted")
* #Template()
*/
public function indexAction()
{
$data = array();
$form = $this->createFormBuilder($data, array(
'action' => $this->generateUrl('getStartedEnd'),
))
->add('email', 'email')
->add('submit', 'submit')
->getForm();
return array(
'form' => $form->createView(),
);
}
/**
* #Route("getstarted/end", name="getStartedEnd")
* #Template()
*/
public function endAction(Request $request)
{
$form = $this->createFormBuilder()
->add('email', 'email')
->add('submit', 'submit')
->getForm();
$form->handleRequest($request);
if ($form->isValid()) {
$data = $form->getData();
} else {
/** #todo here we have to raise some sort of exception or error */
echo 'no data submitted (See the todo in the code)';exit;
}
// Pass the email to the template
$return['email'] = $data['email'];
// Get the domain part of the email and pass it to the template
$domain = explode('#', $data['email']);
$return['domain'] = $domain[1];
// 1) Create the new user
$user = new User();
// Get the token generator
$tokenGenerator = $this->container->get('fos_user.util.token_generator');
$user->setEmail($return['email']);
$userRandomUsername = substr($tokenGenerator->generateToken(), 0, 12);
$user->setUsername('random-' . $userRandomUsername);
$plainPassword = substr($tokenGenerator->generateToken(), 0, 12);
$encoder = $this->container->get('security.password_encoder');
$encoded = $encoder->encodePassword($user, $plainPassword);
// Set the password for the user
$user->setPassword($encoded);
/** #var $userManager \FOS\UserBundle\Model\UserManagerInterface */
$userManager = $this->get('fos_user.user_manager');
// Perstist the user in the database
$userManager->updateUser($user);
$userId = $user->getId();
// 2) Create the Company object
$company = new Companies();
$company->setBrand($return['domain'])
->setAdded(new \DateTime())
->setOwnerId($userId);
// 3) Create the Store object
$store = new Stores();
$store->setEmail($return['email'])
->setUrl($return['domain'])
->setAdded(new \DateTime());
// Get the Entity Manager
$em = $this->getDoctrine()->getManager();
// Persist Company and get its ID
$em->persist($company);
$em->flush();
$return['companyId'] = $company->getId();
// Set the property branchOf of the Store object
$store->setBranchOf($return['companyId']);
// Persist the Store object
$em->persist($store);
$em->flush();
$return['storeId'] = $store->getId();
return $return;
}
}
Here the User Entity that ovewrites the one provided by FOSUserBundle
// MyVendor/UserBundle/Entity/User.php
namespace MyVendor\UserBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="prefix_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
// your own logic
}
}
Some essential code of Companies.php
// AppBundle/Entity/Companies.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Companies
*
* #ORM\Table(name="companies")
* #ORM\Entity
*/
class Companies
{
/**
* #var integer
*
* #ORM\Column(name="ownerId", type="integer", nullable=false)
*/
private $ownerid;
/**
* Set ownerid
*
* #param integer $ownerid
* #return Companies
*/
public function setOwnerid($ownerid)
{
$this->ownerid = $ownerid;
return $this;
}
/**
* Get ownerid
*
* #return integer
*/
public function getOwnerid()
{
return $this->ownerid;
}
}
Some essential code of Stores.php
// AppBundle/Entity/Stores.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Stores
*
* #ORM\Table(name="stores", uniqueConstraints={#ORM\UniqueConstraint(name="branchOf", columns={"branchOf"})})
* #ORM\Entity
*/
class Stores
{
/**
* #var integer
*
* #ORM\Column(name="branchOf", type="integer", nullable=false)
*/
private $branchof;
/**
* Set branchof
*
* #param integer $branchof
* #return Stores
*/
public function setBranchof($branchof)
{
$this->branchof = $branchof;
return $this;
}
/**
* Get branchof
*
* #return integer
*/
public function getBranchof()
{
return $this->branchof;
}
}
You can use a custom registration form but the best way is clearly to listen to registration event dispatched by FOSUser.
Here is an example :
class RegistrationListener implements EventSubscriberInterface
{
/**
* L'entity manager
*
* #var EntityManager
*/
private $em;
/**
* Constructeur de l'EventListener
*
* #param \Doctrine\ORM\EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager)
{
$this->em = $entityManager;
}
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_INITIALIZE => 'onRegistrationInit',
);
}
/**
* Triggered when FOSUserEvents::REGISTRATION_INITIALIZE is caught.
*
* #param \FOS\UserBundle\Event\UserEvent $userEvent
*/
public function onRegistrationInit(UserEvent $userEvent)
{
$user = $userEvent->getUser();
// Define your own logic there
}
}
Don't forget to make this listener a service:
#services.yml
services:
oe_user.registration:
class: OrienteExpress\UserBundle\EventListener\RegistrationListener
# arguments are optional but you still can need them
# so I let the EM as example which is an often used parameter
arguments:
entityManager: "#doctrine.orm.entity_manager"
tags:
- { name: kernel.event_subscriber }
You'll find the complete list of event dispatched by FOSUser here
Moreover, Symfony entities are a model of objects. That said, you need to understand that you don't work with ids within your model, but object.
You should not use thing such as $var->setUserId() within entites. Doctrine is there to manage your relations, so be carefull about this. You might face unexpected problem by not using Symfony & Doctrine the way it has been designed for.
EDIT:
In your company entity, your relation is beetween a Company and a User objects. That means you dont need a User id in your company but just a instance of User.
I think you might go back to the basics before wanting to do advanced stuff.
Your relation beetween the user and the company should not be designed by an integer attribute but a real doctrine relation.
Ex:
class Company {
/**
* #ORM\ManyToOne(targetEntity="Path\To\User")
* #ORM\JoinColumn(nullable=false)
*/
private $owner;
/**
* #param $user User
*/
public function setUser(User $user)
{
$this->user = $user;
}
}
Then when you'll create a new company. You won't need to know the User's id or even insert it to make the link between them. But if you are not aware yet of this, once again, I think you should go back to the basics of Symfony since this is one of the most (maybe the most) important feature to master.

Symfony2 Many-to-Many with attribute object persistance (Many-to-One/One-to-Many)

I have looked a couple of question on stack overflow. Many questions seems to be close to what I am asking, but I can't find my answer throughout them. So here I am asking a brand new question.
Please feel free to ask me for more info if I omitted something important.
Context
I am building a Symfony2 Web application that manages some users avalability to some events on a calendar. So I have 3 entities to represent that. "Utilisateur" that means user which is an extension of FOSUserBundle, "Evenement" which means event and an intermediary object that stores status of the relation between those last two. The intermediate entity name is "Disponibilite" which means availability.
What I want to do
When a user clicks on an event on the calendar, it brings him to a page where he can specify if he is available or not from a dropdown list and then write a brief comment to motivate his decision. He then submits his availability and the server saves his availability for the event.
Problem
In my form for submiting my availability, I can't seem to find a way to inject information about the related objects (Evenement and Utilisateur). I don't want to make an entity choice field since the User and Event are already chosen. I want to make a sort of hidden entity field. Is that possible? Is it the good way to do?
Note
The user may or may not have a Disponibilite row in the database. If we look for availability of User A for Event E and there is no result, it means that this user has never given his availability for this event. In the case that there is a result, it means that the user has already given his availability and is currently modifying his availability for the event.
I am starting with Symfony2, so I may be missing something very basic
Code
Here is the simplification of my code.
Entity\Utilisateur.php
This is an heritage of the BaseUser from FOSUserBundle
class Utilisateur extends BaseUser
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
}
Entity\Evenement.php
An entity that represents an event.
class Evenement
{
/**
* #ORM\Id
* #ORM\OneToMany(targetEntity="Musique\GestionBundle\Entity\Disponibilite", mappedBy="evenement")
*/
private $disponibilite;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
}
Entity\Disponibilite.php
An entity that represents the relation between Evenement and Utilisateur.
class Disponibilite
{
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Musique\GestionBundle\Entity\Evenement",inversedBy="disponibilite")
*/
private $evenement;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Musique\UtilisateurBundle\Entity\Utilisateur")
*/
private $utilisateur;
}
Form\DisponibiliteType.php
The for I use to get information about availability for a certain event for a certain user
class Disponibilite extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('disponibilite','choice', array(
'choices' => array("DispOui"=>"Disponible","DispCondition"=>"Incertain","DispNon"=>"Non-Disponible"),
'required' => true))
->add('raison')
// Add entity field here? hidden field?
;
}
In the controller
Action to load the view of DisponibiliteType (the form)
function disponibiliteAction($id){
$repo_evenement = $this->getDoctrine()->getManager()->getRepository('MusiqueGestionBundle:Evenement');
$repo_dispo = $this->getDoctrine()->getManager()->getRepository('MusiqueGestionBundle:Disponibilite');
$disponibilite = $repo_dispo->findBy(
array('evenement'=>$id,'utilisateur'=>$this->getUser())
);
$evenement = $repo_evenement->find($id);
if(count($disponibilite) > 0){
$disponibilite = $disponibilite[0];
} else {
$disponibilite = new Disponibilite();
$disponibilite->setEvenement($evenement);
$disponibilite->setUtilisateur($this->getUser());
}
$form = $this->createForm(new DisponibiliteType(),$disponibilite);
return $this->render('MusiqueGestionBundle:Calendrier:disponibilite.html.twig',array('form' => $form->createView(),'evenement'=> $evenement));
}
Action triggered when the form is posted
function sendDisponibiliteAction(){
$request = $this->getRequest();
$repo_evenement = $this->getDoctrine()->getManager()->getRepository('MusiqueGestionBundle:Evenement');
$evenement = $repo_evenement->find($request->get('evenement'));
$repo_utilisateur = $this->getDoctrine()->getManager()->getRepository('MusiqueUtilisateurBundle:Utilisateur');
$utilisateur = $repo_utilisateur->find($request->get('utilisateur'));
$disponibilite = new Disponibilite();
$disponibilite->setEvenement($evenement);
$disponibilite->setUtilisateur($utilisateur);
$form = $this->createForm(new DisponibiliteType(),$disponibilite);
$form->bind($request);
if($form->isValid()){
$em = $this->getDoctrine()->getManager();
$em->persist($disponibilite);
$em->flush();
}
return $this->redirect($this->generateUrl("calendrier"));
}
That's not so hard, actually. I've made it with just one action, if you want, you can split it like you did before.
function disponibiliteAction($id)
{
$em = $this->getDoctrine()->getManager();
$repo_evenement = $em->getRepository('MusiqueGestionBundle:Evenement');
$repo_dispo = $em->getRepository('MusiqueGestionBundle:Disponibilite');
$evenement = $repo_evenement->findOneById($id);
if (is_null($evenement)) {
throw new \Symfony\Component\HttpKernel\Exception\NotFoundHttpException();
}
$disponibilite = $repo_dispo->findOneBy(
array('evenement'=>$evenement,'utilisateur'=>$this->getUser())
);
if (is_null($disponibilite)) {
$disponibilite = new Disponibilite();
$disponibilite->setEvenement($evenement);
$disponibilite->setUtilisateur($this->getUser());
}
$form = $this->createForm(new DisponibiliteType(),$disponibilite);
if ($this->getRequest()->isMethod('post')) {
$form->submit($this->getRequest());
if ($form->isValid()) {
if ($disponibilite->getId() === null) {
$em->persist($disponibilite);
}
$em->flush();
return $this->redirect($this->generateUrl("calendrier"));
}
}
return $this->render('MusiqueGestionBundle:Calendrier:disponibilite.html.twig',array('form' => $form->createView(),'evenement'=> $evenement));
}

Symfony 2 - ManyToOne Bidirectional relationship behaviour

I had a big time trying to figure out how to setup a ManyToOne -> OneToMany relationship with Doctrine 2 and it still not working...
Here is the application behaviour:
A site has Pages
A User can write Comment on a Page
Here are my Entities (simplified):
Comment Entity:
**
* #ORM\Entity
* #ORM\Table(name="comment")
*/
class Comment {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* Many Comments have One User
*
* #ORM\ManyToOne(targetEntity="\Acme\UserBundle\Entity\User", inversedBy="comments")
*/
protected $user;
/**
* Many Comments have One Page
*
* #ORM\ManyToOne(targetEntity="\Acme\PageBundle\Entity\Page", inversedBy="comments")
*/
protected $page;
...
/**
* Set user
*
* #param \Acme\UserBundle\Entity\User $user
* #return Comment
*/
public function setUser(\Acme\UserBundle\Entity\User $user)
{
$this->user = $user;
return $this;
}
/**
* Set page
*
* #param \Acme\PageBundle\Entity\Page $page
* #return Comment
*/
public function setPage(\Acme\PageBundle\Entity\Page $page)
{
$this->page = $page;
return $this;
}
User Entity:
/**
* #ORM\Entity
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* The User create the Comment so he's supposed to be the owner of this relationship
* However, Doctrine doc says: "The many side of OneToMany/ManyToOne bidirectional relationships must be the owning
* side", so Comment is the owner
*
* One User can write Many Comments
*
* #ORM\OneToMany(targetEntity="Acme\CommentBundle\Entity\Comment", mappedBy="user")
*/
protected $comments;
...
/**
* Get Comments
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getComments() {
return $this->comments ?: $this->comments = new ArrayCollection();
}
Page Entity:
/**
* #ORM\Entity
* #ORM\Table(name="page")
*/
class Page
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* One Page can have Many Comments
* Owner is Comment
*
* #ORM\OneToMany(targetEntity="\Acme\CommentBundle\Entity\Comment", mappedBy="page")
*/
protected $comments;
...
/**
* #return \Doctrine\Common\Collections\Collection
*/
public function getComments(){
return $this->comments ?: $this->comments = new ArrayCollection();
}
I want a bidirectional relationship to be able to get the collection of Comments from the Page or from the User (using getComments()).
My problem is that when I try to save a new Comment, I get an error saying that doctrine is not able to create a Page entity. I guess this is happening because it's not finding the Page (but it should) so it's trying to create a new Page entity to later link it to the Comment entity that I'm trying to create.
Here is the method from my controller to create a Comment:
public function createAction()
{
$user = $this->getUser();
$page = $this->getPage();
$comment = new EntityComment();
$form = $this->createForm(new CommentType(), $comment);
if ($this->getRequest()->getMethod() === 'POST') {
$form->bind($this->getRequest());
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$comment->setPage($page);
$comment->setUser($user);
$em->persist($comment);
$em->flush();
return $this->redirect($this->generateUrl('acme_comment_listing'));
}
}
return $this->render('AcmeCommentBundle:Default:create.html.twig', array(
'form' => $form->createView()
));
}
I don't understand why this is happening. I've checked my Page object in this controller (returned by $this->getPage() - which return the object stored in session) and it's a valid Page entity that exists (I've checked in the DB too).
I don't know what to do now and I can't find anyone having the same problem :(
This is the exact error message I have:
A new entity was found through the relationship
'Acme\CommentBundle\Entity\Comment#page' that was not configured to
cascade persist operations for entity:
Acme\PageBundle\Entity\Page#000000005d8a1f2000000000753399d4. To solve
this issue: Either explicitly call EntityManager#persist() on this
unknown entity or configure cascade persist this association in the
mapping for example #ManyToOne(..,cascade={"persist"}). If you cannot
find out which entity causes the problem implement
'Acme\PageBundle\Entity\Page#__toString()' to get a clue.
But I don't want to add cascade={"persist"} because I don't want to create the page on cascade, but just link the existing one.
UPDATE1:
If I fetch the page before to set it, it's working. But I still don't know why I should.
public function createAction()
{
$user = $this->getUser();
$page = $this->getPage();
// Fetch the page from the repository
$page = $this->getDoctrine()->getRepository('AcmePageBundle:page')->findOneBy(array(
'id' => $page->getId()
));
$comment = new EntityComment();
// Set the relation ManyToOne
$comment->setPage($page);
$comment->setUser($user);
$form = $this->createForm(new CommentType(), $comment);
if ($this->getRequest()->getMethod() === 'POST') {
$form->bind($this->getRequest());
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($comment);
$em->flush();
return $this->redirect($this->generateUrl('acme_comment_listing'));
}
}
return $this->render('AcmeCommentBundle:Default:create.html.twig', array(
'form' => $form->createView()
));
}
UPDATE2:
I've ended up storing the page_id in the session (instead of the full object) which I think is a better idea considering the fact that I won't have a use session to store but just the id. I'm also expecting Doctrine to cache the query when retrieving the Page Entity.
But can someone explain why I could not use the Page entity from the session? This is how I was setting the session:
$pages = $site->getPages(); // return doctrine collection
if (!$pages->isEmpty()) {
// Set the first page of the collection in session
$session = $request->getSession();
$session->set('page', $pages->first());
}
Actually, your Page object is not known by the entity manager, the object come from the session. (The correct term is "detached" from the entity manager.)
That's why it tries to create a new one.
When you get an object from different source, you have to use merge function. (from the session, from an unserialize function, etc...)
Instead of
// Fetch the page from the repository
$page = $this->getDoctrine()->getRepository('AcmePageBundle:page')->findOneBy(array(
'id' => $page->getId()
));
You can simply use :
$page = $em->merge($page);
It will help you if you want to work with object in your session.
More information on merging entities here

Resources