I'm very new using Doctrine, is the first project I work with it and I'm having an error while I try to insert a new user.
The thing is I've got a class User with a foreign key Country and when I try to insert a user Doctrine also try to insert the country, the country already exists so PDO launch an integrity constraint violation and Doctrine a Doctrine\DBAL\DBALException.
I know the annotation cascade={"persist"} makes the country entity to be written in the db, without it, doctrine launch another error:
A new entity was found through the relationship 'User#country' that was not configured to cascade persist operations for entity: Country#0000000078b1861f00007f935266d9fe. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement 'Country#__toString()' to get a clue.
I've tried with all cascade options and only with persist and all the error above doesn't come up...
Is there something like cascade={"no-persist"} or something that tells doctrine the value of this attribute must be already inserted in table country???
Some code:
/**
* User
*
* #Table(name="user")
* #Entity
*/
class User {
...
/**
* #var Country
*
* #OneToOne(targetEntity="Country", cascade={"persist"})
* #JoinColumn(name="country", referencedColumnName="id")
*/
private $country
...
}
/**
* Country
*
* #Table(name="country")
* #Entity
*/
class Country {
...
/**
* #var integer
*
* #Column(name="id", type="integer")
* #Id
*/
private $id;
}
Any clue will be highly appreciated.
Thanks.
Put the cascade=persist back in.
You need to check the database to see if the country exists. Your insert with an existing country fails because the country object needs to be managed by the entity manager.
$country = $countryRepository->find($countryId);
if (!$country)
{
$country = new Country();
$entityManager->persist($country);
}
$user->setCountry($country);
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.
Merge is creating not working for children #OneToMany
I am using Php Doctrine and I am using #OnToMany mapping with cascade all. I have a parent class SalesOrder and child class SalesOrderDetails.
Case 1 : Save - When I save new record sales order along with sales order details. It is working as expected.
Case 2 : Update - Here is the issue, I am merging the Sales Order which is fine however its inserting new records for its children SalesOrderDetail instead of updating it. Ideally it should it apply mergebut for children as well but its not.
As of now, I am getting the Sales Order Details by id from DB then change the properties of it. Ideally that should not be the case, mean if we set the id to unmanned object, it should update instead of creating new records.
Note:
1. Merge is working with parent object if it has the id value.
2. I am not adding new item here, I am just updating the existing recorded through merge.
SalesOrder.php
/**
* #Entity #Table(name="sales_orders")
* */
class SalesOrder extends BaseEntity {
/**
* #OneToMany(targetEntity="SalesOrderDetail",cascade="all", mappedBy="salesOrder" )
*/
protected $itemSet;
function __construct() {
$this->itemSet = new ArrayCollection();
}
}
SalesOrderDetail.php
/**
* #Entity #Table(name="sales_order_details")
* */
class SalesOrderDetail extends BaseEntity {
/** #Id #Column(type="integer") #GeneratedValue * */
protected $id;
/**
* #ManyToOne(targetEntity="SalesOrder")
* #JoinColumn(name="order_no", referencedColumnName="order_no")
*/
protected $salesOrder;
}
Debug Mode screen
If I use cascade={"merge"}
I am getting different error if I am using Cascades merge
Type: Doctrine\ORM\ORMInvalidArgumentException Message: Multiple
non-persisted new entities were found through the given association
graph: * A new entity was found through the relationship
'Ziletech\Database\Entity\SalesOrder#itemSet' that was not configured
to cascade persist operations for entity:
Ziletech\Database\Entity\SalesOrderDetail#0000000052218380000000007058b4a6.
To solve this issue: Either explicitly call EntityManager#persist() on
this unknown entity or configure cascade persist this association in
the mapping for example #ManyToOne(..,cascade={"persist"}). If you
cannot find out which entity causes the problem implement
'Ziletech\Database\Entity\SalesOrderDetail#__toString()' to get a
clue. * A new entity was found through the relationship
'Ziletech\Database\Entity\SalesOrder#itemSet' that was not configured
to cascade persist operations for entity:
Ziletech\Database\Entity\SalesOrderDetail#0000000052218071000000007058b4a6.
To solve this issue: Either explicitly call EntityManager#persist() on
this unknown entity or configure cascade persist this association in
the mapping for example #ManyToOne(..,cascade={"persist"}). If you
cannot find out which entity causes the problem implement
'Ziletech\Database\Entity\SalesOrderDetail#__toString()' to get a
clue.
You have a mistake in your mapping, cascade needs an array
/**
* #OneToMany(targetEntity="SalesOrderDetail", cascade={"all"}, mappedBy="salesOrder" )
*/
protected $itemSet;
I'm using the zfcuser doctrine module for my zf2 project with bjyauthorize, which is working quite fine. Now I'd like to get the connected Country entity out of my User entity
in User.php:
/**
* An example of how to implement a role aware user entity.
*
* #ORM\Entity
* #ORM\Table(name="users", indexes={
* #ORM\Index(name="fk_User_Country1_idx", columns={"Country_id"}),
* }, uniqueConstraints={#ORM\UniqueConstraint(name="email_UNIQUE", columns={"email"})})
* #ORM\HasLifecycleCallbacks()
*/
class User implements UserInterface, ProviderInterface
{
...
/**
* #var ersEntity\Country
* #ORM\ManyToOne(targetEntity="Country", inversedBy="users")
* #ORM\JoinColumn(name="Country_id", referencedColumnName="id")
*/
protected $country;
in Country.php
/**
* Entity\Country
*
* #ORM\Entity()
* #ORM\Table(name="Country")
* #ORM\HasLifecycleCallbacks()
*/
class Country implements InputFilterAwareInterface
{
...
/**
* #ORM\OneToMany(targetEntity="User", mappedBy="country")
* #ORM\JoinColumn(name="id", referencedColumnName="Country_id")
*/
protected $users;
A simple testAction in one of my Controllers fails:
$user = $em->getRepository("ersEntity\Entity\User")
->findOneBy(array('id' => 1));
error_log($user->getFirstname().' '.$user->getSurname());
error_log('country: '.$user->getCountry()->getName());
which results in:
[Tue May 19 00:02:44 2015] [error] [client 185.17.207.16] Andi N.
[Tue May 19 00:02:44 2015] [error] [client 185.17.207.16] PHP Fatal error: Call to a member function getName() on a non-object in /home/ers/www/ers/module/Admin/src/Admin/Controller/TestController.php on line 172
I'm wondering why it's not possible to get the Country entity from the User entity. With other entities in this same project this is working fine.
Can somebody tell me what needs to be done to be able to get the Country entity out of this zfcuser-bjyauthorize-doctrine User entity?
For more code info, this whole project is available at https://github.com/inbaz/ers in the develop branch.
EDIT:
With users who have no country set there needs to be an error. It's right that there is the need to check if a country exists. But this user has a country set. I checked that via phpmyadmin. It's not possible to get this country entity via the getCountry() method.
Maybe this is cause the deserializing and serializing of the doctrine entity into the session. I checked the doctrine documentation on how to save entities into the session. But I'd like to keep all subentities in the session, so in my case I have a order entity in the session which holds multiple package entities. Each package entity has one user and multiple item entities. When getting the order entity back from the session I'd like to be able to access all these elements.
I even tried to do a merge on each user in the session like:
foreach($participants as $participant) {
$participant = $em->merge($participant);
}
but that doesn't change anything. Even a merge on the whole order was not successful.
Do you have an idea on how to get the doctrine entities back from the session with the full doctrine features?
It looks all fine to me. At first I thought your getCountry method was missing from your entity definition but I see it is there.
My guess is that user with id 1 does not have a Country set. Go to your table (phpmyadmin or whatever) and check the value of the Country_id column.
If you want each user to have a country you can insure your data integrity by adding nullable=false to your join column definition according to the Doctrine2 #JoinColumn specs.
Otherwise (if $country is allowed to be null) you should maybe first check if country is an object before pulling getName():
$countryName = null;
$country = $user->getCountry();
if(is_object($country)){
$countryName = $country->getName();
}
/** #var null|string $countryName */
$countryName //... use your country name which is null or string
One more thing, I would strongly suggest to use only lowercase strings for table and column names. Using uppercase could get you into trouble.
EDIT
I took a closer look and see now that your mappings are wrong. Did you use doctrine to validate your schema? There is a #ORM\JoinColumn on the inverse side of the relationship (inside Country.php). The inverse side does not need this #ORM\JoinColumn declariation. You should remove this line. The owning side is User so the join column definition should be only declared there.
It should be like this:
Country.php:
/**
* #var Collection
* #ORM\OneToMany(targetEntity="User", mappedBy="country")
*/
protected $users;
User.php:
/**
* #var Country
* #ORM\ManyToOne(targetEntity="Country", inversedBy="users")
* #ORM\JoinColumn(name="Country_id", referencedColumnName="id")
*/
protected $country;
Doctrine 2: Can entities be saved into sessions?
This is the answer to my question.
if a lazy-loaded entity is not loaded at serialization time, it won't be loadable after de-serialization. So you have to make sure the entity is fully loaded before serializing it.
There is still the question open if it's not possible to create a new doctrine entity and get an array copy of the de-serialized one and populate this data into the new 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
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.