class DistCache
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\PlaceInfo")
* #ORM\JoinColumn(nullable=false)
*/
private $placeOne;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\PlaceInfo")
* #ORM\JoinColumn(nullable=false)
*/
private $placeTwo;
This table has two members, both related to PlaceInfo class.
And PlaceInfo class doesn't have any member related to DistCache class.
Then I want to delete the DistCache entry when one of two members(placeOne or placeTwo) is deleted
I googled around and found cascade="remove" or orphanRemoval=true, but both looks bit different my purpose.
How can I solve?
I can see that for both PlaceInfo object you set nullable=false , so when deleting a PlaceInfo, not only have to delete the DistCache entities managed by entityManager, you have to delete the ones in the database too.
I suggest you can use preRemove event from Doctrine life cycle callbacks.
On the remove event of a PlaceInfo record, you query all the DistCache objects which use the deleted PlaceInfo object and remove them first.
In short, you need to :
Add #ORM\HasLifecycleCallbacks before your class to enable life cycles.
Add preRemove function in PlaceInfo class :
/**
* #ORM\PreRemove
* #param LifecycleEventArgs $event
*/
public function removeDistCache(LifecycleEventArgs $event)
{
$em = $event->getEntityManager();
// Use $em to query and delete the DistCache entities
}
Related
So I have a following single inheritance table defined:
/**
* #Entity
* #Table(name="listKeys")
* #InheritanceType("SINGLE_TABLE");
* #DiscriminatorColumn(name="parent", type="string")
* #DiscriminatorMap({"Type1" = "Something1", "Type2" = "Something2"})
*/
abstract class ListKey
{
/**
* #OneToMany(targetEntity="KeyListValue", mappedBy="listKey", cascade={"persist", "remove"}, orphanRemoval=true)
*/
private Collection $listValues;
/**
* #ManyToOne(targetEntity="KeyListValue")
* #JoinColumn(name="defaultKeyListValueId")
*/
private ?KeyListValue $defaultKeyListValue = null;
}
With the list options defined as:
/**
* #Entity
* #Table(name="keyListValues")
*/
class KeyListValue
{
/**
* #Column(type="string")
*/
private $label;
/**
* #ManyToOne(targetEntity="ListKey", inversedBy="listValues", cascade={"persist"})
* #JoinColumn(name="caKeyId")
*/
private ListKey $listKey;
}
Before adding list values to a key, persisting and flushing worked ok, so the key would get inserted first after the commit order calculation. But once I added a new column default key list value, the commit order changes, and key list values try to get inserted first so the transaction fails.
Workaround is that I use flush() after creating new key and the adding the list values but this is problematic since ListKey also has association with some other entities which are not persisted yet at the time of flush so cascades would have to be declared and I don't want that. Any suggestion on how I can redefine this relationship better or a better workaround so the commit order would first insert keys?
I have an User entity with a many-to-many entity (Wish) linked to it, I now need the top 10 wishes now using the Symfony QueryBuilder.
The problem here is that i do not have direct access to the ManyToMany Table/Entity/Repository as its automatically created and managed by Doctrine ORM.
Making a manual OneToMany, ManyToOne Entity is not really an option as it will break existing code (mostly the automatic collection population and the add / remove functions of it)
Doing a manual SQL is kinda dodgy since the table name is generated and might change some update (even if not I would prefer to keep it clean)
some code:
class User {
// .........
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Wish")
*/
private $wish;
public function __construct()
{
$this->wish = new ArrayCollection(); // with get/set/add/remove etc
}
}
class Wish {
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
// ... get set etc
}
I have an Entity Employee
class Employee
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="WorkHour", mappedBy="employee", cascade={"persist", "remove"})
*/
private $workHours;
}
and WorkHour
class WorkHour
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var Profile
*
* #ORM\ManyToOne(targetEntity="Employee", inversedBy="workHours")
* #ORM\JoinColumn(name="employee_id", referencedColumnName="id")
*/
protected $profile;
/**
* #var integer
*
* #ORM\Column(name="weekday", type="smallint")
*/
private $weekday;
/**
* #var \DateTime
*
* #ORM\Column(name="hour_from", type="time")
*/
private $hourFrom;
/**
* #var \DateTime
*
* #ORM\Column(name="hour_to", type="time")
*/
private $hourTo;
}
Now I'm confused when I'm going to add addWorkHour(), removeWorkHour() methods.
Usually one-to-many relation you can add as many relations as you want, but in my case, one employee can have only up-to-7 workHours, and for a specified weekday (from 0 to 6) can have only one (or no) record.
So I think what I need is something methods like,
public function setWorkHourByWeekday($hour_from, $hour_to, $weekday);
public function getWorkHourByWeekday($weekday);
And after set workhours for an employee, when you persist that employee,
I want doctrine delete those workhours that are no longer exist, update those workhours that are changed, and create new workhours that not exist before.
How can I implement this? Should I write these logic in class Employee or its Repository, or a WorkHourManager class?
I think WorkDay is a probably better name for your entity, so i'll use that :).
$workdays= $employee->getWorkDays();
$workdays->clear(); // Clear existing workdays
// Add new workdays
foreach(...) {
$workday = new WorkDay();
$workday ->setWeekday(0); // You could use a constant like `WorkDay::MONDAY`
$workday ->setStart('09:00');
$workday ->setEnd('17:00');
$workdays->add($workday);
}
Set orphanRemoval=true on $workHours to remove WorkHours without an Employee.
The setWeekday method in your Workday entity should throw an exception when an invalid weekday is supplied (other than 0-6). You could also use a Weekday value object in combination with Doctrine embeddables.
I'd go for a service or manager class in this case.
My advice is not to drop old workhours, maybe you don't needed now, but this data could be useful in the future. So, will be better just add workHours to the Employee and make a report the get the last workHours for today. About validations, there is a lot of ways of doing that. If you are working with forms and the rules are complex maybe you need read http://symfony.com/doc/current/cookbook/validation/custom_constraint.html , but maybe you can find alternatives in the action controller or the entity class itself.
I am trying to create a many-to-many foreign key table relation targeting the same entity.
I have successfully created relations with other entities but I'm having trouble when targeting the same entity.
I read a few stack overflow questions and answers and saw what I was doing was possible..
I based my class of this example - the table unit_relations is empty when i add a parent, but works when I add a child to a unit. I'm assuming because the field has the inversedBy annotation.
What do I need to add to allow bi-drectional updating/inserting?
I tried swapping the joinColums like this answer - nothing...
--
/**
* Unit
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Acme\DemoBundle\Entity\UnitRepository")
*/
class Unit
{
....
/**
* #ORM\ManyToMany(targetEntity="Unit", inversedBy="parents")
* #ORM\JoinTable(name="unit_relations",
* joinColumns={#ORM\JoinColumn(name="unit_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="child_unit_id", referencedColumnName="id")}
* )
*/
private $children;
/**
* #ORM\ManyToMany(targetEntity="Unit", mappedBy="children")
*/
private $parents;
}
is adding inversedBy instead of having mappedBy, with the join columns also swapped, the proper way to do it?
Can someone explain this?
/**
* #ORM\ManyToMany(targetEntity="Unit", inversedBy="parents")
* #ORM\JoinTable(name="unit_relations",
* joinColumns={#ORM\JoinColumn(name="unit_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="child_unit_id", referencedColumnName="id")}
* )
*/
private $children;
/**
* #ORM\ManyToMany(targetEntity="Unit", inversedBy="children")
* #ORM\JoinTable(name="unit_relations",
* joinColumns={#ORM\JoinColumn(name="child_unit_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="unit_id", referencedColumnName="id")}
* )
*/
private $parents;
EDIT
as requested here's the code showing how I create the relation. I just realized it may because the entity doesn't have an id yet since I'm adding the relation on its creation...
$unit = new \Acme\DemoBundle\Entity\Unit();
/* #var $parentUnit \Acme\DemoBundle\Entity\Unit */
$parentUnit = $this->getDoctrine()->getRepository('AcmeDemoBundle:Unit')->findById($request->get('unitId'));
$unit->addParent($parentUnit);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($unit);
$entityManager->flush();
It doesn't matter, that there is no ID, when you add a parent relations.
I cant in detail explain why this happens, but i think the main problem is, that in Many-To-Many Self-Referencing the attribute with JoinTable annotation is potentially the Master-Entity. You can say, that it "holds" all other relations to this Entity.
You can recieve the bi-directional updating/inserting while changing the function $unit->addParent($parent). Change it as follows:
public function addParent($parent)
{
$this->parents[] = $parent;
$parent->addChild($this); // Add the relation in the proper way
}
That should work fine!
Regards!
I have a recursive entity containing parent/children item
namespace Vendor\StructureBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Events;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* Vendor\StructureBundle\Entity\Structure
* #Gedmo\Tree(type="nested")
*
* #ORM\Table(name="lowbi_structure")
* #ORM\Entity(repositoryClass="Gedmo\Tree\Entity\Repository\NestedTreeRepository")
* #ORM\HasLifecycleCallbacks()
* #CustomAssert\ganttDate
*/
class Structure {
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
...
/**
* #ORM\Column(name="title", type="string", length=64)
*/
private $title;
/**
* #Gedmo\TreeParent
* #ORM\ManyToOne(targetEntity="Structure", inversedBy="children",cascade={"persist"})
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true, onDelete="SET NULL")
*/
private $parent;
/*
* #ORM\OneToMany(targetEntity="Structure", mappedBy="parent",cascade={"persist","remove"})
* #ORM\OrderBy({"lft" = "ASC"})
*/
private $children;
...
}
and i want to update the "parent" when i persit this entity.
/**
* Set prePersist
*
* #ORM\PrePersist()
* #ORM\PreUpdate()
*
*/
public function prePersist()
{
$this->getParent()->setTitle('Foo');
}
The probleme is that my current entity is persisted but the parent entity is not. The title is not saved. How can i save the parent's properties?
PS : I simplified the code. In real world, i need to update parent start dates/end dates to fit the children (project management tree)
What you're trying to do here is not possible with a Lifecycle callback (i.e. #PrePersist) if you save only the child afterwards.
Doctrine only tracks and saves changes to the owning side of a relation.
When a bidirectional assocation is updated, Doctrine only checks on
one of both sides for these changes. This is called the owning side of
the association.
Therefore you can't persist an update of the parent from the child by persisting the child only.
You can find more information about the concepts of the inverse and owning side in this answer and in the documentation chapter Working with Associations.
To solve this issue ... you can either persist the parent instead of the child ... or (instead of using the lifecycle callback #PrePersist) create an event listener that will automatically persist the parent. This is a better practice in general because it keeps application logic out of your model.
The documentation chapter How to Register Event Listeners and Subscribers provides all the necessary information to get you started.
I don't know why, but if i return $this in the prePersist function, IT WORKS.
**
* Set prePersist
*
* #ORM\PrePersist()
* #ORM\PreUpdate()
*
*/
public function prePersist()
{
//$this->getParent()->setTitle('Foo');
return $this->getParent()->setTitle('Foo'); //solved this problem!!!!
}