I'm trying to figure out a way to cascade an operation on all entities associated with an entity. For example, if I have a User, and a user has entities, and those entities have entities, I want to perform an operation on every entity. Specifically I want to perform validation on every entity in that tree.
How would I implement something like 'getAssociatedEntities()':
class User {
/**
* #ManyToOne(targetEntity="Comment")
*/
private $comment;
/**
* #ManyToOne(targetEntity="Something")
*/
private $something;
}
$user->setComment($comment);
$user->setSomething($something);
$associated_entities = $user->getAssociatedEntities(); // NOT A REAL METHOD
foreach ($associated_entities AS $entity) {
validate($entity);
}
I realize I could use lifecycle callbacks to perform validation. However, setting validation annotations makes things sooooo much easier. I can validate every entity I persist, BUT I can't validate its associated entities.
I can validate every entity I persist, but I can't validate its
associated entities.
Of course you can perform any action to entitied associated with main entity if:
You add cascade={"all"} to #ManyToOne definition (required !).
#HasLifecycleCallbacks for each sub-entity you want action performed and mark methods #PrePersist or #PostPersist or more in documentation.
For example I use this method to cascade deletion of image file in the Image entity, while the any entity that contains Image entity is beeing deleted:
Here I have an livecycle callback:
/**
* An image.
*
* #ORM\Entity
* #ORM\Table(name="images")
* #ORM\HasLifecycleCallbacks
*/
class ImageEntity extends AbstractEntity
{
/**
* #ORM\PreRemove
*/
public function preRemoveDeleteFile()
{
// remoce file
}
}
Here I have an entity that contains Image (where there is a cascade operation defined):
/**
* A competency group name.
*
* #ORM\Entity
* #ORM\Table(name="product_meta_image_cover")
*/
class ProductMetaImageCoverEntity extends AbstractEntity
{
/**
* #var \ModuleModel\Entity\ImageEntity
* #ORM\OneToOne(targetEntity="ModuleModel\Entity\ImageEntity", cascade={"all"}, orphanRemoval=true)
* #ORM\JoinColumn(onDelete="CASCADE")
*/
protected $image;
}
Related
I need some help getting my head around a 3 way mapping.
I have an entity Student and an entity Parent, obviously one parent can have many students and vice versa, but i need additional information between each parent and student that will be different for each one.
Perhaps we have the following data:
Student A - Parent A (no responsibility) , Parent B (has responsibility) - even though one parent holds legal responsibility and the other doesn't, they are both still parents of the same student.
Student B - Parent A (has responsibility), Parent B (has responsibility) - in this case another student has the same parents but this time they both have legal responsibility.
To start basic entities i would have:
class Student
{
// normally would have a ManyToMany here to link parents, but i need the 3rd entity
// to hold whether this student's parent has legal responsibility or not
}
class Parent
{
// normally again would have ManyToMany here to link students to the parent
}
class ParentStudent
{
/**
* #var boolean
* #ORM\Column(type="boolean", options={"default":true})
*/
private $responsibility = true;
// it's this part where i link student to parent and vice versa that's becoming confusing
}
This is typical Many to Many relationship with edge data / relationship data use case.
You Actually have relationship data class with extra meta data about their relationship, thus it becomes an Entity and you access it just like any other Entity class. Think this relation in terms of graph, nodes and edges. Your Student and Parent Entities are nodes while relationship between them is an edge with weight true/false (i.e 0/1).
For RDBMS, you solve it by introducing 3 entities:
Student
Parent
ParentStudent or StudentParent whichever suits and rhymes better with application
You can have as much as data to this relationship and also map it's ID to other relevant data to this association as it is an entity now.
Further You have Following relationships with each other which goes from OneToMany with intermediate entity and ManyToOne mapping inverse side, ultimately connecting both entities as ManyToMany via ParentStudent Entity:
<?php
/*
* #ORM\Entity()
*/
class Student {
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\ParentStudent", mappedBy="student", orphanRemoval=true)
*/
private $parentStudent;
}
/*
* #ORM\Entity()
*/
class Parent {
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\ParentStudent", mappedBy="parent", orphanRemoval=true)
*/
private $parentStudent;
}
/*
* #ORM\Entity()
*/
class ParentStudent {
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Parent", inversedBy="parentStudent")
* #ORM\JoinColumn(nullable=false)
*/
private $parent;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Student", inversedBy="parentStudent")
* #ORM\JoinColumn(nullable=false)
*/
private $student;
/**
* #var boolean
* #ORM\Column(type="boolean", options={"default":true})
*/
private $responsibility = true;
}
I have a handler which uses the same Entity for two different kind of queries:
/**
* #ORM\Entity
* #ORM\Table(name="app_iso50k1.meter", schema="app_iso50k1")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="mettype", type="string")
* #ORM\DiscriminatorMap({"phy" = "PhyMeter"})
*/
abstract class Meter
{
const TYPE_PHYSICAL = 'PHY';
const TYPE_VIRTUAL = 'VIR';
const TYPE_LOGICAL = 'LOG';
/**
* #ORM\Column(type="string")
* #ORM\Id
*/
protected $id;
<methods>
}
/**
* #ORM\Entity
* #ORM\Entity(repositoryClass="PhyMeterRepository")
* #HasLifecycleCallbacks
*/
class PhyMeter extends Meter
{
/**
* #ORM\Column(type="integer", nullable=false)
*/
protected $idInstrum;
/**
* #ORM\Column(type="integer", nullable=false)
*/
protected $idDevice;
/**
* #ORM\Column(type="integer")
*/
protected $instrumType;
...<methods>
}
The first handler's method is performed on a legacy DB table and it would need to map all the fields annotated with #ORM\Column (id, idInstrum, idDevice, instrumType). To do that, I use a primitive query and I map the data by means of a ResultSetMapping
$rsm = new ResultSetMapping;
$rsm->addEntityResult('Belka\Iso50k1Bundle\Entity\PhyMeter', 'mt');
$rsm->addFieldResult('mt', 'id', 'id');
...
and it works like a charm. The problem is on the the other handler's methods which need to persist data on app.meter table: what I really would like to persist are a small part of the properties (i.e. just the id, idInstrum but not instrumType so as not to have that column in my new table's schema.
I was thinking about using StaticPHPDriver but I'm not sure if it is the right way: what I really would like is manually adding/removing some ORM mapping according to my needs (i.e. different handler's functions)
Is that possible? I could remove the mappings (#ORM\column annotation) I don't need to persist, but that way I cannot map the extra properties by using ResultSetMapping, unless I can add it programmatically.
Any hint is more than welcome
I have an entity with a OneToMany relation to itself (note that it uses single table inheritance).
/**
* #ORM\Table()
* #ORM\Entity()
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="discr", type="string", length=30)
**/
abstract class PlatformPost
{
// [...]
/**
* #var PlatformPost
*
* #ORM\ManyToOne(targetEntity="PlatformPost", inversedBy="comments")
*/
private $parent;
/**
* #var PlatformPost[]|Collection
*
* #ORM\OneToMany(targetEntity="PlatformPost", mappedBy="parent", orphanRemoval=true, cascade={"ALL"})
* #Assert\Valid()
*/
private $comments;
// [...]
}
I create this entity from an api and then persist it. If it already exists i use $em->merge($post) to update it.
This only kind of works. The entity I get from that method is exactly like I want it but doctrine does not update the foreign key of the comments. If I query the post again the comment array still contains the old entities which should have been deleted.
A quick solution would be to remove all comments before merging but i'd like a better solution.
When defining a relationship, there is a property on the related model (not a DB column), but I would like to sort by it (in the #OrderBy annotation).
I have a base model that is extended using single table inheritance. The property in question is basically an order property that is specified in each child class, but is not saved to the DB.
(I don't want to add an order column to the DB table, since the ordering depends purely on which child class the discriminator is mapped to. There is already a unique constraint so that each child class can be used no more than once in the relationship.)
Here's a really simplified version of my code...
Base entity:
/**
* #ORM\Entity
* #ORM\Table(name="base")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="class_name", type="string")
*
* #ORM\DiscriminatorMap({
* "Base" = "Models\Base",
* "ChildA" = "Models\ChildB",
* "ChildB" = "Models\ChildA"
* })
**/
class Base
{
/** #ORM\Column(type="string") **/
protected $class_name;
/**
* #ORM\ManyToOne(targetEntity="Related", inversedBy="collection")
**/
protected $related;
// this is just a plain ol' property (not in the DB)
protected $order;
public function getClassName()
{
return $this->class_name;
}
}
Children:
/**
* #ORM\Entity
* #ORM\Table(name="child_a")
**/
class ChildA extends Base
{
$order = 1;
}
/**
* #ORM\Entity
* #ORM\Table(name="child_b")
**/
class ChildB extends Base
{
$order = 2;
}
Related entity:
/**
* #ORM\Entity
* #ORM\Table(name="related")
**/
class Related
{
/**
* #ORM\OneToMany(targetEntity="Base", mappedBy="related")
* #ORM\OrderBy({"order"="ASC"})
**/
protected $collection;
public function getCollection()
{
$em = App::make('Doctrine\ORM\EntityManagerInterface');
// map each Base instance to the appropriate child class
return $this->collection->map(function ($base) use ($em) {
$class_name = $base->getClassName();
return $em->find($class_name, $base->getId());
});
}
}
Is it possible to use the order property for ordering the collection relationship? (Ordering based on class_name using a switch-like construct would also be valid, but I haven't found any way to do that either, and it would be harder to maintain.)
Thanks in advance!
The directive beginning with ORM is very much telling Doctrine you're doing referencing a property that has a relationship with a table field. You can't use ORM directives on fields that don't exist. Doctrine annotations: OrderBy
You would have to implement this in a function, best in the model itself (within your getCollection() function), or if you're using a framework like Symfony place it in a function of the repository class for this entity. You'd have to use PHP sorting functions to do this. SQL/DQL won't work either because the property isn't related to a field in the table.
Is it possible to have One-To-One Relationships in Flow without having to set the attributes twice?
I have two tables that are connected in a One-To-One Relationship, but only one of them should contain an extra column for this Relation.
Doctrine clearly supports this behavior:
http://doctrine-orm.readthedocs.org/en/latest/reference/association-mapping.html#one-to-one-bidirectional
The class that should come with a componenttape column:
/**
* #Flow\Entity
*/
class Component{
/**
* #var \Some\Package\Domain\Model\Component\Tape
* #ORM\OneToOne(cascade={"all"}, inversedBy="component")
*/
protected $componentTape;
…
}
The class that should just be able to find the connection without an extra column:
/**
* #Flow\Entity
*/
class Tape{
/**
* #var \ Some\Package\Domain\Model\Component
* #ORM\OneToOne(mappedBy="componentTape")
*/
protected $component;
}
A doctrine update will create extra columns for both models.
This is what my workarround at the moment looks like:
class Component{
..
/**
* #param \Some\Package\Domain\Model\Component\Tape $componentTape
* #return void
*/
public function setComponentTape($componentTape) {
$this->componentTape = $componentTape;
$this->componentTape->setComponent($this);
}
The workaround will be necessary anyway to keep the relation correct at all times during a request.
But the second DB column shouldn't be necessary. Did you check if doctrine actually fills it? Maybe/Probably just the created migration is wrong and the component column in Tape can be omitted.
Does your workaround stil work for you?
In my case, I have to update the ComponentTape model on the repository by self:
class Component {
/**
* #param \Some\Package\Domain\Model\Component\Tape $componentTape
* #return void
*/
public function setComponentTape($componentTape) {
$this->componentTape = $componentTape;
$this->componentTape->setComponent($this);
$this->componentTapeRepository->update($this->componentTape);
}