Persist another entity in preUpdate LifecycleCallback - php

I have this User entity
/**
* User Entity.
*
* #ORM\Entity
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="Application\Repository\UserRepository")
* #ORM\HasLifecycleCallbacks()
* #ORM\EntityListeners({"Application\Listener\User"})
*/
class User extends Entity implements UserInterface, ProviderInterface
{
...
/**
* #var \Application\Entity\Organization
*
* #ORM\ManyToOne(targetEntity="Application\Entity\Organization")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="organization_id", referencedColumnName="organization_id")
* })
*/
protected $organization;
...
}
Wich uses the listener
namespace Application\Listener;
use Doctrine\ORM\Event\PreUpdateEventArgs;
class User
{
public function preUpdate(\Application\Entity\User $user, PreUpdateEventArgs $eventArgs)
{
if ($eventArgs->hasChangedField('organization')) {
$oldOrganization = $eventArgs->getOldValue('organization');
if ($oldOrganization) {
$oldOrganization->decreaseTotalMembers();
$eventArgs->getEntityManager()->persist($oldOrganization);
}
$newOrganization = $eventArgs->getNewValue('organization');
if ($newOrganization) {
$newOrganization->increaseTotalMembers();
$eventArgs->getEntityManager()->persist($newOrganization);
}
}
}
}
So, as you can see, every single time a user change organization, I would like to decrease the number of members in the old organization and increase in the new organization.
So, in the controller when I create an organization I have:
public function createAction(){
...
$this->getOrganizationService()->hydrateOrganization($organization, $data);
$user = $organization->getAuthor();
if (!$user->hasOrganization()) {
$user->setOrganization($organization);
$this->getUserService()->saveUser($user);
}
...
}
So I expect that when save the user (persist + flush), the increasement in the total members of the organization is flushed as well but it does not occur.
Do you have any idea on why is it happending and how to solve it?

Related

Doctrine 2 persist only save header object why?

I want save purchase order header with purchase order details.This my PurchaseOrder Entity Class=>
namespace AppBundle\Entity;
use AppBundle\Entity\PurchaseInvoiceDetail;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* PurchaseOrder
*
* #ORM\Table(name="purchase_order", indexes={#ORM\Index(name="fk_purchase_order_supplier1_idx", columns={"supplier_id"})})
* #ORM\Entity
*/
class PurchaseOrder
{
/**
* #var PurchaseOrderDetails
*
* #ORM\OneToMany(targetEntity="AppBundle\Entity\PurchaseOrderDetails", mappedBy="purchaseOrder",cascade={"cascade"})
* #JMS\Type("ArrayCollection<FinanceBundle\Entity\AutoAllocation>")
*/
private $purchaseOrderDetails;
public function __construct()
{
$this->purchaseOrderDetails = new ArrayCollection();
}
public function addPurchaseOrderDetail(PurchaseOrderDetails $purchaseOrderDetails)
{
$this->purchaseOrderDetails->add($purchaseOrderDetails);
}
/**
* #return PurchaseOrderDetails
*/
public function getPurchaseOrderDetails()
{
return $this->purchaseOrderDetails;
}
/**
* #param PurchaseOrderDetails $purchaseOrderDetails
*/
public function setPurchaseOrderDetails($purchaseOrderDetails)
{
$this->purchaseOrderDetails = $purchaseOrderDetails;
}
}
and PurchaseOrderDetail class as this =>
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* PurchaseOrderDetails
*
* #ORM\Table(name="purchase_order_details", indexes={#ORM\Index(name="fk_purchase_order_details_purchase_order1_idx", columns={"purchase_order_id"}), #ORM\Index(name="fk_purchase_order_details_invt_item1_idx", columns={"id_item"})})
* #ORM\Entity
*/
class PurchaseOrderDetails
{
/**
* #var \AppBundle\Entity\PurchaseOrder
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\PurchaseOrder",inversedBy="purchaseOrderDetails")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="purchase_order_id", referencedColumnName="id")
* })
*/
private $purchaseOrder;
/**
* Set purchaseOrder
*
* #param \AppBundle\Entity\PurchaseOrder $purchaseOrder
*
* #return PurchaseOrderDetails
*/
public function setPurchaseOrder(\AppBundle\Entity\PurchaseOrder $purchaseOrder = null)
{
$this->purchaseOrder = $purchaseOrder;
return $this;
}
/**
* Get purchaseOrder
*
* #return \AppBundle\Entity\PurchaseOrder
*/
public function getPurchaseOrder()
{
return $this->purchaseOrder;
}
}
my php code in symfony 3.1 as follows=>
$em = $this->getDoctrine()->getManager();
$purchaseOrder = new PurchaseOrder();
$puchaseOrderDetail = new PurchaseOrderDetails();
$puchaseOrderDetail->setPrice(100);
$purchaseOrder->setPurchaseOrderDetails($puchaseOrderDetail);
$puchaseOrderDetail->setPurchaseOrder($purchaseOrder);
$em->persist($purchaseOrder);
$em->flush();
no errors occurred and just only purchase order have persisted and purchase order detail doesn't
You are not persisting the detail object. Either persist it manually with
$em->persist($purchaseOrderDetail);
or fix
cascade={"persist"}
in the #ORM\OneToMany annotation of PurchaseOrder::$purchaseOrderDetails (cascade={"cascade"} is probably a typo).
You need to persist PurchaseOrderDetails as well.
The below code should save both of your entities.
$em = $this->getDoctrine()->getManager();
$purchaseOrder = new PurchaseOrder();
$puchaseOrderDetail = new PurchaseOrderDetails();
$puchaseOrderDetail->setPrice(100);
$purchaseOrder->setPurchaseOrderDetails($puchaseOrderDetail);
$puchaseOrderDetail->setPurchaseOrder($purchaseOrder);
$em->persist($purchaseOrder);
$em->persist($puchaseOrderDetail);
$em->flush();
As #Finwe has mentioned, If your business logic requires, and you don't want to persist separately PurchaseOrderDetails entity while creating a new PurchaseOrder. You might consider configuring cascade_persist. which will persist automatically associated entity.
To do so, add cascade option to your association config :
#ORM\OneToMany(targetEntity="AppBundle\Entity\PurchaseOrderDetails", mappedBy="purchaseOrder",cascade={"persist"})

