Persisting entity with Doctrine association - php

I'm having trouble trying to persist an entity with an association using Doctrine.
Here's the mapping on my owning side: (User.php)
/** #Role_id #Column(type="integer") nullable=false */
private $role_id;
/**
* #ManyToOne(targetEntity="Roles\Entities\Role")
* #JoinColumn(name="role_id", referencedColumnName="id")
*/
private $role;
There's no mapping on the inverse side, I tried with (OneToMany) and it didn't seem to make a difference.
Basically, I'm passing a default value of 2 (integer) to a method setRole_id but it shows up as blank when I actually go to persist the entity which causes a MySQL error as that column doesn't allow nulls.
Edit 1:
Literally just persisting this for role_id
$this->user->setRole_id( 2 );
Cheers,
Ewan

Your mapping seems incorrect. Try to rewrite it as follows:
/**
* #ManyToOne(targetEntity="Roles\Entities\Role")
* #JoinColumn(name="role_id", referencedColumnName="id", nullable=false)
*/
private $role;
In other words, you only need to describe the role_id as the join column of your relationship. You don't need to map it as a "normal" column. Then just write and use a regular setter declared like the one below:
public function setRole(Roles\Entities\Role $role) {
$this->role = $role;
}
Use the above instead of $this->user->setRole_id(2) and persist your user entity. Doctrine should automatically take care of storing the correct entity ID in the foreign key field for you.

Related

Doctrine: how to describe manyToOne relation with composite key and update cascade?

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.

Enter entity property from the inversed side in one to one relation

Let's assume i have an one-to-one relation with one entity person
class Person
{
...
/**
* #var Player
* #ORM\OneToOne(targetEntity="AppBundle\Entity\Player", inversedBy="person")
*/
private $player;
...
}
and one entity Player
class Player
{
...
/**
* #var Person
* #ORM\OneToOne(targetEntity="AppBundle\Entity\Person", mappedBy="player")
*/
private $person;
...
}
Now the person side is holding the foreign key for the person.
Every try to access something from the inversed side is failing, for example
$em->getRepository('AppBundle:Player')->findByPerson();
ends up in
[Doctrine\ORM\ORMException]
You cannot search for the association field
'AppBundle\Entity\Player#person', because it is the inverse side of
an association. Find methods only work on owning side associations.
Doing the same to the owning side (find player for the person), everything is fine.
I cant figure out: How can i access entities from both sides?
I need that, because i need to know, which player hasn't already persons assigned and vice versa. I thought, doctrine is loading the related entities ... for this case plain sql seems the easier solution for that? Or have i really to deal with dql and joins?
In your class Player, can you try with this :
class Player
{
...
/**
* #var Person
* #ORM\OneToOne(targetEntity="AppBundle\Entity\Person", mappedBy="player")
* #ORM\JoinColumn(name="person_id", referencedColumnName="id")
*/
private $person;
...
}
Here is the doctrine doc: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#one-to-one-bidirectional

Doctrine one-to-many returning null

I'm trying to map a Milestones to a Project but when I try to reference the relation it's always returning null.
The database looks perfect, the targetEntity paths are correct and the scheme is validating by using
doctrine:scheme:validate
project.php
/**
* #ORM\OneToMany(targetEntity="Planning\Readmodel\Project\Milestone\Milestone", mappedBy="project", cascade={"persist", "remove"})
*/
private $milestones;
milestone.php
/**
* #ORM\ManyToOne(targetEntity="Planning\Readmodel\Project\Project", inversedBy="milestones", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="projectId", referencedColumnName="projectId")
*/
private $project;
But when I try to get the milestone I get null using:
$this->milestones;
Any idea what I might be doing wrong? Thank you.
Your owning entity definition i.e Project looks fine to me but your inversed entity i.e Milestone has a problem in JoinColumn annotation in JoinColumn annotation name relates to the column of your current entity which hold the relation to project entity but in referencedColumnName you have to provide the column of your parent entity that is primary key of project entity which should be referencedColumnName="id"
So your annotation for milestone entity should be like
/**
* #ORM\ManyToOne(targetEntity="Planning\Readmodel\Project\Project", inversedBy="milestones", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="project_id", referencedColumnName="id")
*/
private $project;
According to the official docs 5.11. Mapping Defaults
The name of the join table defaults to a combination of the simple,
unqualified class names of the participating classes, separated by an
underscore character. The names of the join columns default to the
simple, unqualified class name of the targeted class followed by
“_id”. The referencedColumnName always defaults to “id”, just as in
one-to-one or many-to-one mappings.
Make sure to update your database by running doctrine update command

How to safely & remove this associated entity, or set the ID null

[Using Symfony2 & Doctrine]
I have two classes
/* AppBundle\Entity\AccessToken
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User)
*/
protected $user;
and AppBundle\Entity\User, which contains no references to AppBundle\Entity\AccessToken. I get the famous parent conflict error from symfony attempting to delete a persisted User object.
Is there a way I can easily remove these access tokens, or set the user_id NULL without manually looping through my entities?
On a bidirectional association I can use cascade. I can't figure out what to do here.
Any thoughts?
You can set on delete parameter on your association.
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User)
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="SET NULL")
*/
protected $user;

Symfony Association Mapping OneToOne and OneToMany to Same Entity

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

Categories