I have a View entity that represents the primary page record, and then I have an associated entity called ViewVersion which stores multiple versions of the entity as it's changed over time. The View entity sets the current "Published" ViewVersion in the VersionId field. This makes for a simple OneToOne association. But in some contexts I will also want to get all the versions associated with this View entity, e.g. if I want to allow the user to review older versions and revert back. So I will need another mapping which is a OneToMany. The first viewVersion will map to the active "published" version, and the second viewVersions will show all the versions.
Entity Definitions
/**
* #ORM\Entity
* #ORM\Table(name="view")
* #ORM\Entity(repositoryClass="Gutensite\CmsBundle\Entity\View\ViewRepository")
*/
class View extends Entity\Base {
/**
* #ORM\OneToOne(targetEntity="\Gutensite\CmsBundle\Entity\View\ViewVersion", inversedBy="view", cascade={"persist", "remove"}, orphanRemoval=true)
* #ORM\JoinColumn(name="versionId", referencedColumnName="id")
*/
protected $viewVersion;
/**
* #ORM\Column(type="integer", nullable=true)
*/
protected $versionId = NULL;
/**
* #ORM\OneToMany(targetEntity="\Gutensite\CmsBundle\Entity\View\ViewVersion", mappedBy="viewAll", cascade={"persist", "remove"}, orphanRemoval=true)
*/
protected $viewVersions;
}
/**
* #ORM\Entity
* #ORM\Table(name="view_version")
* #ORM\Entity(repositoryClass="Gutensite\CmsBundle\Entity\View\ViewVersionRepository")
*/
class ViewVersion extends Entity\Base {
/**
* #ORM\OneToOne(targetEntity="\Gutensite\CmsBundle\Entity\View\View", mappedBy="viewVersion", cascade={"persist"})
*/
protected $view;
/**
* #ORM\ManyToOne(targetEntity="\Gutensite\CmsBundle\Entity\View\View", inversedBy="viewVersions")
* #ORM\JoinColumn(name="viewId", referencedColumnName="id")
*/
protected $viewAll;
/**
* The primary view entity that this version belongs to.
* #ORM\Column(type="integer", nullable=true)
*/
protected $viewId;
}
This "works" but is it recommended to have two associations with the same entity like this? Or is this a really bad idea?
The ViewVersion entity will reference a single View entity in both cases, but the mapped associations need two separate variables, e.g. View and ViewAll. I'm not exactly sure how the internals work for the association, and how the reference variable with the mapping is used.
Alternatively, I could get rid of the OneToOne association, and just set a ViewRepository function to get the current published version based on the versionId (just like the old mapped entity used to do with the getVersion()). That would work, but is it more internal overhead, because it would make two queries... or will Doctrine be smart enough to optimize this, just like it did with the getVersion().
NOTE:
These other answers are not complete.
References:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/working-with-associations.html
http://doctrine-orm.readthedocs.org/en/2.0.x/reference/association-mapping.html#one-to-many-bidirectional
Typically, I have found the best approach is to solve this in a different way.
One common pattern I have seen before is you use a single table to hold all records, and have an 'active' flag.
If your query to select the active one works like so:
SELECT * FROM table WHERE active = true ORDER BY updated_at DESC LIMIT 1;
Then enabling a new one becomes as simple as:
UPDATE table SET active = 1, updated_at = '<timestamp>' WHERE id = <new id>;
UPDATE table SET active = 0, updated_at = '<timestamp>' WHERE id = <old id>;
Your new page will be active as soon as the first query hits, and your second query will avoid any sort of weirdness as that row will already be no longer active.
If you have other models that depend on a consistent ID to reference, then another route which also maintains some sanity would be to have one table for the active entries (in whole, not in part) and then a second table with additional metadata to track versions.
The latter approach could be nicely handled via Doctrine's inheritance system (http://docs.doctrine-project.org/en/2.0.x/reference/inheritance-mapping.html) which would let you define the base View class, and then for the "ViewRevision" model, extend View and add a "Revised on" type timestamp.
Per the advice from #jmather I've decided this model is "okay", because I need a single View entity that other entities can access (e.g. Routing urls that point to a single View, i.e. "page").
I've changed the OneToOne relationship for View to be unidirectional only, because the ViewVersion already has an association back to the View via the other OneToMany (so it doesn't need two paths back).
This allows me to keep a simple method for $view->getPublished() handy and seems more logical.
/**
* #ORM\Entity
* #ORM\Table(name="view")
*/
class View extends Entity\Base {
/**
* This is a OneToOne Unidirectional association, just so that we can get the
* current published version easily, based on the publishedId.
* #ORM\OneToOne(targetEntity="\Gutensite\CmsBundle\Entity\View\TestVersion")
* #ORM\JoinColumn(name="publishedId", referencedColumnName="id")
*/
protected $published;
/**
* #ORM\Column(type="integer", nullable=true)
*/
protected $publishedId = NULL;
/**
* This is the regular OneToMany Bi-Directional Association, for all the versions.
* #ORM\OneToMany(targetEntity="\Gutensite\CmsBundle\Entity\View\ViewVersion", mappedBy="view", cascade={"persist", "remove"}, orphanRemoval=true)
*/
protected $versions;
}
/**
* #ORM\Entity
* #ORM\Table(name="view_version")
*/
class ViewVersion extends Entity\Base {
/**
* #ORM\ManyToOne(targetEntity="\Gutensite\CmsBundle\Entity\View\View", inversedBy="versions")
* #ORM\JoinColumn(name="viewId", referencedColumnName="id")
*/
protected $view;
/**
* The primary view entity that this version belongs to.
* #ORM\Column(type="integer", nullable=true)
*/
protected $viewId;
}
However, I've discovered that as long as the $view->publishedId is set the view can't be deleted from the database because of foreign key constraints (even though it's uni-directional). So I have to break that foreign key link before removing. I think that's fine. I posted details about that here: Overlapping Entity Association causing Database Foreign Key Constraint Errors when Removing Entity
Related
I have a table that has composite primary key: id + est_date. And it has an entity:
class Parent
{
/**
* #ORM\Id
*/
private $id;
/**
* #ORM\Id
*/
private int $estDate;
...
}
Now I need to create a related table and its entity.
class Child
{
...
/**
* don't know what to write here
*/
private $parentId;
/**
* don't know what to write here
*/
private int $parentEstDate;
...
}
How to discribe relation ManyToOne (many "Child" entities may relate to 1 "Parent")? And the second issue is - "estDate" of the "Parent" may change. How to specify cascade update in "Child"?
Please don't write that doctrine doesn't recomment to use composite keys. I know that.
on the child-entity you would refer to the parent entity the same way as with single columns, essentially. Starting with
annotation version:
/**
* #ORM\ManyToOne(targetEntity=Parent::class)
*/
private ?Parent $parent;
since the child is the owning side, you have to provide join columns, as you have noticed. There is a badly documented annotation JoinColumns that allows to define multiple join columns. (Note for those using the attribute syntax instead: you should be able to have multiple #[JoinColumn(...)], without the JoinColumns-Wrapper)
annotation version:
/**
* #ORM\ManyToOne(targetEntity=Parent::class)
* #ORM\JoinColumns({
* #ORM\JoinColumn("parent_id", referencedColumnName="id"),
* #ORM\JoinColumn("parent_est_date", referencedColumnName="est_date")
* })
*/
private ?Parent $parent;
If you want to add the inverse side as well, you always reference the object property, not the columns when using mappedBy/inversedBy.
Generally with doctrine-orm: Your class/object should not care about columns, only about php stuff, doctrine should handle the rest. The annotations tell doctrine, how this converts to columns. So not every column will get its own property in this case.
I'm developing a custom content management system with Symfony 5 and Doctrine.
I'm trying to implement a relation between the entities Document and Video (actually there are many more, but for simplicity sake let's say are just two) and the User entity.
The relation represent the User who wrote the document or recorded the video. So the relation here is called Author. Each document or video can have one or more author. Each User can have none or more document or video.
I would like to use just a single associative Author associative entity, like this:
entity_id|author_id|entity
Where:
entity_id: is the id of the document or video
author_id: is the user_id who authored the entity
entity: is a constant like document or video to know to which entity the relation refer to
The problem is that I cannot understand how to build this in Doctrine. Was this a classic SingleEntity<-->Author<-->Users relationship I would have build it as a ManyToMany item, but here it's different.
Author would probably contain two ManyToOne relations (one with the User entity and one with either the Document or the Video entity) plus the entity type field, but I really don't know how to code the "DocumentorVideo`" part. I mean:
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity=??????????, inversedBy="authors")
* #ORM\JoinColumn(nullable=false)
*/
private $entity; // Document or Video
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="articles")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* #ORM\Column(type="smallint")
*/
private $entityType;
How should I manage the first field?
Don't know if would be better to store it under two differents attributes. If not and mandatory, I think those "objects" should have a common interface or something, so take a look to doctrine inheritance that should fulfill your needs
My suggestion is to store the entity namespace Ex. Acme\Entity\Document in a property and the id in another and to use the entity manager to get the entity.
Edit: Though you won't have the relation, I prefer that way over others because it is reusable and the performance is rather the same. Also if I need to pass it to a JSON response, I just create a normalizer and I am good to go.
/**
* #ORM\Column(type="string")
*/
private $entityNamespace;
/**
* #ORM\Column(type="integer")
*/
private $entityId;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function getEntity()
{
return $this->em->getRepository($this->entityNamespace)->find($this->entityId);
}
There are following entities: Zone, ZoneRecord
ZoneRecord has a Method validate() to validate against all other ZoneRecord's of related Zone.
Now I want to check / validate each ZoneRecord (the ones which are saved already plus the ones which are added by Zone->addRecord(ZoneRecord) on runtime) which is related to the Zone if Zone gets saved.
Right now I have a PreFlush Lifecyclecallback ZoneRecord->validate where I trigger this->getZone->getRecords(): this methods gives me only the already saved entities which are in db.
How can I get ALL related Entities of Zone (the saved from DB and the dynamicly added)?
The Problem seems to be in Doctrine internal.
I use InheritanceType "JOINED".
The annotation in Zone looks like:
/**
* #var ZoneRecord[]
* #ORM\OneToMany(targetEntity="Application\Entity\ZoneRecord", mappedBy="zone", cascade={"all"}, orphanRemoval=true)
*/
protected $records;
/**
* #var ZoneRecordA[]
* #ORM\OneToMany(targetEntity="Application\Entity\ZoneRecordA", mappedBy="zone", cascade={"all"}, orphanRemoval=true)
*/
protected $recordsa;
The annotation from ZoneRecord:
/**
* #ORM\Entity
* #ORM\Table(name="zonerecords")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap({
* "A" = "Application\Entity\ZoneRecordA"
* })
* #ORM\HasLifecycleCallbacks
*/
abstract class ZoneRecord
The annotation from ZoneRecordA:
/**
*
* #ORM\Entity
* #ORM\Table(name="zonerecords_a")
*
*/
class ZoneRecordA extends ZoneRecord
If I add or remove a ZoneRecord from Zone via Zone->addRecordA(ZoneRecord) / Zone->removeRecordA(ZoneRecord) it will only be removed from the protected $recordsa; stack. The protected $records; will be untouched until the entity is successfully flushed and reloaded.
It seems that Doctrine does internal differ between ZoneRecord and ZoneRecordA although they are marked as JOINED and ZoneRecordA extends ZoneRecord.
This is especially a problem if you want to delete Records - right now I use both delete Methods (Zone->removeRecord(ZoneRecordA) and Zone->removeRecordA(ZoneRecordA)) to be sure the Entity is removed from the EntityManager.
This strange behaviour also occurs if you add entitys to Zone via addRecord() or addRecordA(), they are used as different collections until they are flushed and reloaded.
(Sorry for my incoherent question: I tried to answer some questions as I was writing this post, but here it is:)
I'm trying to create a database model with a many-to-many relationship inside a link table, but which also has a value per link, in this case a stock-keeping table. (this is a basic example for more problems I'm having, but I thought I'd just test it with this before I would continue).
I've used exportmwb to generate the two Entities Store and Product for this simple example, both are displayed below.
However, the problem now is that I can't figure out how to access the stock.amount value (signed int, as it can be negative) using Doctrine. Also, when I try to create the tables using doctrine's orm:schema-tool:create function
This yielded only two Entities and three tables, one as a link table without values and two data tables, as many-to-many relationships aren't entities themselves so I can only have Product and Store as an entity.
So, logically, I tried changing my database model to have stock as a separate table with relationships to store and product. I also rewrote the fieldnames just to be able to exclude that as a source of the problem:
Then what I found was that I still didn't get a Stock entity... and the database itself didn't have an 'amount'-field.
I really needed to be able to bind these stores and products together in a stock table (among other things)... so just adding the stock on the product itself isn't an option.
root#hdev:/var/www/test/library# php doctrine.php orm:info
Found 2 mapped entities:
[OK] Entity\Product
[OK] Entity\Store
And when I create the database, it still doesn't give me the right fields in the stock table:
So, looking up some things here, I found out that many-to-many connections aren't entities and thus cannot have values. So I tried changing it to a separate table with relationships to the others, but it still didn't work.
What am I doing wrong here?
A Many-To-Many association with additional values is not a Many-To-Many, but is indeed a new entity, since it now has an identifier (the two relations to the connected entities) and values.
That's also the reason why Many-To-Many associations are so rare: you tend to store additional properties in them, such as sorting, amount, etc.
What you probably need is something like following (I made both relations bidirectional, consider making at least one of them uni-directional):
Product:
namespace Entity;
use Doctrine\ORM\Mapping as ORM;
/** #ORM\Table(name="product") #ORM\Entity() */
class Product
{
/** #ORM\Id() #ORM\Column(type="integer") */
protected $id;
/** ORM\Column(name="product_name", type="string", length=50, nullable=false) */
protected $name;
/** #ORM\OneToMany(targetEntity="Entity\Stock", mappedBy="product") */
protected $stockProducts;
}
Store:
namespace Entity;
use Doctrine\ORM\Mapping as ORM;
/** #ORM\Table(name="store") #ORM\Entity() */
class Store
{
/** #ORM\Id() #ORM\Column(type="integer") */
protected $id;
/** ORM\Column(name="store_name", type="string", length=50, nullable=false) */
protected $name;
/** #ORM\OneToMany(targetEntity="Entity\Stock", mappedBy="store") */
protected $stockProducts;
}
Stock:
namespace Entity;
use Doctrine\ORM\Mapping as ORM;
/** #ORM\Table(name="stock") #ORM\Entity() */
class Stock
{
/** ORM\Column(type="integer") */
protected $amount;
/**
* #ORM\Id()
* #ORM\ManyToOne(targetEntity="Entity\Store", inversedBy="stockProducts")
* #ORM\JoinColumn(name="store_id", referencedColumnName="id", nullable=false)
*/
protected $store;
/**
* #ORM\Id()
* #ORM\ManyToOne(targetEntity="Entity\Product", inversedBy="stockProducts")
* #ORM\JoinColumn(name="product_id", referencedColumnName="id", nullable=false)
*/
protected $product;
}
Doctrine handles many-to-many relationships just fine.
The problem that you're having is that you don't need a simple ManyToMany association, because associations can't have "extra" data.
Your middle (stock) table, since it contains more than product_id and store_id, needs its own entity to model that extra data.
So you really want three classes of entity:
Product
StockLevel
Store
and two associations:
Product oneToMany StockLevel
Store oneToMany StockLevel
I am developing my application using Zend Framework 2 and Doctrine 2.
While writting annotations, I am unable to understand the difference between mappedBy and inversedBy.
When should I use mappedBy?
When should I use inversedBy?
When should I use neither?
Here is an example:
/**
*
* #ORM\OneToOne(targetEntity="\custMod\Entity\Person", mappedBy="customer")
* #ORM\JoinColumn(name="personID", referencedColumnName="id")
*/
protected $person;
/**
*
* #ORM\OneToOne(targetEntity="\Auth\Entity\User")
* #ORM\JoinColumn(name="userID", referencedColumnName="id")
*/
protected $user;
/**
*
* #ORM\ManyToOne (targetEntity="\custMod\Entity\Company", inversedBy="customer")
* #ORM\JoinColumn (name="companyID", referencedColumnName="id")
*/
protected $company;
I did a quick search and found the following, but I am still confused:
example 1
example 2
example 3
mappedBy has to be specified on the inversed side of a (bidirectional) association
inversedBy has to be specified on the owning side of a (bidirectional) association
from doctrine documentation:
ManyToOne is always the owning side of a bidirectional assocation.
OneToMany is always the inverse side of a bidirectional assocation.
The owning side of a OneToOne assocation is the entity with the table containing the foreign key.
See https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/unitofwork-associations.html
The answers above were not sufficient for me to understand what was going on, so after delving into it more I think I have a way of explaining it that will make sense for people who struggled like I did to understand.
inversedBy and mappedBy are used by the INTERNAL DOCTRINE engine to reduce the number of SQL queries it has to do to get the information you need. To be clear if you don't add inversedBy or mappedBy your code will still work but will not be optimized.
So for example, look at the classes below:
class Task
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="task", type="string", length=255)
*/
private $task;
/**
* #var \DateTime
*
* #ORM\Column(name="dueDate", type="datetime")
*/
private $dueDate;
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="tasks", cascade={"persist"})
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
protected $category;
}
class Category
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="Task", mappedBy="category")
*/
protected $tasks;
}
These classes if you were to run the command to generate the schema (for example, bin/console doctrine:schema:update --force --dump-sql) you will notice that the Category table does not have a column on it for tasks. (this is because it does not have a column annotation on it)
The important thing to understand here is that the variable tasks is only there so the internal doctrine engine can use the reference above it which says its mappedBy Category. Now... don't be confused here like I was... Category is NOT referring TO THE CLASS NAME, its referring to the property on the Task class called 'protected $category'.
Like wise, on the Tasks class the property $category mentions it is inversedBy="tasks", notice this is plural, this is NOT THE PLURAL OF THE CLASS NAME, but just because the property is called 'protected $tasks' in the Category class.
Once you understand this it becomes very easy to understand what inversedBy and mappedBy are doing and how to use them in this situation.
The side that is referencing the foreign key like 'tasks' in my example always gets the inversedBy attribute because it needs to know what class (via the targetEntity command) and what variable (inversedBy=) on that class to 'work backwards' so to speak and get the category information from. An easy way to remember this, is the class that would have the foreignkey_id is the one that needs to have inversedBy.
Where as with category, and its $tasks property (which is not on the table remember, just only part of the class for optimization purposes) is MappedBy 'tasks', this creates the relationship officially between the two entities so that doctrine can now safely use JOIN SQL statements instead of two separate SELECT statements. Without mappedBy, the doctrine engine would not know from the JOIN statement it will create what variable in the class 'Task' to put the category information.
Hope this explains it a bit better.
In bidirectional relationship has both an owning side and an inverse side
mappedBy : put into The inverse side of a bidirectional relationship To refer to the field in the owning side of entity
inversedBy : put into The owning side of a bidirectional relationship To refer to the field on the inverse side of entity
AND
mappedBy attribute used with the OneToOne, OneToMany, or ManyToMany mapping declaration.
inversedBy attribute used with the OneToOne, ManyToOne, or ManyToMany mapping declaration.
Notice :
The owning side of a bidirectional relationship the side that contains the foreign key.
there two reference about inversedBy and mappedBy into Doctrine Documentation :
First Link,Second Link
5.9.1. Owning and Inverse Side
For Many-To-Many associations you can chose which entity is the owning and which the inverse side. There is a very simple semantic rule to decide which side is more suitable to be the owning side from a developers perspective. You only have to ask yourself, which entity is responsible for the connection management and pick that as the owning side.
Take an example of two entities Article and Tag. Whenever you want to connect an Article to a Tag and vice-versa, it is mostly the Article that is responsible for this relation. Whenever you add a new article, you want to connect it with existing or new tags. Your create Article form will probably support this notion and allow to specify the tags directly. This is why you should pick the Article as owning side, as it makes the code more understandable:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html