Symfony2 Doctrine2 trouble with optional one to one relation - symfony

I have a problem with Doctrine2 in Symfony2 and two relationed entities.
There is a user-entity that can (not must) have a usermeta-entity referenced which contains information like biography etc.
The usermeta is optional because user is imported by another system, while usermeta is managed in my application.
Of course I want to save both together, so that saving a user must create or update a usermeta-entity.
Both are joined by a column named aduserid (same name in both tables).
I've recognized that if usermeta is an optional reference the owning-side in this case should be usermeta, otherwise doctrine loads user and needs the usermeta entity - but it's not always there.
Please note the comments in User->setMeta..
/**
* User
*
* #ORM\Table(name="user")
* #ORM\Entity
*/
class User
{
/**
* #var Usermeta
* #ORM\OneToOne(targetEntity="Usermeta", mappedBy="user", cascade={"persist"})
*/
protected $meta;
public function getMeta()
{
return $this->meta;
}
/**
*
* #param Usermeta $metaValue
*/
public function setMeta($metaValue)
{
// I've tried setting the join-column-value here
// - but it's not getting persisted
// $metaValue->setAduserid($this->getAduserid());
// Then I've tried to set the user-object in Usermeta - but then
// it seems like Doctrine wants to update Usermeta and searches
// for ValId names aduserid (in BasicEntityPersister->_prepareUpdateData)
// but only id is given - so not luck here
// $metaValue->setUser($this);
$this->meta = $metaValue;
}
/**
* #var integer
*
* #ORM\Column(name="rowid", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* Get rowid
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* #var integer
*
* #ORM\Column(name="ADuserid", type="integer", nullable=false)
*/
private $aduserid;
/**
* Set aduserid
*
* #param integer $aduserid
* #return User
*/
public function setAduserid($aduserid)
{
$this->aduserid = $aduserid;
return $this;
}
/**
* Get aduserid
*
* #return integer
*/
public function getAduserid()
{
return $this->aduserid;
}
// some mor fields....
}
And the Usermeta class:
/**
* Usermeta
*
* #ORM\Table(name="userMeta")
* #ORM\Entity
*/
class Usermeta
{
/**
* #ORM\OneToOne(targetEntity="User", inversedBy="meta")
* #ORM\JoinColumn(name="ADuserid", referencedColumnName="ADuserid")
*/
protected $user;
public function getUser()
{
return $this->$user;
}
public function setUser($userObj)
{
$this->user = $userObj;
}
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var integer
*
* #ORM\Column(name="ADuserid", type="integer", nullable=false)
*/
private $aduserid;
/**
* Set aduserid
*
* #param integer $aduserid
* #return User
*/
public function setAduserid($aduserid)
{
$this->aduserid = $aduserid;
return $this;
}
/**
* Get aduserid
*
* #return integer
*/
public function getAduserid()
{
return $this->aduserid;
}
}
the controller code looks like this:
...
$userForm->bind($request);
if($userForm->isValid()) {
$em->persist($user);
$em->flush();
}
...

