Symfony2 & Doctrine : One-To-One relation and persistence of the id references - symfony

I have a problem with establishing a bidirectional One-To-One relationship between two entities.
I have an entity "Campaign" that CAN reference another entity "DonationData". A donation data that is created is always linked to a campaign. I want a bidirectional relationship because I want to be able to find the related campaign from a DonationData entity.
This is my code for the Campaign Entity :
<?php
/**
* #ORM\Entity
* ...
*/
class Campaign
{
...
/**
* #ORM\OneToOne(targetEntity="DonationData", mappedBy="campaign", cascade={"persist", "remove"})
*/
protected $donationData;
...
/**
* Set donationData
*
* #param DonationData $donationData
* #return Campaign
*/
public function setDonationData(DonationData $donationData = null)
{
$this->donationData = $donationData;
return $this;
}
...
?>
And the related code for my DonationData entity :
<?php
/**
* #ORM\Entity
* ...
*/
class DonationData
{
...
/**
* #ORM\OneToOne(targetEntity="Campaign")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="campaign_id", referencedColumnName="id")
* })
*/
protected $campaign;
...
/**
* Set campaign
* #param Campaign $campaign
*
* #return DonationData
*/
public function setCampaign($campaign)
{
$this->campaign = $campaign;
return $this;
}
...
?>
The piece of code where I'm adding my DonationData form in my Campaign Form (CampaignType.php)
<?php
...
->add('donationData', new DonationDataType(), array(
'label' => false
))
...
?>
And in the CampaignController.php side, when I'm handling the creation operations, I didn't change anything : I'm just persisting the related entity binded from the request, and then flushing the Entity Manager :
<?php
...
$em->persist($campaign);
$em->flush();
...
?>
My problem is when I want to persist a Campaign entity form that has an embedded DonationData form inside. I successfully persist both the Campaign and the DonationData entities, but there is only the donation_data_id reference in the Campaign entry that is persisted. When I look in database to the persisted DonationData, the campaign_id is always set to NULL.
Do you see any explanation for that?
Thank you.

In class DonationData, field $campaign you don't specify the inversedBy property, should be
#ORM\JoinColumn(name="campaign_id", referencedColumnName="id", inversedBy="donationData")
and did you set the cascade_validation default option in the parent form?
as in http://symfony.com/doc/current/reference/forms/types/form.html

Related

Doctrine 2 - How to remove one related entity and set to null others?

I have following entities in my database:
class Product
{
// ...
/**
* #OneToMany(targetEntity="Feature", mappedBy="product")
**/
private $features;
// ...
}
class Feature
{
// ...
/**
* #ManyToOne(targetEntity="Product", inversedBy="features")
* #JoinColumn(name="product_id", referencedColumnName="id")
**/
private $product;
// ...
}
In my database, I have one product entity and related to it many features. This is example, but for some reasons I need to remove Product entity and simultaneously set to NULL fields "product_id" in features entity which were assigned to the deleted object.
It is possible to do that calling only $this->getDoctrine()->getManager()->remove($product) ?
edit your entity mapping:
class Feature
{
/**
* #ManyToOne(targetEntity="Product", inversedBy="features")
* #JoinColumn(name="product_id", referencedColumnName="id", onDelete="set null")
**/
private $product;
}
now, update your schema

symfony2 entity relationship

I try to make a social network with symfony2. I am not sure of how to modelize a user and his friends in the User entity.
I mean a friend is also a user so I have a User entity linked to another User entity.
How in the annotation of the User entity can I express that kind of relationship?
For the moment I have something like :
the User entity :
...
/**
* #ORM\ManyToMany(targetEntity="User")
*
*/
private $friends;
...
with $friends as an arraycollection. Is it correct?
That is a self-referencing ManyToMany relationship. It's actually the example used in the documentation.
Basically, you would so something like this:
<?php
/** #Entity **/
class User
{
// ...
/**
* #ManyToMany(targetEntity="User", mappedBy="myFriends")
**/
private $friendsWithMe;
/**
* #ManyToMany(targetEntity="User", inversedBy="friendsWithMe")
* #JoinTable(name="friends",
* joinColumns={#JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="friend_user_id", referencedColumnName="id")}
* )
**/
private $myFriends;
public function __construct() {
$this->friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection();
$this->myFriends = new \Doctrine\Common\Collections\ArrayCollection();
}
// ...
}
Note that it's set as a bidirectional relation. That is, you can get the list of friends and the list of people that are friends with a User.

M:N relation with more fields

So far the relations M:N I've built are simple intermediate tables where Doctrine does not need to create an entity for this table.
I have two entities Product and ingredient, they have a relationship M:N easily describe with Doctrine as follows. but the real problem is when i need store a amount field in the relation (I need to list the ingredients and also the amount).
How can solve this?
class Product {
//...
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="MyBundle\Entity\Ingredient", inversedBy="product")
* #ORM\JoinTable(name="product_ingredient",
* joinColumns={
* #ORM\JoinColumn(name="product_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="ingredient_id", referencedColumnName="id")
* }
* )
*/
private $ingredient;
//...
class Ingredient {
// ...
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="MyBundle\Entity\Product", mappedBy="ingredient")
*/
private $product;
// ...
You can't do it without intermediate entity really, that's why doctrine docs says that ManyToMany relationships are rare.
It's also the easiest thing to do, just add RecipeItem entity which will store information about Ingredient and amount and link it with relationship of ManyToOne to Product
Edit
Since I was asked to provide an example:
class Product {
//...
/**
* #ORM\OneToMany(targetEntity="RecipeItem", mappedBy="product")
*/
private $ingredients;
//...
class RecipeItem {
// ...
/**
* #ManyToOne(targetEntity="Product", inversedBy="ingredients")
**/
private $product;
/**
* #ManyToOne(targetEntity="Ingridient")
**/
private $ingredient;
/**
* #Column(type="decimal")
**/
private $amount;
}
class Ingredient {
// Don't use bidirectional relationships unless you need to
// it impacts performance
}
Now having a product you can simply:
foreach($product->getIngridients() as $item){
echo "{$item->getAmount()} of {$item->getIngridient()->getName()}";
}

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

Automatically updating created_by in the model

I wanted to have a created_by field for my model, say Product, that is automatically updated and I am using FOSUserBundle and Doctrine2. What is the recommended way of inputting the User id into Product?
Can I do it in the Product model? I am not sure how to do so and any help would be wonderful. Thanks!
I want to do something like this in the model, but I don't know how to get the user id.
/**
* Set updatedBy
*
* #ORM\PrePersist
* #ORM\PreUpdate
* #param integer $updatedBy
*/
public function setUpdatedBy($updatedBy=null)
{
if (is_null($updatedBy)) {
$updatedBy = $user->id;
}
$this->updatedBy = $updatedBy;
}
To relate the user to the product you want to associate the two entities:
http://symfony.com/doc/current/book/doctrine.html#entity-relationships-associations
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="products")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* You may need to use the full namespace above instead of just User if the
* User entity is not in the same bundle e.g FOS\UserBundle\Entity\User
* the example is just a guess of the top of my head for the fos namespace though
*/
protected $user;
and for the automatic update field you may be after lifecyclecallbacks:
http://symfony.com/doc/current/book/doctrine.html#lifecycle-callbacks
/**
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks()
*/
class Product
{
/**
* #ORM\PreUpdate
*/
public function setCreatedValue()
{
$this->created = new \DateTime();
}
}
EDIT
This discussion talks about getting the container in the entity in which case you could then get the security.context and find the user id from that if you mean to associate the current user to the product they edited:
https://groups.google.com/forum/?fromgroups#!topic/symfony2/6scSB0Kgds0
//once you have the container you can get the session
$user= $this->container->get('security.context')->getToken()->getUser();
$updated_at = $user->getId();
Maybe that is what you are after, not sure it is a good idea to have the container in the entity though, could you not just set the user on the product in the update action in your product controller:
public function updateAction(){
//....
$user= $this->get('security.context')->getToken()->getUser();
$product->setUser($user)
}

Resources