Attach entity history/changes to the entity as OneToMany association

I've an entity Order, with a property events which should contain a list of all changes made to this entity.
The Order class:
<?php
/**
* #ORM\Entity
*/
class Order
{
// more stuff...
/**
* #ORM\OneToMany(
* targetEntity="OrderEvent",
* mappedBy="order",
* cascade={"persist", "merge"}
* )
*/
protected $events;
// more stuff...
}
The OrderEvent class:
/**
* #ORM\Entity
*/
class OrderEvent
{
// more stuff...
/**
* #ORM\ManyToOne(targetEntity="Order", inversedBy="events")
* #ORM\JoinColumn(nullable=false)
*/
protected $order;
// more stuff...
}
class OrderLifecycle
{
public function preUpdate(Order $order, PreUpdateEventArgs $args)
{
$changes = $args->getEntityChangeSet();
if (!empty($changes)) {
$event = new OrderEvent();
$event->setOrder($order)
->setChanges($changes);
$order->addEvent($event);
return $event;
}
}
}
But according to the doctrine documentation, the preUpdate method should not be used to change associations.
What is the recommended way to do things like this one?
I am using Zend Framework 2, but I think that's not relevant.
I think in this case you could use PostUpdate event. In that case you are sure that the update action was successful and you can do what you want; add the new OrderEvent instance to your $events collection.
EDIT
You are not the first one implementing such thing. Maybe you should check existing examples and see how they deal with this (or even consider using it). For example the Gedmo Loggable solution.
With this extension you can mark entities as loggable with a simple #annotiation:
/**
* #Entity
* #Gedmo\Loggable
*/
class Order
{
// Your class definition
}

Add brands through company, it's possible? How?

I have this two tables (see pics below) mapped as follow:
class Brand
{
...
/**
* #var Company
*
* #ORM\ManyToOne(targetEntity="Company")
* #ORM\JoinColumn(name="companies_id", referencedColumnName="id")
*/
protected $company;
}
class Company
{
...
}
I need to add support for add a new Brand from Company but I have not idea in how to achieve this. This are handled through SonataAdminBundle but I think I need to add something else to entities in order to create brands from company but I am not sure what this would be, can I get some help? I am stucked
1st attempt
After get an answer this is how I modify Company entity:
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
class Company
{
...
/**
* #var Brand
* #ORM\OneToMany(targetEntity="Brand", mappedBy="company", cascade={"persist"})
**/
protected $brands;
public function __construct()
{
$this->brands = new ArrayCollection();
}
...
public function getBrands()
{
return $this->brands;
}
/**
* Add brands
*
* #param Brand $brand
* #return Brands
*/
public function addBrand( Brand $brand)
{
$this->brands[] = $brand;
return $this;
}
/**
* Remove brands
*
* #param Brand $brand
*/
public function removeBrand( Brand $brand)
{
$this->brands->removeElement($brand);
}
}
But I am getting this error:
No entity manager defined for class
Doctrine\Common\Collections\ArrayCollection
Why is that?
You could try setting up your entities like this:
class Brand
{
/**
* #var Company
*
* #ORM\ManyToOne(targetEntity="Company", inversedBy="brands")
* #ORM\JoinColumn(name="companies_id", referencedColumnName="id")
*/
protected $company;
}
class Company
{
/**
* #var ArrayCollection
*
* #OneToMany(targetEntity="Brand", mappedBy="company", cascade={"persist"})
**/
protected $brands;
}
What we're defining here is that new Brands can be created from the Company entity with cascade={"persist"}.
It's recommended you implement addBrand and removeBrand in Company for direct interaction with the ArrayCollection.
A simple example of the final functionality:
$company = $service->getCompany(1); // our company entity
$brand = new Brand();
$brand->set...
...
$company->addBrand($brand);
$entityManager->persist($company);
EDIT
This is just an example, you may choose not to add with keys or even implement a remove function, but this is a starting point:
public function addBrand(Brand $brand)
{
// key needs to be something that can uniquely identify the brand
// e.g. name
$this->getBrands()->set(*key*, $brand);
return $this;
}
public function removeBrand($key)
{
$this->getBrands()->remove($key);
return $this;
}

