I have a "many-to-many" relation with an association table. The function calculDesCouts(), from the parent table is calculating the total cost of ingredients, and persist this info in a field. Actually, because of the owning and inversed sides of the relations, I'm not able to cascade persist the parent table when an ingredient price has changed.
What is the proper way to do this? I mean, when a child row is updated, how to trigger the persist callback from the parent table? I know I can do it in my controller, with a loop on all recipes using this ingredient, but I would really like to do it on the ORM layer...
This is my parent entity, with a lifecycle callback on the calculDesCouts :
<?php
/**
* #ORM\Table(name="recette")
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class Recette
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="\My\AcmeBundle\Entity\RecetteIngredientAssociation", mappedBy="recette", cascade={"persist"})
*/
protected $recette_ingredient_associations;
/**
* #ORM\Column(type="decimal", scale=2, nullable=true)
*/
private $cout_nourriture;
/**
* #ORM\PrePersist
* #ORM\PreUpdate
*/
public function calculDesCouts()
{
$this->cout_nourriture = 0;
foreach ($this->recette_ingredient_associations as $ria) {
$this->cout_nourriture += $ria->getIngredient()->getPrix() * $ria->getQuantite();
}
}
}
This is my association entity:
<?php
/**
* #ORM\Table(name="recette_ingredient_association")
* #ORM\Entity
*/
class RecetteIngredientAssociation
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="\My\AcmeBundle\Entity\Recette", inversedBy="recette_ingredient_association")
* #ORM\JoinColumn(name="recette_id", referencedColumnName="id")
*
*/
private $recette;
/**
* #ORM\ManyToOne(targetEntity="\My\AcmeBundle\Entity\Ingredient", inversedBy="recette_ingredient_association")
* #ORM\JoinColumn(name="ingredient_id", referencedColumnName="id")
*/
private $ingredient;
/**
* #ORM\Column(type="decimal", scale=2, nullable=false)
*/
private $quantite;
}
And this is my child entity:
<?php
/**
* #ORM\Table(name="ingredient")
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class Ingredient
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="decimal", scale=2, nullable=true)
*/
private $prix;
/**
* #ORM\OneToMany(targetEntity="\My\AcmeBundle\Entity\RecetteIngredientAssociation", mappedBy="ingredient")
*/
protected $recette_ingredient_associations;
}
I don't know how to make it on ORM level, but I would do it on Symfony2 layer.
To make this what you need is to create an EventListener which will in case of Ingredient's update call Recette's calculDesCouts.
Like declare a service:
app.ingredient_listener_update:
class: AppBundle\Listener\IngredientPriceListener
tags:
- { name: doctrine.event_listener, event: postUpdate }
and in this AppBundle\Listener\IngredientPriceListener make a method
public function postUpdate(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$em = $args->getEntityManager();
if ($entity instanceof Ingredient) {
foreach ($entity->get_recette_ingredient_associations() as $association)
foreach ($association->get_recettes() as $recette) {
$recette->calculDesCouts();
$em->persist($recette);
}
}
}
Related
I want to insert into my joint-table RoleUser idUser and idRole ,but in the function I should add an object user and role
How can I do that?
the joint table RoleUser :
/**
* RoleUser
*
* #ORM\Table(name="role_user", indexes={#ORM\Index(name="fk_role_user_id", columns={"ref_user_id"}), #ORM\Index(name="fk_role_id", columns={"ref_role_id"})})
* #ORM\Entity(repositoryClass="AppBundle\Repository\RoleUserRepository")
*/
class RoleUser
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var \AppBundle\Entity\Role
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Role")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="ref_role_id", referencedColumnName="id")
* })
*/
private $refRole;
/**
* #var \AppBundle\Entity\User
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="ref_user_id", referencedColumnName="id")
* })
*/
private $refUser;
/**
* #param Role $refRole
*/
public function setRefRole(\AppBundle\Entity\Role $refRole)
{
$this->refRole = $refRole;
}
/**
* #param User $refUser
*/
public function setRefUser(\AppBundle\Entity\User $refUser)
{
$this->refUser= $refUser;
}
}
In my controller I want to insert the following (for a particular case I should insert in the background, the user can't choose his role):
$user = new User();
$role= new Role();
$roleUser =new RoleUser();
$roleUser->setRefUser($user->getId());
$roleUser->setRefRole(1);
but I konw that I should pass a user and a role :
$roleUser->setRefUser($user);
$roleUser->setRefRole($role);
You need to use a relation ManyToMany instead of OneToMany and remove your Entity RoleUser. you can follow the next example in the documentation official, to mapping this kind of relations.
For the save:
In this case you need to add in the two entities this relation:
<?php
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Location
*
* #ORM\Table(name="location")
* #ORM\Entity
*/
class Location
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* <p>Represent the </p>
* #ORM\ManyToMany(targetEntity="LogoLocation", mappedBy="locations")
*/
private $logoLocationCurse;
/**
* Location constructor.
*/
public function __construct()
{
$this->logoLocationCurse = new ArrayCollection();
}
public function addlogoLocationCurse(LogoLocation $location)
{
$this->logoLocationCurse->add($location);
$location->addLocations($this);
}
public function removeLogo(LogoLocation $location)
{
$this->logoLocationCurse->removeElement($location);
$location->removeLocation($this);
}
}
<?php
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use \DateTime;
/**
* Class LogoLocation
* #ORM\Table(name="logo_location_curso")
* #ORM\Entity
*/
class LogoLocation
{
/**
* #var integer
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* Many Users have Many Groups.
* #ORM\ManyToMany(targetEntity="Location", inversedBy="logocurso")
* #ORM\JoinTable(name="logo_locations")
*/
private $locations;
/**
* LogoLocation constructor.
*/
public function __construct()
{
$this->locations = new ArrayCollection();
}
/**
* #param Location $location
*/
public function addLocations(Location $location)
{
$this->locations->add($location);
}
/**
* #param Location $location
*/
public function removeLocation(Location $location)
{
$this->locations->removeElement($location);
}
/**
*
*/
public function removeLocations()
{
/** #var Location $location */
foreach ($this->locations as $location) {
$location->removeLogo($this);
}
}
}
and make the flush
Im tying to add communication parts to a rootCommunication in my data-fixture, there is no error, but only just NULL in the database field 'root_communication_id'. Why?
Parts of my Model 'Communication'
/**
* Communication
*
* #ORM\Table(name="communication")
* #ORM\Entity(repositoryClass="Mother\BaseBundle\Entity\Repository\CommunicationRepository")
*/
class Communication
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="message", type="text", nullable=true)
*/
private $message;
/**
* #ORM\ManyToOne(targetEntity="Communication", inversedBy="childrenCommunication", cascade={"persist"})
* #ORM\JoinColumn(name="root_communication_id", referencedColumnName="id", nullable=true)
*
*/
private $rootCommunication;
/**
* #ORM\OneToMany(targetEntity="Communication", mappedBy="rootCommunication")
*
*/
private $childrenCommunication;
}
In a first data-fixture i added three communications to the database, in this secound fixture i add the childrenCommunication to the rootCommunication.
/**
* {#inheritDoc}
*/
public function load( ObjectManager $manager ){
$contentRepo = $this->container->get('doctrine')->getManager()->getRepository('MotherBaseBundle:Communication');
$communication1 = $contentRepo->find( $this->getReference('communication1')->getId() );
$communication1->addChildrenCommunication( $this->getReference('communication2') );
$communication1->addChildrenCommunication( $this->getReference('communication3') );
$manager->persist( $communication1 );
$manager->flush();
}
I assume you are not setting the rootCommunication when you are adding the child.
You should add an auto setter to the add method, like..
public function addChildrenCommunication(CommunicationInterface $communication)
{
if (!$this->childrenCommunication->contains($communication)) {
$this->childrenCommunication->add($communication);
$communication->setRootCommunication($this);
}
return $this;
}
.. and the same for the remove..
this is my entity:
/**
* #ORM\Table(name="Animal")
* #ORM\HasLifecycleCallbacks
*/
class Animal {
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var localizedcontent $lctitle
*
* #ORM\ManyToOne(targetEntity="localizedcontent",fetch="EAGER", cascade={"persist"})
* #ORM\JoinColumn(name="lcTitle", referencedColumnName="pkId", nullable=false)
*/
private $lctitle;
/**
* #var localizedcontent $lcdescription
*
* #ORM\ManyToOne(targetEntity="localizedcontent",fetch="EAGER", cascade={"persist"})
* #ORM\JoinColumn(name="lcDescription", referencedColumnName="pkId", nullable=false)
*/
private $lcdescription;
/**
* #ORM\PostLoad
*/
public function postLoad(){
$lct = $this->lctitle;
$lcd = $this->lcdescription;
}
This is my dql:
SELECT a,lct FROM Animal JOIN e.lctitle lct WHERE a.id=:id
When i'm starting xdebug, it tells me that lcdescription is a proxy object and lctitle doesn't exists. I don't know why.
I think the postLoad event is too early because the localizedcontent isn't loaded at this moment, right? Is there an other listener for reading the value of lctitle in relation to the Animal Object?
Thanks
Doctrine always returns proxies. These classes inherit from the entity-classes. It might help if you declare your relations protected instead of private.
/**
* #var localizedcontent $lctitle
*
* #ORM\ManyToOne(targetEntity="localizedcontent",fetch="EAGER", cascade={"persist"})
* #ORM\JoinColumn(name="lcTitle", referencedColumnName="pkId", nullable=false)
*/
protected $lctitle;
or you could write a getter and call this one in your post-load function
public function getLctitle() {
return $this->lctitle;
}
public function getLcdescription() {
return $this->lcdescription;
}
/**
* #ORM\PostLoad
*/
public function postLoad(){
$lct = $this->getLctitle();
$lcd = $this->getLcdescription();
}
I have many relations of this type, but I can't see why this one is not working.
I have a Delegation and a Promotion entities:
Delegation
Promotion
/**
* Company\CBundle\Entity\Promotion
*
* #ORM\Entity
* #DoctrineAssert\UniqueEntity("promotionCode")
*/
class Promotion
{
const REGISTER_DAYS = 30;
const REGISTER_DISCOUNT = 100;
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="promotionCode", type="string", unique=true, nullable=true)
*/
protected $promotionCode;
/**
* #ORM\Column(name="name", type="string")
*/
protected $name;
/**
* #ORM\Column(name="description", type="string", nullable=true)
*/
protected $description;
/**
* #ORM\Column(name="days", type="integer")
*/
protected $days;
/**
* #ORM\Column(name="discount", type="float")
*/
protected $discount;
/**
* #ORM\ManyToOne(targetEntity="Delegation", inversedBy="promotions")
* #ORM\JoinColumn(name="delegation_id", referencedColumnName="id")
*/
private $delegation;
/**
* #ORM\ManyToOne(targetEntity="Product", inversedBy="promotions")
*/
private $product;
/**
* #var date $adquiredDate
*
* #ORM\Column(name="adquiredDate", type="date", nullable=true)
*/
private $adquiredDate;
When in a controller I create a promotion, the table Promotion has the new object related to the delegation one
private function createPromotion($delegation)
{
$em = $this->getDoctrine()->getEntityManager();
$promotion = Promotion::createPromotion($delegacion, Promotion::REGISTER_DAYS, Promotion::REGISTER_DISCOUNT);
$em->persist($promotion);
$em->persist($delegation);
$em->flush();
}
Database
*************************** 15. row ***************************
id: 32
delegation_id: 19
days: 20
discount: 50
adquiredDate: 2013-01-10
*************************** 16. row ***************************
id: 33
delegation_id: 19
days: 25
discount: 50
adquiredDate: 2013-01-10
*************************** 17. row ***************************
id: 34
delegation_id: 19
days: 30
discount: 50
adquiredDate: 2013-01-10
But when I call the $delegation->getPromotions() in another controller/action there is no promotions, returns a Doctrine\ORM\PersistentCollection with no data.
Can anyone help, please?
Edit with more information.
$delegation->getPromotions() is empty, but looking for a promotion of that delegation and calling $promotion->getDelegation() is returning the delegation correctly :?
Have you tried defining your $delegation property like
/**
* #ORM\ManyToOne(targetEntity="Delegation", inversedBy="promotions")
* #ORM\JoinColumn(name="delegation_id", referencedColumnName="id")
*/
private $delegation;
See Doctrine2 Docs: Association Mapping->Many-To-One
Also there are a lot of typos in your code. For example
/**
* #ORM\OneToMany(targetEntity="Promotion", mappedBy="delegacion", cascade={"all"}, orphanRemoval=true)
*/
protected $promotions;
mappedBy="delegacion" should be mappedBy="delegation".
Or
public function getDeleTacion()
{
return $this->deleTacion;
}
Should be
public function getDelegation()
{
return $this->delegation;
}
Edit
Okay, I created a minimalistic version for you that worked for me. You can built it up from there or watch for differences with your code:
Promotion.php
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Promotion
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Delegation", inversedBy="promotions", cascade={"persist"})
* #ORM\JoinColumn(name="delegation_id", referencedColumnName="id")
*/
public $delegation;
/**
* #ORM\ManyToOne(targetEntity="Product", inversedBy="promotions", cascade={"persist"})
* #ORM\JoinColumn(name="product_id", referencedColumnName="id")
*/
public $product;
}
Delegation.php
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Delegation
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="Promotion", mappedBy="delegation", cascade={"all"}, orphanRemoval=true)
*/
public $promotions;
public function __construct() {
$this->promotions = new \Doctrine\Common\Collections\ArrayCollection();
}
}
Product.php
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Product
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="Promotion", mappedBy="product", cascade={"all"}, orphanRemoval=true)
*/
public $promotions;
public function __construct() {
$this->promotions = new \Doctrine\Common\Collections\ArrayCollection();
}
}
If you now do something like
$delegation = new Delegation();
$product = new Product();
$promotion = new Promotion();
$promotion->delegation = $delegation;
$promotion->product = $product;
$em->persist($promotion);
$em->flush();
$products = $em->createQuery('select p from BundleName\Entity\Product p')->execute();
$delegations = $em->createQuery('select d from BundleName\Entity\Delegation d')->execute();
var_dump(count($products[0]->promotions), count($delegations[0]->promotions));
You should end up with
int(1)
int(1)
So the refrence is in fact saved and can be read. Phew. Good luck with that! :-)
I had a similar error where my many-to-one relationship on some freshly created entities contained entities, and an inverse one-to-many didn't, although database clearly had the corresponding rows in it.
I did persist and flush entities on the one-to-many side, but I had to also do
$entityManager->clear();
before getting those entities from a repository again for the one-to-many relationship to be able to access those entities.
I have this class:
* #ORM\Entity
* #ORM\HasLifecycleCallbacks()
class Parameter{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Project\Bundle\Entity\Anthropometry", inversedBy="parameter")
* #ORM\JoinColumn(name="anthropometry_id", referencedColumnName="id")
*
*/
protected $anthropometry;
/**
* #ORM\Column(name="data", type="string", length=255, nullable=true)
*/
protected $data;
...
}
and this:
/**
* #ORM\Table(name="anthropometry")
* #ORM\Entity
*/
class Anthropometry {
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
*
* #ORM\OneToMany(targetEntity="Project\Bundle\Entity\Parameter", mappedBy="anthropometry", cascade={"persist"})
*
*/
protected $parameter;
...
}
In my Controller I am creating a form and validating in the same Action.
To create the form I need instance one Parameter. But not need persist him.
So.. when I call $em->flush I got the error:
A new entity was found through the relationship ...
To solve this I put cascade={"persist"} in annotation:
//Class Anthropometry
...
/**
*
* #ORM\OneToMany(targetEntity="Project\Bundle\Entity\Parameter", mappedBy="anthropometry", cascade={"persist"})
*
*/
protected $parameter;
But now, in my Database, the parameters are being persisted with field 'Data' = NULL
Can I check with prePersist if the field is NULL before persist?
something like this?
//class Parameter
/**
*
* #ORM\prePersist
*/
public function prePersist(){
if($this->getData() == NULL){
return false;
}
}
Thx!
I didn't verify if it works but you could try unsetting the parameter before persisting the Anthropometry (since you don't need to persist parameters):
//class Anthropometry
/**
* #ORM\prePersist
*/
public function prePersist()
{
if(!is_null($this->parameter) && $this->parameter->getData() == null){
$this->parameter = null;
}
}