The Zdenek Machek comment is almost correct. As you can see from the Doctrine2 documentation, the nullable option should be in the join annotation (#JoinColumn), not in the mapping one (#OneToOne).
#JoinColumn doc:
This annotation is used in the context of relations in #ManyToOne, #OneToOne fields and in the Context of #JoinTable nested inside a #ManyToMany. This annotation is not required. If its not specified the attributes name and referencedColumnName are inferred from the table and primary key names.
Required attributes:
name: Column name that holds the foreign key identifier for this relation. In the context of #JoinTable it specifies the column name in the join table.
referencedColumnName: Name of the primary key identifier that is used for joining of this relation.
Optional attributes:
unique: Determines if this relation exclusive between the affected entities and should be enforced so on the database constraint level. Defaults to false.
nullable: Determine if the related entity is required, or if null is an allowed state for the relation. Defaults to true.
onDelete: Cascade Action (Database-level)
onUpdate: Cascade Action (Database-level)
columnDefinition: DDL SQL snippet that starts after the column name and specifies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. Using this attribute on #JoinColumn is necessary if you need slightly different column definitions for joining columns, for example regarding NULL/NOT NULL defaults. However by default a “columnDefinition” attribute on #Column also sets the related #JoinColumn’s columnDefinition. This is necessary to make foreign keys work.
http://doctrine-orm.readthedocs.org/en/latest/reference/annotations-reference.html#annref-joincolumn
#OneToOne doc:
The #OneToOne annotation works almost exactly as the #ManyToOne with one additional option that can be specified. The configuration defaults for #JoinColumn using the target entity table and primary key column names apply here too.
Required attributes:
targetEntity: FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. IMPORTANT: No leading backslash!
Optional attributes:
cascade: Cascade Option
fetch: One of LAZY or EAGER
orphanRemoval: Boolean that specifies if orphans, inverse OneToOne entities that are not connected to any owning instance, should be removed by Doctrine. Defaults to false.
inversedBy: The inversedBy attribute designates the field in the entity that is the inverse side of the relationship.
http://doctrine-orm.readthedocs.org/en/latest/reference/annotations-reference.html#onetoone

You're using the wrong type of Relation for your problem.
What you want is a unidirectional one to one from Usermeta to User.
A bidirectional one to one relationship would mean the following:
A user MUST have a Usermeta object.
A Usermeta object MUST have a User.
In your case you're only trying to require the second condition.
This does mean that you can only hydrate User from Usermeta and not the other way around.
Unfortunately doctrine does not support Zero or One to Many relationships.

I got the error message "spl_object_hash() expects parameter 1 to be object, null given in..." while trying the same thing. I tried to define a bidirectional One to One relationship while the inversed value could be null. This gave the error message. Taking away the inversed side of the relationship solved the problem.
It is a pity that Zero or One to One relationships aren't supported.

I hope I do not disturb anyone by submitting this very late answer, but here is how I solved this problem:
/**
* #var Takeabyte\GripBundle\Entity\PDF
* #ORM\OneToOne(targetEntity="Takeabyte\GripBundle\Entity\PDF", inversedBy="element", fetch="EAGER", orphanRemoval=true)
*/
protected $pdf = null;
I added = null; to the attribute declaration. I hope this is of any help for anyone who reads this.

Reading my own old question is quite fun since I see the problem at first glance now..
When it came to a solution I've thought that doctrine can only handle Ids named "id", but ... aduserid is just not marked as ID, it's missing the Id annotation and doctrine cannot use the fields for the join column..
Second thing, Zdenek Machek was right: It has to be marked as nullable.

Related

Symfony: How to configure embedded form to create new child object?

I'm building a user management page where I create or edit users.
The user consists of two entities, user and profile, which have a one to one relationship (I would merge, but can't for historical reasons).
/* User.php - Entity Class
/**
* #var Profile
* #Assert\Type(type="App\Entity\Profile")
* #Assert\Valid()
* #ORM\OneToOne(targetEntity="Profile", mappedBy="user", cascade={"persist"})
*/
private $profile;
/* Profile.php - Profile Entity Class
/**
* #var \App\Entity\User
*
* #ORM\OneToOne(targetEntity="App\Entity\User", inversedBy="profile")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* })
*/
private $user;
I built two forms.
One form is the profile form which contains all the essential profile fields (first_name, last_name, email), although does not explicitly contain the relation field (user_id).
The other form is the user form, which contains the basic user fields (username, password), and also includes the profile form.
$builder->add('profile', ProfileForm::class);
When I use this form for editting, everything works fine, and changes to both objects persist. But when I try to use the form to create a new user, it fails, saying that I'm missing user_id.
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'user_id' cannot be null
It seems like this should work, but I'm missing something.
Thanks to #Cerad for this answer. I was not setting the link in my User entity setProfile class.
/**
* Set profile
*
* #param \App\Entity\Profile $profile
*
* #return SfGuardUser
*/
public function setProfile(\App\Entity\Profile $profile = null)
{
$this->profile = $profile;
$this->profile->setUser($this); // Adding this line fixed my issue
return $this;
}

Symfony2 ManytoMany relation with nullable true

I want to use an optional ManyToMany relation between Ordo_soins_perfusion and Ordo_soins_medicament entities.
class Ordo_soins_perfusion
{
/**
* #ORM\ManyToMany(targetEntity="Ordo_soins_medicament",cascade={"persist"})
*#ORM\JoinTable(name="ordo_soinperf_soinmedoc",
* joinColumns={#ORM\JoinColumn(name="Ordo_soins_perfusion_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="Ordo_soins_medicament_id", referencedColumnName="id",nullable=true)})
*/
private $medoc;
class Ordo_soins_medicament
{
/**
* #var string
*
* #ORM\Column(name="medicament", type="string", length=255,nullable=true)
*/
private $medicament;
/**
* #var string
*
* #ORM\Column(name="quantite", type="string", length=50,nullable=true)
*/
private $quantite;
Now when i save a new Ordo_soins_perfusion object without filling the Ordo_soins_medicament form i found a new ligne created in the join table and in the Ordo_soins_medicament table.
How the add a Ordo_soins_medicament object only if not null
Thanks
First make sure the ManyToMany relationship is correct. Thereafter initialize an empty new ArrayCollection() on these properties, so relationships actually can be added by Doctrine.
public function __construct()
{
$this->medoc = new ArrayCollection();
}
The next step is to make sure the relationship is set properly, by adding getters/setters. In case of collections you could also use add.
public function addMedoc($item)
{
$item->setPerfusion($this);
$this->medoc->add($item);
}
Doctrine will handle the relationship/join table when you persist and flush the new entity.

Cascade remove working fined but not when more than one entry... why?

I am building an app where USERS can give a SCORE to some ANSWERS left by the community.
here the schema/relations of the entities:
ANSWER (one to many with) SCORE (many to one with) USER
I want that when i remove an ANSWER, the SCORES related to that ANSWER are also deleted...
BUT when I have more than one SCORE, then symfony triggers this exception:
"SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`symfony`.`Score`, CONSTRAINT `FK_6F8F552A4AB7A507` FOREIGN KEY (`answer_id`) REFERENCES `Answer` (`id`))
I must say that it works fine if there is only one SCORE related to that answer. (strange?)
Here my entities:
you will see that I do have the cascade={"remove"}, I emphasis the fact that SCORE id is build on ANSWER_ID and USER_ID. (because I want that a SCORE can only be delivered once for an answer by a user).
class=Answer
{
/**
* #ORM\OneToMany(targetEntity="XX\BlogBundle\Entity\Score", mappedBy="answer", cascade={"persist", "remove"})
**/
private $scores;
/**
* #param \XX\BlogBundle\Entity\Score $scores
*/
public function removeScore(\XX\BlogBundle\Entity\Score $score)
{
$this->scores->removeElement($score);
}
// OTHER ATTRIBUTES ETC
}
.
class=Score
{
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="XX\BlogBundle\Entity\Anwer", inversedBy="scores")
*/
private $answer;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="XX\BlogBundle\Entity\User", cascade={"persist"})
*/
private $user;
/**
* #var smallint
* #ORM\Column(name="valeur", type="smallint", length=10, nullable=true)
*/
private $value;
// SETTER AND GETTER
}
.
class User
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
* #ORM\Column(name="name", type="string", length=10)
*/
private $name;
}
I would remove them manually instead of any cascade operations.. Something like this:
public function removeAnswerAction($id)
{
...
$answer = /* ... */ ->find($id);
foreach ($answer->getScores as $score) {
$answer->removeScore($score);
$score->setAnswer(null); // it requires setAnswer(Answer $answer = null), if you used a type hint in getter
$em->remove($score); // you can remove it if you want..
}
$em->remove($answer);
$em->flush();
...
}
I don't know how cascade=remove works with collections (I've never been interested in using cascade operations), but in your case doctrine is saying that it cant remove parent (Answer object) without deleting all its collection (related Score objects)..
Probleme solved (see below my exlanation) but not sure if it is the best way. (If anyone has a better solution?)
Actually the probleme doesn't come from the entities or the cascade remove (all that part is ok, maybe it can be optimised)
The issue come from the fact that I used an $answer object on which I had removed some of its scores (throught removeScore(score) in the controller before doing $em->remove($answer).
Also when symfony/doctrine tried to remove in cascade the scores associated to that $answer it couldn't find all of them and potentially would have create some orphin entries in the database, which triggered that exception.
So because I trully need to remove some scores from the $answer object, my solution is to store all thoses scores that I remove in an array so that I can add them to the $answer object before removing it.
$answer = $em->getRepository('XXBlogBundle:Answer')->find(1);
foreach($answer->getScores() as $score)
{
// I REMOVE SOME SCORES HERE FROM THE ANSWER OBJECT
if($score->getValue() == 0)
{
$array_temp_save_score[] = $score;
$answer->removeScore($score);
}
}
// SOME OF MY CODE WHERE I USE $ANSWER WITHOUT ALL SCORE
// ...
// END OF THAT PART
foreach($array_temp_save_score as $v)
{
$answer->addScore($v);
}
$em->remove($answer);
$em->flush();

ManyToMany relationship with extra fields in symfony2 orm doctrine

Hi i have that same question as here: Many-to-many self relation with extra fields? but i cant find an answer :/ I tried first ManyToOne and at the other site OneToMany ... but then i could not use something like
public function hasFriend(User $user)
{
return $this->myFriends->contains($user);
}
because there was some this problem:
This function is called, taking a User type $user variable and you then use the contains() function on $this->myFriends.
$this->myFriends is an ArrayCollection of Requests (so different type than User) and from the doctrine documentation about contains():
The comparison of two elements is strict, that means not only the value but also the type must match.
So what is the best way to solve this ManyToMany relationship with extra fields? Or if i would go back and set the onetomany and manytoone relationship how can i modify the hasFriend method? To example check if ID is in array collection of ID's.
EDIT: i have this table... and what i need is:
1. select my friends... and my followers ...check if i am friend with him or not. (because he can be friend with me and i dont have to be with him... like on twitter). I could make manytomany but i need extra fields like: "viewed" "time when he subscribe me" as you can see at my table.
And make query like this and then be able in twig check if (app.user.hasFriend(follower) or something like that)
$qb = $this->createQueryBuilder('r')
->select('u')
->innerJoin('UserBundle:User', 'u')
->Where('r.friend_id=:id')
->setParameter('id', $id)
->orderBy('r.time', 'DESC')
->setMaxResults(50);
return $qb->getQuery()
->getResult();
I was trying to have a many to many relationship with extra fields, and couldn't make it work either... The thing I read in a forum (can't remember where) was:
If you add data to a relationship, then it's not a relationship anymore. It's a new entity.
And it's the right thing to do. Create a new entity with the new fields, and if you need it, create a custom repository to add the methods you need.
A <--- Many to many with field ---> B
would become
A --One to many--> C (with new fields) <-- One to many--B
and of course, C has ManyToOne relationships with both A and B.
I searched everywhere on how to do this, but in the end, it's the right thing to do, if you add data, it's no longer a relationship.
You can also copy what contains usually do, or try to overwrite it in a custom repository, to do whatever you need it to do.
I hope this helps.
I'm adding another answer since it has nothing to do with my original answer. Using the new info you posted, I'm calling the table/entity you posted "Follower". The original entity, "User".
What happens if you create the following associations:
namespace Acme\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Acme\UserBundle\Entity\User
*
* #ORM\Table()
* #ORM\Entity
*/
class User
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Acme\FollowerBundle\Entity\Follower", mappedBy="followeduser")
*/
protected $followers;
/**
* #ORM\OneToMany(targetEntity="Acme\FollowerBundle\Entity\Follower", mappedBy="followeeuser")
*/
protected $followees;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
public function __construct()
{
$this->followers = new \Doctrine\Common\Collections\ArrayCollection();
$this->followees = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add followers
*
* #param Acme\FollowerBundle\Entity\Follower $follower
*/
public function addFollower(\Acme\FollowerBundle\Entity\Follower $follower)
{
$this->followers[] = $follower;
}
/**
* Add followees
*
* #param Acme\FollowerBundle\Entity\Follower $followee
*/
public function addFollowee(\Acme\FollowerBundle\Entity\Follower $followee)
{
$this->followees[] = $followee;
}
/**
* Get followers
*
* #return Doctrine\Common\Collections\Collection
*/
public function getFollowers()
{
return $this->followers;
}
/**
* Get followees
*
* #return Doctrine\Common\Collections\Collection
*/
public function getFollowees()
{
return $this->followees;
}
}
namespace Acme\FollowerBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Acme\FollowerBundle\Entity\Follower
*
* #ORM\Table()
* #ORM\Entity
*/
class Follower
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\User", inversedBy="followers")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $followeduser;
/**
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\User", inversedBy="followees")
* #ORM\JoinColumn(name="followee_id", referencedColumnName="id")
*/
protected $followeeuser;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set followeduser
*
* #param Acme\UserBundle\Entity\User $followeduser
*/
public function setFolloweduser(\Acme\UserBundle\Entity\User $followeduser)
{
$this->followeduser = $followeduser;
}
/**
* Get followeduser
*
* #return Acme\UserBundle\Entity\User
*/
public function getFolloweduser()
{
return $this->followeduser;
}
/**
* Set followeeuser
*
* #param Acme\UserBundle\Entity\User $followeeuser
*/
public function setFolloweeuser(\Acme\UserBundle\Entity\User $followeeuser)
{
$this->followeeuser = $followeeuser;
}
/**
* Get followeeuser
*
* #return Acme\UserBundle\Entity\User
*/
public function getFolloweeuser()
{
return $this->followeeuser;
}
}
I'm not sure if this would do the trick, I really don't have much time to test it, but if it doesn't, I thnk that it's on it's way. I'm using two relations, because you don't need a many to many. You need to reference that a user can have a lot of followers, and a follower can follow a lot of users, but since the "user" table is the same one, I did two relations, they have nothing to do with eachother, they just reference the same entity but for different things.
Try that and experiment what happens. You should be able to do things like:
$user->getFollowers();
$follower->getFollowedUser();
and you could then check if a user is being followed by a follower whose user_id equals $userThatIwantToCheck
and you could search in Followers for a Follower whose user = $user and followeduser=$possibleFriend