Doctrine 2 association overwrite

I have a question about Doctrine 2 and the ability (or not?) to extend an association between to classes.
Best explained with an example :)
Let's say I have this model (code is not complete):
/**
* #Entity
*/
class User {
/**
* #ManyToMany(targetEntity="Group")
* #var Group[]
*/
protected $groups;
}
/**
* #Entity
*/
class Group {
/**
* #ManyToMany(targetEntity="Role")
* #var Role[]
*/
protected $roles;
}
/**
* #Entity
*/
class Role {
/**
* #ManyToOne(targetEntity="RoleType")
* #var RoleType
*/
protected $type;
}
/**
* #Entity
*/
class RoleType {
public function setCustomDatas(array $params) {
// do some stuff. Default to nothing
}
}
Now I use this model in some projects. Suddenly, in a new project, I need to have a RoleType slightly different, with some other fields in DB and other methods. Of course, it was totally unexpected.
What I do in the "view-controller-but-not-model" code is using services:
// basic configuration
$services['RoleType'] = function() {
return new RoleType();
};
// and then in the script that create a role
$role_type = $services['RoleType'];
$role_type->setCustomDatas($some_params);
During application initialization, I simply add this line to overwrite the default RoleType
$services['RoleType'] = function() {
return new GreatRoleType();
};
Ok, great! I can customize the RoleType call and then load some custom classes that do custom things.
But... now I have my model. The model says that a Role targets a RoleType. And this is hard-written. Right now, to have my custom changes working, I need to extend the Role class this way:
/**
* #Entity
*/
class GreatRole extends Role {
/**
* Changing the targetEntity to load my custom type for the role
* #ManyToOne(targetEntity="GreatRoleType")
* #var RoleType
*/
protected $type;
}
But then, I need to extend the Group class to target GreatRole instead of Role.
And in the end, I need to extend User to target GreatGroup (which targets GreatRole, which targets GreatRoleType).
Is there a way to avoid this cascade of extends? Or is there a best practice out there that is totally different from what I did?
Do I need to use MappedSuperClasses? The doc isn't very explicit...
Thanks for your help!
--------------------------- EDIT ---------------------------
If I try to fetch all the hierarchy from User, that's when I encounter problems:
$query
->from('User', 'u')
->leftJoin('u.groups', 'g')
->leftJoin('g.roles', 'r')
->leftJoin('r.type', 't');
If I want to have a "r.type" = GreatRoleType, I need to redefine each classes.

Symfony2 : Many-To-Many with a custom link table

