I have "Project" entity which can have several Benefits, while a benefit belongs to just one Project:
To me it seems a many to one - one to many relationship.
I followed the indication here
The Project entity is:
/**
* #ORM\Entity(repositoryClass="AppBundle\Entity\ProjectRepository")
* #ORM\Table(name="projects")
*/
class Project
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\oneToMany(targetEntity="Benefit", mappedBy="project")
*/
protected $benefits;
/**
* Constructor
*/
public function __construct()
{
$this->benefits = new \Doctrine\Common\Collections\ArrayCollection();
}
// other stuff
/**
* Add benefits
*
* #param \AppBundle\Entity\Benefit $benefits
* #return Project
*/
public function addBenefit(\AppBundle\Entity\Benefit $benefits)
{
$this->benefits[] = $benefits;
return $this;
}
/**
* Remove benefits
*
* #param \AppBundle\Entity\Benefit $benefits
*/
public function removeBenefit(\AppBundle\Entity\Benefit $benefits)
{
$this->benefits->removeElement($benefits);
}
/**
* Get benefits
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getBenefits()
{
return $this->benefits;
}
}
The benefit entity is:
/**
* #ORM\Entity(repositoryClass="AppBundle\Entity\BenefitRepository")
* #ORM\Table(name="benefits")
*/
class Benefit
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
// Other relevant fields
/**
* #ORM\ManyToOne(targetEntity="Project", inversedBy="benefits")
*/
protected $project;
In my controller I was hoping to do:
$project = $em->getRepository('AppBundle:Project')->findOneById(3);
$benefit = new Benefit();
// set some fields for the new Benefit
$benefit->setProject($project);
$em->persist($benefit);
and I was hoping to see the benefits as a collection inside the project entity doing:
$benefits = $project->getBenefits();
But it did not work, so I explicitely did:
$project->addBenefit($benefit);
$em->persist($project);
$benefits = $project->getBenefits();
And I indeed see new the newly created Benefit inside the collection inside project. The problem is that if I rerun this and add a new benefit to the same project, I just get the last one. Of course if in the same portion of code I create 2 benefits and add both, I have a collection of 2, but that's not what I want. On the Benefit side everything is ok: each new Benefit is persisted, all of them correctly pointing to the same Project.
What am I missing?
EDIT:
Here are the steps I make/stuff I checked:
The DB is in sync with the current entity metadata.
The updated Project entity is:
<?php
// src/AppBundle/Entity/Project.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="AppBundle\Entity\ProjectRepository")
* #ORM\Table(name="projects")
*/
class Project
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\oneToMany(targetEntity="Benefit", mappedBy="project", cascade="persist")
*/
protected $benefits;
/**
* Constructor
*/
public function __construct()
{
$this->benefits = new \Doctrine\Common\Collections\ArrayCollection();
}
// Other irrelevant fields
/**
* Add benefits
*
* #param \AppBundle\Entity\Benefit $benefit
* #return Project
*/
public function addBenefit(\AppBundle\Entity\Benefit $benefit)
{
$this->benefits[] = $benefit;
$benefit->setProject($this);
return $this;
}
/**
* Remove benefits
*
* #param \AppBundle\Entity\Benefit $benefits
*/
public function removeBenefit(\AppBundle\Entity\Benefit $benefits)
{
$this->benefits->removeElement($benefits);
}
/**
* Get benefits
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getBenefits()
{
return $this->benefits;
}
}
Note that the removeBenefit is probably not properly implemented, but for the moment it's not relevant.
I clean the Benefit table.
I create a new benefit and attach to the a Project:
$em = $this->getDoctrine()->getManager();
$project = $em->getRepository('AppBundle:Project')->findOneById(3);
$benefit = new Benefit();
$benefit->setName('Name of the benefit');
// here I set other irrelevant fields
$project->addBenefit($benefit);
$em->persist($project);
$em->flush();
The Benefit gets properly persisted to DB. It properly links to the Project:
I then comment all the code in the controller and simply perform:
$em = $this->getDoctrine()->getManager();
$project = $em->getRepository('AppBundle:Project')->findOneById(3);
$benefits = $project->getBenefits();
return $this->render('testBenefits.html.twig', array(
'benefits' => $benefits, 'project' => $project));
If I dump $project I get:
And of course if I dump $benefits I get this:
You are not setting project in your benefit class.
public function addBenefit(\AppBundle\Entity\Benefit $benefit)
{
$this->benefits[] = $benefit;
$benefit->setProject($this); // Add this
return $this;
}
Noticed that I also changed your argument from benefits to benefit as addBenefit deals with one benefit object at a time.
Related
I have 2 Entities.
Part and Inventory
class Part
{
/**
* #ORM\Id
* #ORM\Column(type="string")
*/
private $partNumber;
/** #ORM\Column(name="part_name", type="string") */
private $partName;
/** #ORM\Column(type="string") */
private $warehouseStatus;
....
Inventory.php
class Inventory
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* One Product has One Shipment.
* #ORM\OneToOne(targetEntity="Part")
* #ORM\JoinColumn(name="part_number", referencedColumnName="part_number")
*/
private $partNumber;
/** #ORM\Column(type="decimal") */
private $inStock;
I create the Part in this way
class one {
private function method1 {
$part = new Part();
$part->partNumber = 'blabla';
$part->warehouseStatus = 1;
.....
}
class two {
private function method1 {
$inv = new Inventory();
$inv->partNumber = 'blabla'; // it crashes here
$inv->inStock = 1;
.....
}
}
In class two I'm trying to make a relation with the first object but partNumber crashes since he is expecting an Entity Object as Part and not a string. Is there an integrated doctrine method to create a reference to the part entity without having to instantiate repositories and so forth.
You need to use the getReference function from the EntityManager for that:
/**
* Gets a reference to the entity identified by the given type and identifier
* without actually loading it, if the entity is not yet loaded.
*
* #param string $entityName The name of the entity type.
* #param mixed $id The entity identifier.
*
* #return object|null The entity reference.
*
* #throws ORMException
*/
public function getReference($entityName, $id);
In your case:
$inv->partNumber = $entityManager->getReference(Part::class, $thePartIdYouReference);
I have to internationalize an app and particularly an entity called Program. To do so, I created an other entity ProgramIntl which contains a "locale" attribute (en_GB, fr_FR, etc) and strings which must be internationalized. I want the programIntl attribute in Program to be an associative array (with locale as key).
We have an API to read/write programs. GET and POST works fine but when I want to update data (PUT), the programIntl is not updated: an insert query is launched (and fails because of the unique constraint, but that's not the question).
Here is the code:
In Program.php:
/**
* #var
*
* #ORM\OneToMany(targetEntity="ProgramIntl", mappedBy="program", cascade={"persist", "remove", "merge"}, indexBy="locale", fetch="EAGER")
* #ORM\JoinColumn(nullable=false, onDelete="cascade")
* #Groups({"program_read", "program_write"})
*/
private $programIntl;
public function addProgramIntl($programIntl)
{
$this->programIntl[$programIntl->getLocale()] = $programIntl;
$programIntl->setProgram($this);
return $this;
}
public function setProgramIntl($programIntls)
{
$this->programIntl->clear();
foreach ($programIntls as $locale => $programIntl) {
$programIntl->setLocale($locale);
$this->addProgramIntl($programIntl);
}
}
public function getProgramIntl()
{
return $this->programIntl;
}
In ProgramIntl.php:
/**
* #ORM\Entity(repositoryClass="App\Repository\ProgramIntlRepository")
* #ORM\Table(name="program_intl",uniqueConstraints={#ORM\UniqueConstraint(name="program_intl_unique", columns={"program_id", "locale"})})
*/
class ProgramIntl
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
* #Groups({"program_read", "program_write"})
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Program", inversedBy="programIntl")
* #ORM\JoinColumn(nullable=false)
*/
private $program;
/**
* #ORM\Column(type="string", length=5, options={"fixed" = true})
*/
private $locale;
/**
* #ORM\Column(type="string", length=64)
* #Assert\NotBlank()
* #Groups({"program_read", "program_write"})
*/
private $some_attr;
/* ... */
}
Any idea of what could be the reason of the "insert" instead of "update" ?
Thanks
I forgot to mention that we use api-platform.
But I found the solution myself. In case anyone is interested, adding the following annotation to classes Program and ProgramIntl solved the problem:
/* #ApiResource(attributes={
* "normalization_context"={"groups"={"program_read", "program_write"}},
* "denormalization_context"={"groups"={"program_read", "program_write"}}
* }) */
why Symfony2 performs 40 DB queries if I use following code:
$em = $this->getDoctrine()->getManager();
$records = $em->getRepository('MyWebBundle:Highlight')->findAll();
I thought that findAll() method returns only all items from Highlight entity and associations to other entities replaces Proxy objects. But now findAll() method gettings all associations entities.
Do you know where is the problem ?
indexAction
public function indexAction() {
$em = $this->getDoctrine()->getManager();
$records = $em->getRepository('MyWebBundle:Highlight')->findAll();
$csrf = $this->get('security.csrf.token_manager');
$token = $csrf->refreshToken(self::FORM_TOKEN_ID);
$params = array(
"data" => array(
"all" => $records,
),
"token" => $token->getValue(),
"static" => array(
"add" => $this->generateUrl("admin_highlight_add"),
"edit" => $this->generateUrl("admin_highlight_edit"),
"del" => $this->generateUrl("admin_highlight_del"),
),
);
$ser = $this->get('jms_serializer');
$jsonContent = $ser->serialize($params, 'json');
return array('jsonContent' => $jsonContent);
}
Highlight entity
namespace My\WebBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* Highlight
*
* #JMS\ExclusionPolicy("none")
* #ORM\Table()
* #ORM\Entity(repositoryClass="My\WebBundle\Entity\HighlightRepository")
*/
class Highlight {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="abbreviation", type="string", length=8, unique=true)
*/
private $abbreviation;
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=80, nullable=true)
*/
private $description;
/**
* #var string
*
* #ORM\Column(name="color", type="string", length=7)
*/
private $color;
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="Goods", mappedBy="highlight")
*/
private $goods;
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="Calibration", mappedBy="highlight")
*/
private $calibrations;
/**
* Constructor
*/
public function __construct() {
$this->goods = new \Doctrine\Common\Collections\ArrayCollection();
$this->calibrations = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId() {
return $this->id;
}
/**
* Set abbreviation
*
* #param string $abbreviation
* #return Highlight
*/
public function setAbbreviation($abbreviation) {
$this->abbreviation = $abbreviation;
return $this;
}
/**
* Get abbreviation
*
* #return string
*/
public function getAbbreviation() {
return $this->abbreviation;
}
/**
* Set description
*
* #param string $description
* #return Highlight
*/
public function setDescription($description) {
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription() {
return $this->description;
}
/**
* Set color
*
* #param string $color
* #return Highlight
*/
public function setColor($color) {
$this->color = $color;
return $this;
}
/**
* Get color
*
* #return string
*/
public function getColor() {
return $this->color;
}
/**
* Add goods
*
* #param \My\WebBundle\Entity\Goods $goods
* #return Highlight
*/
public function addGood(\My\WebBundle\Entity\Goods $goods) {
$this->goods[] = $goods;
return $this;
}
/**
* Remove goods
*
* #param \My\WebBundle\Entity\Goods $goods
*/
public function removeGood(\My\WebBundle\Entity\Goods $goods) {
$this->goods->removeElement($goods);
}
/**
* Get goods
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getGoods() {
return $this->goods;
}
/**
* Add calibrations
*
* #param \My\WebBundle\Entity\Calibration $calibrations
* #return Highlight
*/
public function addCalibration(\My\WebBundle\Entity\Calibration $calibrations) {
$this->calibrations[] = $calibrations;
return $this;
}
/**
* Remove calibrations
*
* #param \My\WebBundle\Entity\Calibration $calibrations
*/
public function removeCalibration(\My\WebBundle\Entity\Calibration $calibrations) {
$this->calibrations->removeElement($calibrations);
}
/**
* Get calibrations
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getCalibrations() {
return $this->calibrations;
}
}
Highlight repository is empty
I think the problem comes from the serializer. Since you serializer highliths, each of them has their properties serialized as well which means that lazy query will be performed to retieved Goods which will be also serialized.
You should then prevent this behaviour by adding annotations to highlight's goods property as this
use ...
use JMS\SerializerBundle\Annotation\ExclusionPolicy;
use JMS\SerializerBundle\Annotation\Exclude;
/**
* ...
* #ExclusionPolicy("none")
*/
class Highlight
{
/**
* ...
* #Exclude
*/
private $goods;
}
You can have further details about exclusion stratigies from JMSSerializer doc
findAll itself does not perform many queries. Queries are executed when you access related entity via getters. As relation are not fetched eagerly, they first time are fetched when you are acessing them.
I think serializer access all children to send your object.
See Doctrine documentation
Whenever you have a managed entity instance at hand, you can traverse
and use any associations of that entity that are configured LAZY as if
they were in-memory already. Doctrine will automatically load the
associated objects on demand through the concept of lazy-loading.
To prevent this either disable children serialization or use fetch EAGER or build a DQL query, which prefetch all the children alongside with parents, like (just sample, not valid DQL)
SELECT Highlight, Good, Calibration
FROM Highlights Highlight
LEFT JOIN Highlight.googs Good
LEFT JOIN Goog.calibrations Calibration
WHERE ...
Hi i have that same question as here: Many-to-many self relation with extra fields? but i cant find an answer :/ I tried first ManyToOne and at the other site OneToMany ... but then i could not use something like
public function hasFriend(User $user)
{
return $this->myFriends->contains($user);
}
because there was some this problem:
This function is called, taking a User type $user variable and you then use the contains() function on $this->myFriends.
$this->myFriends is an ArrayCollection of Requests (so different type than User) and from the doctrine documentation about contains():
The comparison of two elements is strict, that means not only the value but also the type must match.
So what is the best way to solve this ManyToMany relationship with extra fields? Or if i would go back and set the onetomany and manytoone relationship how can i modify the hasFriend method? To example check if ID is in array collection of ID's.
EDIT: i have this table... and what i need is:
1. select my friends... and my followers ...check if i am friend with him or not. (because he can be friend with me and i dont have to be with him... like on twitter). I could make manytomany but i need extra fields like: "viewed" "time when he subscribe me" as you can see at my table.
And make query like this and then be able in twig check if (app.user.hasFriend(follower) or something like that)
$qb = $this->createQueryBuilder('r')
->select('u')
->innerJoin('UserBundle:User', 'u')
->Where('r.friend_id=:id')
->setParameter('id', $id)
->orderBy('r.time', 'DESC')
->setMaxResults(50);
return $qb->getQuery()
->getResult();
I was trying to have a many to many relationship with extra fields, and couldn't make it work either... The thing I read in a forum (can't remember where) was:
If you add data to a relationship, then it's not a relationship anymore. It's a new entity.
And it's the right thing to do. Create a new entity with the new fields, and if you need it, create a custom repository to add the methods you need.
A <--- Many to many with field ---> B
would become
A --One to many--> C (with new fields) <-- One to many--B
and of course, C has ManyToOne relationships with both A and B.
I searched everywhere on how to do this, but in the end, it's the right thing to do, if you add data, it's no longer a relationship.
You can also copy what contains usually do, or try to overwrite it in a custom repository, to do whatever you need it to do.
I hope this helps.
I'm adding another answer since it has nothing to do with my original answer. Using the new info you posted, I'm calling the table/entity you posted "Follower". The original entity, "User".
What happens if you create the following associations:
namespace Acme\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Acme\UserBundle\Entity\User
*
* #ORM\Table()
* #ORM\Entity
*/
class User
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Acme\FollowerBundle\Entity\Follower", mappedBy="followeduser")
*/
protected $followers;
/**
* #ORM\OneToMany(targetEntity="Acme\FollowerBundle\Entity\Follower", mappedBy="followeeuser")
*/
protected $followees;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
public function __construct()
{
$this->followers = new \Doctrine\Common\Collections\ArrayCollection();
$this->followees = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add followers
*
* #param Acme\FollowerBundle\Entity\Follower $follower
*/
public function addFollower(\Acme\FollowerBundle\Entity\Follower $follower)
{
$this->followers[] = $follower;
}
/**
* Add followees
*
* #param Acme\FollowerBundle\Entity\Follower $followee
*/
public function addFollowee(\Acme\FollowerBundle\Entity\Follower $followee)
{
$this->followees[] = $followee;
}
/**
* Get followers
*
* #return Doctrine\Common\Collections\Collection
*/
public function getFollowers()
{
return $this->followers;
}
/**
* Get followees
*
* #return Doctrine\Common\Collections\Collection
*/
public function getFollowees()
{
return $this->followees;
}
}
namespace Acme\FollowerBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Acme\FollowerBundle\Entity\Follower
*
* #ORM\Table()
* #ORM\Entity
*/
class Follower
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\User", inversedBy="followers")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $followeduser;
/**
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\User", inversedBy="followees")
* #ORM\JoinColumn(name="followee_id", referencedColumnName="id")
*/
protected $followeeuser;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set followeduser
*
* #param Acme\UserBundle\Entity\User $followeduser
*/
public function setFolloweduser(\Acme\UserBundle\Entity\User $followeduser)
{
$this->followeduser = $followeduser;
}
/**
* Get followeduser
*
* #return Acme\UserBundle\Entity\User
*/
public function getFolloweduser()
{
return $this->followeduser;
}
/**
* Set followeeuser
*
* #param Acme\UserBundle\Entity\User $followeeuser
*/
public function setFolloweeuser(\Acme\UserBundle\Entity\User $followeeuser)
{
$this->followeeuser = $followeeuser;
}
/**
* Get followeeuser
*
* #return Acme\UserBundle\Entity\User
*/
public function getFolloweeuser()
{
return $this->followeeuser;
}
}
I'm not sure if this would do the trick, I really don't have much time to test it, but if it doesn't, I thnk that it's on it's way. I'm using two relations, because you don't need a many to many. You need to reference that a user can have a lot of followers, and a follower can follow a lot of users, but since the "user" table is the same one, I did two relations, they have nothing to do with eachother, they just reference the same entity but for different things.
Try that and experiment what happens. You should be able to do things like:
$user->getFollowers();
$follower->getFollowedUser();
and you could then check if a user is being followed by a follower whose user_id equals $userThatIwantToCheck
and you could search in Followers for a Follower whose user = $user and followeduser=$possibleFriend
I'm trying to perform a ManyToMany self referencing association in my Symfony 2.1 project by following the Doctrine docs: http://docs.doctrine-project.org/en/latest/reference/association-mapping.html#many-to-many-self-referencing
My use-case is that I'm working on a CMS and I'm adding the ability to have related items of content. For example: I could have a sidebar on a website which would say that this piece of content X is related to Y and Z. Similarly on pages where content Y appears it says that it is related to content item X.
In my tests using this to add a new relation between content items fails because it reaches PHP's maximum nesting level of 100 because it is running toArray() on the current content item and then again on the related content item and so on and so on.
I've seen many similar questions on SO about Many-to-Many Self referential Doctrine associations but none with enough complete code to be able to see how others have managed this. Can anybody help?
My Content entity:
/**
* #ORM\MappedSuperclass
* #ORM\Table(name="content")
* #ORM\Entity(repositoryClass="CMS\Bundle\Common\ContentBundle\Entity\ContentRepository")
* #ORM\InheritanceType("JOINED")
*/
abstract class content implements ContentInterface
{
/**
* #var int $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $title
*
* #ORM\Column(name="title", type="string", length=255)
* #Assert\NotBlank()
*/
private $title;
// Other class properties
/**
* #var array
*
* #ORM\ManyToMany(targetEntity="Content", cascade={"persist"})
* #ORM\JoinTable(name="content_relation",
* joinColumns={#ORM\JoinColumn(name="relation_id", referencedColumnName="id")},
* inverseJoinColumns={
* #ORM\JoinColumn(name="related_content_id", referencedColumnName="id")
* })
**/
private $related;
public function __construct()
{
$this->related = new ArrayCollection();
}
// Other getters & setters for class properties
/**
* #return array
*/
public function getRelated()
{
return $this->related;
}
/**
* #param Content $relation
*/
public function addRelation(Content $relation)
{
$this->related->add($relation);
$this->related->add($this);
}
/**
* #return array
*/
public function toArray()
{
$related = array();
foreach($this->getRelated() as $relatedItem) {
$related[] = $relatedItem->toArray();
}
return array(
'type' => static::getType(),
'id' => $this->id,
'title' => $this->title,
....
'related' => $related
);
}
In my RelationsController for managing the related content data I use it like this:
/**
* Creates a new relation to a content item
*
* #Route("{_locale}/content/{id}/related", name="relation_add")
* #Method("POST")
*/
public function addAction(Request $request, $id)
{
// Validation and error checking
// $entity is loaded by the repository manager doing a find on the passed $id
$entity->addRelation($relation);
$em = $this->getEntityManager();
$em->persist($entity);
$em->persist($relation);
$em->flush();
$response = $relation->toArray();
return new JsonResponse($response, 201);
}
The fix for this was to use the JMSSerializerBundle to encode the entity to JSON instead of using a toArray method and change the addRelation function to:
/**
* #param Content $relation
*/
public function addRelation(Content $relation)
{
$this->related[] = $relation;
if (! $relation->getRelated()->contains($this)) {
$relation->addRelation($this);
}
}