An optional OneToOne relationship between entities?

I have a User Entity. This is considered the primary entity in this case and the mere fact it is being used means it is present.
The User entity, has a Store entity. But not all Users will necessarily have a Store entity.
It is worth noting that this is an existing database we are working with, and the id for the User table is the same as the id for the Store table. Name (id) and Value. It's just that in some cases, Store does not have a record for a given User id.
User:
class User extends Entity
{
/**
* #ORM\Id
* #ORM\Column(type="string", length=36)
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="Store")
* #ORM\JoinColumn(name="id", referencedColumnName="id")
*/
protected $store;
...
}
Store:
class Store extends Entity
{
/**
* #ORM\Id
* #ORM\Column(type="string", length=36)
*/
protected $id;
...
}
This causes problems in the controllers. If a User entity does not have a Store record, it fails with a "Entity not found" exception. This can be dealt with using a try catch easy enough (I haven't been able to find a way to check if an Entity object exists or is just a proxy). If the User does have a store record, all is fine here.
But the big issue I have is especially the Fixtures:
protected function createUser($id)
{
$user = new User();
$user->setId($id);
$user->setEmail($id.'#example.com');
$user->setUserName($id.'_name');
$user->setArea($this->manager->find('Area', 156)); // Global
$this->manager->persist($user);
return $user;
}
When I run Fixtures, this fails. Giving me the error "Integrity constraint violation: 1048 Column 'id' cannot be null". This message disappears if I remove the store entity from User. So in a nutshell, I cannot add a user if it doesn't have a store.
Anyone know what's happening? I've done some looking around and I can't find anything, including doctrine docs, on having optional relationships between Entities. Which I thought would have been a common situation.
Found the solution to this on this doc page:
http://docs.doctrine-project.org/en/latest/tutorials/composite-primary-keys.html#use-case-3-join-table-with-metadata
In my case, rather than the User entity being associated with the Store entity using the id field, the store property in the User entity would be associated to the Store entity by user (an entity object). In return, the Store object will hold a User entity, which is annotated as the entity's id.
I'm sure that's as confusing as hell, so just look at the sample above. Below are my adjusted Entity classes:
User
class User extends Entity
{
/**
* #ORM\Id
* #ORM\Column(type="string", length=36)
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="Store", mappedBy="user")
*/
protected $store;
...
}
Store
class Store extends Entity
{
/**
* #ORM\Column(type="string", length=36)
*/
protected $id;
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="User")
* #ORM\JoinColumn(name="id", referencedColumnName="id")
*/
protected $user;
...
}
Now, if there is no Store record present for a given User, the store property in the User entity will be null. Fixtures runs as expected too.
In addition to the answer above, I also needed to add an inversedBy attribute. Otherwise, an invalid Entity mapping error will be thrown.
Using the entities above, the Store object would need to look like this:
class Store extends Entity
{
/**
* #ORM\Column(type="string", length=36)
*/
protected $id;
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="User", inversedBy="store")
* #ORM\JoinColumn(name="id", referencedColumnName="id")
*/
protected $user;
...
}

Resources