I'm working on a form with 3 entities :
order (idorder)
support reference table (idsupport)
link table (idorder, idsupport)
And when i try to select one or more support i got this error:
Catchable Fatal Error: Argument 1 passed to Myapp\MyBundle\Entity\PcastCmdsupports::setIdsupports() must be an instance of Myapp\MyBundle\Entity\PcastSupports, instance of Doctrine\Common\Collections\ArrayCollection given,
called in C:\wamp\www\php\Symfony\vendor\symfony\src\Symfony\Component\Form\Util\PropertyPath.php on line 347 and defined in C:\wamp\www\php\Symfony\src\Myapp\MyBundle\Entity\PcastCmdsupports.php line 62
Since i already created my link table i saw on the web that i can simply create 2 Many-To-One relation in my link table :
/**
* #var PcastSupports
*
* #ORM\ManyToOne(targetEntity="PcastSupports")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="IDSUPPORTS", referencedColumnName="IDSUPPORTS")
* })
*/
private $idsupports;
/**
* #var PcastOrder
*
* #ORM\ManyToOne(targetEntity="PcastOrder")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="IDORDER", referencedColumnName="IDORDER")
* })
*/
private $idorder;
and my setters and getters :
/**
* Set idsupports
*
*/
public function setIdsupports(\Myapp\MyBundle\Entity\PcastSupports $idsupports)
{
$this->idsupports = $idsupports;
}
/**
* Get idsupports
*
*/
public function getIdsupports()
{
return $this->idsupports;
}
/**
* Set idorder
*
*/
public function setIdcommande(\Myapp\MyBundle\Entity\PcastOrder $idorder)
{
$this->idorder = $idorder;
}
/**
* Get idorder
*
*/
public function getIdorder()
{
return $this->idorder;
}
In my order form i can choose one or many supports so i created my form like this:
$form_clips = $this->createFormBuilder($cmdclips)
->add('idorder', new CmdsupportsType)
->getForm();
And finally my supportsType form:
$builder
->add('idsupports', 'entity', array(
'class' => 'MyappMyBundle:PcastSupports',
'property' => 'name',
'expanded' => true,
'multiple' => true,
'query_builder' => function(EntityRepository $er)
{
return $er->createQueryBuilder('pts')
->orderBy('pts.idsupports','ASC');
},
));
I'm not using any arraycollection so i don't understand the issue. And the issue happened during this action:
$form_clips->bindRequest($request);
Thank a lot for your help !
I tried to make it work with the many-to-many relation in a simple case (user, company and a user_company entities) but i got a problem when i try to add a company to a user:
Warning: oci_bind_by_name() [<a href='function.oci-bind-by-name'>function.oci-bind-by-name</a>]: Invalid variable used for bind in C:\wamp\www\php\Promocast\Symfony\vendor\doctrine-dbal\lib\Doctrine\DBAL\Driver\OCI8\OCI8Statement.php line 113
I googling a lot but i didn't find anything on this error... According to stack trace the error is when doctrine try to add the company object :
array('column' => ':param10', 'variable' => object(PcastCompany), 'type' => '1')
My user entity (societe = company):
/**
* #ORM\ManyToMany(targetEntity="PcastSociete", inversedBy="users")
* #ORM\JoinTable(name="PcastLienusersociete",
* joinColumns={#ORM\JoinColumn(name="ImUser_iduser", referencedColumnName="iduser")},
* inverseJoinColumns={#ORM\JoinColumn(name="PcastLienusersociete_idsociete", referencedColumnName="idsociete")}
* )
*/
private $societes;
public function getSocietes()
{
return $this->societes;
}
public function addSociete(\Myapp\MyBundle\Entity\PcastSociete $societe)
{
$this->societes[] = $societe;
}
My company entity:
/**
* #ORM\ManyToMany(targetEntity="ImUser", mappedBy="societes")
*/
private $users;
public function __construct() {
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
}
If anybody have any idea...
Thanks
You should not have an entity representing the link table. If you annotate both your entities correctly, Doctrine will handle the creation of the link table by itself.
Moreover, you do not need any link table to do a Many-to-One relationship in the first place, what you want to do is use the Many-to-Many annotations in both entities.
http://readthedocs.org/docs/doctrine-orm/en/latest/reference/association-mapping.html?highlight=many%20to%20one#many-to-many-bidirectional
Start with the basics. I was curious about something else concerning ManyToMany so I grabbed your entities as a test case. Before diving into forms and such, make sure you can execute a simple test case from the command line such as:
use Zayso\ArbiterBundle\Entity\PcastSociete as Company;
use Zayso\ArbiterBundle\Entity\ImUser as User;
protected function test1()
{
$em = $this->getContainer()->get('doctrine.orm.entity_manager');
$company = new Company();
$em->persist($company);
$user = new User();
$user->addSociete($company);
$em->persist($user);
$em->flush();
}
For entities I used:
namespace Zayso\ArbiterBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
*/
class ImUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer",name="iduser")
* #ORM\GeneratedValue
*/
protected $id;
public function getId() { return $this->id; }
/**
* #ORM\ManyToMany(targetEntity="PcastSociete", inversedBy="users")
* #ORM\JoinTable(name="PcastLienusersociete",
* joinColumns={#ORM\JoinColumn(name="ImUser_iduser", referencedColumnName="iduser")},
* inverseJoinColumns={#ORM\JoinColumn(name="PcastLienusersociete_idsociete", referencedColumnName="idsociete")}
* )
*/
private $societes;
public function getSocietes()
{
return $this->societes;
}
public function addSociete(PcastSociete $societe)
{
$this->societes[] = $societe;
}
public function __construct()
{
$this->societes = new ArrayCollection();
}
}
namespace Zayso\ArbiterBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
*/
class PcastSociete
{
/**
* #ORM\Id
* #ORM\Column(type="integer", name="idsociete")
* #ORM\GeneratedValue
*/
protected $id;
public function getId() { return $this->id; }
/**
* #ORM\ManyToMany(targetEntity="ImUser", mappedBy="societes")
*/
private $users;
public function __construct()
{
$this->users = new ArrayCollection();
}
}
Get the above working then we can move on to the forms problem.

Categories