Symfony2 ManytoMany relation with nullable true - symfony

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.

Related

Cascading simple derived identity in Doctrine

How can I cascade a joined model with a OneToOne relationship where by only the User table has an auto increment strategy and the joined Profile model must have an id that matches the User id.
My models look like this:
Company\Model\User:
class User
{
/**
* #Id
* #GeneratedValue
* #Column(type="integer")
* #var int
*/
private $id;
/**
* #OneToOne(targetEntity="Profile", inversedBy="user", cascade={"persist"})
* #var Profile
*/
private $profile;
Company\Model\Profile:
class Profile
{
/**
* #Id
* #OneToOne(targetEntity="User", mappedBy="profile")
* #JoinColumn(name="id")
* #var User
*/
private $user;
When persisting an instance of the User model, it causes the following error to be output:
Entity of type Company\Model\Profile is missing an assigned ID for field 'profile'. The identifier generation strategy for this entity requires the ID field to be populated before EntityManager#persist() is called. If you want automatically generated identifiers instead you need to adjust the metadata mapping accordingly.
The doctrine documentation calls this a simple derived identity, but does not explain how to cascade it.
https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/tutorials/composite-primary-keys.html#use-case-2-simple-derived-identity
It turns out that the answer is actually quite simple.
First the mappedBy and inversedBy need to be swapped around.
Secondly, when setting the Profile on the User you must set the user on the profile in turn.
Company\Model\User:
class User
{
/**
* #Id
* #GeneratedValue
* #Column(type="integer")
* #var int
*/
private $id;
/**
* #OneToOne(targetEntity="Profile", mappedBy="user", cascade={"persist"})
* #var Profile
*/
private $profile;
public function setProfile(Profile $profile): void
{
$this->profile = $profile;
$this->profile->setUser($this);
}
Company\Model\Profile:
class Profile
{
/**
* #Id
* #OneToOne(targetEntity="User", inversedBy="profile")
* #JoinColumn(name="id")
* #var User
*/
private $user;

Symfony2 OneToMany relationship - remove action isn't called

I'm using Symfony 2.4.6 and I'm trying to use OneToMany relationship to manage images added to a banner.
I did read a lot about the deletion of a child element (setting orphanRemoval, adding 'remove' to cascade) but none of those worked for me. What I've noticed is that remove actions isn't called at all on update.
I have 2 classes, Banner and BannerFile and using collection field type for adding images and it seems to work OK except the delete action.
class Banner
{
/.../
/**
* #ORM\OneToMany(targetEntity="BannerFile", cascade={"persist", "remove"}, mappedBy="banner", orphanRemoval=true)
*/
private $bannerFiles;
/.../
/**
* Remove bannerFiles
*
* #param BannerFile $bannerFiles
*/
public function removeBannerFile(BannerFile $bannerFiles)
{
$this->bannerFiles->removeElement($bannerFiles);
}
}
class BannerFile
{
/.../
/**
* #var integer $banner
*
* #ORM\ManyToOne(fetch="EXTRA_LAZY", inversedBy="bannerFiles", targetEntity="Banner")
* #ORM\JoinColumn(name="banner_id", nullable=false, onDelete="CASCADE", referencedColumnName="id")
*/
private $banner;
/.../
}
My problem is that the removeBannerFile isn't called.
Thanks for any help.
This happens if you use a database table engine type that does not support foreign keys (such as MyISAM).
Change your engine type to InnoDB, then run the following code to update your database schema.
php console doctrine:schema:update
Add cascade to the ManyToOne definition:
class BannerFile
{
/.../
/**
* #var integer $banner
*
* #ORM\ManyToOne(fetch="EXTRA_LAZY", inversedBy="bannerFiles", targetEntity="Banner", cascade={"remove"})
* #ORM\JoinColumn(name="banner_id", nullable=false, onDelete="CASCADE", referencedColumnName="id")
*/
private $banner;
/.../
}

Symfony2 Doctrine2 trouble with optional one to one relation

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.

Many-to-Many, Doctrine's Entity Generator and Pluralization

Doctrine's many-to-many logic is confusing me a bit. I have a pretty simple many-to-many relationship of recipes to categories. My base entity classes are equally simple.
The Recipe entity class...
class Recipe
{
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="categories")
* #ORM\JoinTable(name="recipe_category")
**/
private $categories;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255)
*/
private $title;
public function __construct() {
$this->categories = new \Doctrine\Common\Collections\ArrayCollection();
}
}
And the Category entity class...
class Category
{
/**
* #ORM\ManyToMany(targetEntity="Recipe")
**/
private $recipes;
/**
* #var integer
*
* #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;
public function __construct() {
$this->recipes = new \Doctrine\Common\Collections\ArrayCollection();
}
}
Seems pretty strait forward and matches Doctrine (and Symfony2's) documentation examples. The strange behavior comes when I try and generate the getters and setters for these classes via the Symfony console app.
The relationship setters/getters are incorrect. Take, for instance, the Category setter in the Recipe class that's generated...
/**
* Add categories
*
* #param \Namespace\CookbookBundle\Entity\Category $categories
* #return Recipe
*/
public function addCategorie(\Namespace\CookbookBundle\Entity\Category $categories)
{
$this->categories[] = $categories;
return $this;
}
It looks like the auto-generation of the method name is off. It should be "addCategory" and should be passed a "category."
While I can just correct this manually, if I re-run the entity generator, it will just add them again.
Am I doing this incorrectly or is this just a quirk of the entity generator? Can I specify an over-ride via annotation?
You're not doing anything wrong as that's how symfony generates them. I usually don't use the app/console to generate them as currently they're not doing a good job. One example is as you've mentioned the pluralization of words as you've mentioned. Another obvious one is the fact that it's using the [] notation which is pretty much treating an ArrayCollection object as a PHP array. You should never treat ArrayCollections as arrays.
This is how I have implemented it myself:
public function addCategory(Category $category)
{
if (!$this->categories->contains($category)
$this->categories->add($category);
return $this;
}
Which doesn't add duplicates to the Array collection if it's already added. Same thing goes with remove:
public function removeCategory(Category $category)
{
if ($this->categories->contains($category)
$this->categories->remove($category);
}
What I've run into many times is let's say you have 4 categories and you add and remove them
$r = new Recipe();
$c1 = new Category();
$c2 = new Category();
$r->addCategory($c1);
$r->addCategory($c2);
// at this point $r->getCategories()->toArray()[0] contains $c1
// and $r->getCategories()->toArray()[1] contains $c2
$r->removeCategory($c1);
// now $r->getCategories()->toArray()[0] is empty and
// $r->getCategories()->toArray()[1] contains $c2 still
// so in order to get the first category you need to:
$r->getCategories()->first();
You are not doing anything wrong. It is just that Doctrine automatically tries to singularize the names of method stubs whenever there is a plural name for a collection property. This is the function that Doctrine calls when you run the command doctrine:generate:entities:
$methodName = Inflector::singularize($methodName);
In your case, Doctrine tries to 'singularize' the word categories but fails to recognize the singular form correctly, so it just removes an 's' from the end returning categorie.
Also, as you see, Doctrine does not singularize the parameter passed to the method stubs, leaving $categories instead of being consistent and modifying it to $categorie.
If you want to avoid this, either you do not use plural words for collections, or use plural words and change the methods afterwards. As #keyboardSmasher comments to your post, doctrine won't overwrite methods you already have when using the command doctrine:generate:entities, and wrong methods won't hurt much if left there alone.
A final note: using ArrayCollections as arrays is perfectly fine, so this code is correct:
$this->categories[] = $category;
ArrayCollection object implements Collection, which in turn implements ArrayAccess. It is done this way precisely to be able to use ArrayCollections as Arrays.

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

Resources