Reference ID not updated with Doctrine association - php

There is probably something I did understand with Doctrine association.
I have a first class :
class FitComments
{
/**
* #var integer
*
* #ORM\Column(name="ID", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="SEQUENCE")
* #ORM\SequenceGenerator(sequenceName="SEQ_FIT_COMMENTS", allocationSize=1, initialValue=1)
*/
private $id;
/**
*
* #ORM\OneToMany(targetEntity="FitCommentsId",mappedBy="comments",cascade={"persist"})
*
*/
private $elements;
/****/
public function __construct()
{
$this->elements=new ArrayCollection();
}
public function getElements()
{
return $this->elements;
}
....
}
And a another Class, the list of elements ID that the comments is link.
/**
* FitCommentsId
* #ORM\Entity
*/
class FitCommentsId
{
/**
* #var integer
*
* #ORM\Column(name="ID", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="SEQUENCE")
* #ORM\SequenceGenerator(sequenceName="SEQ_FIT_COMMENTS_ID", allocationSize=1, initialValue=1)
*/
private $id;
/**
*
* #ORM\ManyToOne(targetEntity="FitComments",inversedBy="elements")
* #ORM\JoinColumn(name="COMMENTS_ID",referencedColumnName="ID")
*/
private $comments;
....
}
I use :
$comments=new FitComment();
$commentId=new FitCommentId();
....
$comments->getElements()->add($commentId);
....
$entityManager->persist($comment);
$entityManager->flush();
But I have a error. $commentId->comments is null. It must be filled with $comment->id normally.
If I must filled manually $commentId->comments, association is not very usefull.
Perhaps I don't understand mechanism.
Note : the SGDB is Oracle.

Try persisting the $commentId also like this:
$comment = new FitComment();
$commentId = new FitCommentId();
....
$comment->getElements()->add($commentId);
....
$entityManager->persist($commentId);
$entityManager->persist($comment);
$entityManager->flush();

No I can't do a 'persist' to $commentId first, because 'persist' to $comment class initiate Oracle sequence for the Id of $comments. (I'm not sure it's very clean what I say....)
'commentID->comments' is link to 'comment->id', and is not null.
I must create $comment at first, and after, create the $commentId.
I know that Doctrine use Sequence before saving record, in persist command. Perhaps I can do a persist without flush at first, and at the end of $commentId recording, do a flush.

Related

Delete link between two table with a query builder

I have made a queryBuilder inside an entity repository to delete a link between two table.
I have this two entities
Domain :
/**
* #var int
*
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(type="string", length=64)
* #Assert\NotBlank
* #Assert\Length(max="64")
* #AppAssert\DomainName
*/
private $name;
// Some other fields
/**
* #var SshKey[]|ArrayCollection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\SshKey", inversedBy="domains")
* #ORM\JoinTable(name="domain_sshkey",
* joinColumns={#ORM\JoinColumn(referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="key_id", referencedColumnName="id")}
* )
*/
private $sshKeys;
And SshKeys :
/**
* #var int
*
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var \DateTime
*
* #Gedmo\Timestampable(on="create")
* #ORM\Column(type="datetime")
*/
private $createdAt;
// Other fields
/**
* #var Domain[]|ArrayCollection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Domain", mappedBy="sshKeys")
*/
private $domains;
I am trying to delete links between this two tables when SshKeys id is in sshKeys field inside domain table.
So I made this query builder in my DomainRepository
public function deleteSshkeyDomainLink($invalidSshkey)
{
$qb = $this->createQueryBuilder('d');
$qb->delete()
->where($qb->expr()->in('ssh.id', ':ssh_keys_id'))
->setParameter('ssh_keys_id', $invalidSshkey)
->join('d.sshKeys', 'ssh')
;
return $qb->getQuery()->execute();
}
But this QB return this error
[Doctrine\ORM\Query\QueryException]
[Semantical Error] line 0, col 39 near 'ssh.id IN(:s': Error: 'ssh' is not defined.
[Doctrine\ORM\Query\QueryException]
DELETE AppBundle\Entity\Domain d WHERE ssh.id IN(:ssh_keys_id)
I don't understand why this is returning ssh is not defined because I have made a join with this alias.
This query builder should work ? I really don't know how too fix this.
Thanks for your help.
why do you want to delete Domain when you just need to delete sshKey from Domain (link between them)?
In Domain entity you can define method removeSshKey like this for example
public function removeSshKey(SshKey $key)
{
$this->sshKeys->removeElement($key);
return $this;
}
Then in controller where you want to delete the link between entities you should call it something like this
$domain = $this->getDoctrine()->getRepository('Domain')->find($domainId);
foreach ($domain->getSshKeys() as $sshKey)
{
if ($sshKey->getId() == $invalidSshKeyId)
{
$domain->removeSshKey($sshKey);
}
}
$em = $this->getDoctrine()->getManager();
$em->flush();
this should delete the link

How can I reference an existing entry to a new entity in Doctrine 2?

I working on the following Models:
User
Category
One User can only get one Category. Categories are "standalone". So I can update, create and delete (okay, maybe with cascading) Categories whenever I want.
When I create a new User, I want do reference one Category to the User.
How can I do so? I want to avoid bad practices.
User entity:
/**
* User
*
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="XXX\Repository\UserRepository")
*/
class User
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="user")
* #ORM\JoinColumn(name="category", referencedColumnName="id")
*/
private $category;
//...
}
Category entity:
/**
* Category
*
* #ORM\Table(name="category")
* #ORM\Entity(repositoryClass="XXX\Repository\CategoryRepository")
*/
class Category
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="User", mappedBy="category")
*/
private $user;
//...
}
You write a User can have one Category, but it seems from your description that a Category can be used by several users. If this is true then your category definition should be changed a bit:
/**
* Category
*
* #ORM\Table(name="category")
* #ORM\Entity(repositoryClass="XXX\Repository\CategoryRepository")
*/
class Category
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var Collection
* #ORM\OneToMany(targetEntity="User", mappedBy="category")
*/
private $users;
public function __construct()
{
// It is important to initialize your collection
$this->users = new ArrayCollection();
}
// add methods for adding/removing and getting users:
// addUser, removeUser, addUsers, removeUsers and getUsers method
}
Read more on initializing collections in the doctrine documentation chapter 27.7. Initialize collections in the constructor.
Then in your User class you will need methods for setting and getting category.
public function setCategory(Category $category)
{
$category->addUser($user);
$this->category = $category;
}
public function getCategory()
{
return $this->category;
}
Now you can do:
$category = // get the category you want to set from entity manager
$user = new User();
$user->setCategory($category);
$entityManager->persist($user);
$entityManager->flush();
Now both your user and category are correctly set.
This is just a basic example that will help you get on your way.

Symfony2 doctrine exception in persisting OneToMany entities

I configure this entities:
MarketMain:
class MarketMain
{
/**
* #var integer
*
* #ORM\Column(name="id", type="bigint", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\OneToMany(targetEntity="\Acme\CMSBundle\Entity\MarketLanguage", mappedBy="marketMain", indexBy="langId", cascade="all", orphanRemoval=true, fetch="EXTRA_LAZY")
*/
private $marketLanguage;
}
MarketLanguage:
class MarketLanguage
{
/**
* #var \Acme\CMSBundle\Entity\MarketMain
* #ORM\Id
* #ORM\ManyToOne(targetEntity="\Acme\CMSBundle\Entity\MarketMain", inversedBy="marketLanguage")
* #ORM\JoinColumn(name="market_id", referencedColumnName="id")
*/
private $marketMain;
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(name="lang_id", type="integer", nullable=false)
*/
private $langId = 1;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=200, nullable=true)
*/
private $name;
}
And I want to save entity like this:
...........
$form = $this->createForm(new MarketMainType(), new MarketMain());
$form->handleRequest($request);
$marketFormData = $form->getData();
$em->persist($marketFormData);
$em->flush($marketFormData);
foreach ($marketFormData->getMarketLanguage() as $market_language)
{
$market_language->setName("My market name");
$market_language->setMarketMain($marketFormData);
$em->persist($market_language);
}
$em->flush();
Than I get this error:
Entity of type Acme\CMSBundle\Entity\MarketLanguage is missing an
assigned ID for field 'marketMain'. 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.
If I trying to do $marketFormData persist after foreach statment I get this error:
Entity of type Acme\CMSBundle\Entity\MarketLanguage has identity
through a foreign entity Acme\CMSBundle\Entity\MarketMain, however
this entity has no identity itself. You have to call
EntityManager#persist() on the related entity and make sure that an
identifier was generated before trying to persist
'Acme\CMSBundle\Entity\MarketLanguage'. In case of Post Insert ID
Generation (such as MySQL Auto-Increment or PostgreSQL SERIAL) this
means you have to call EntityManager#flush() between both persist
operations.
I know that If I try to persist $marketFormData before loop doctrine does not know the releated $marketLanguage references, but if I set persist after the foreach it says taht I have first persist parent entity. So I tried this code and it worked:
...........
$form = $this->createForm(new MarketMainType(), new MarketMain());
$form->handleRequest($request);
$marketFormData = $form->getData();
$market_languages = $marketFormData->getMarketLanguage();
$marketFormData->setMarketLanguage(null);
$em->persist($marketFormData);
$em->flush($marketFormData);
$marketFormData->setMarketLanguage($market_languages);
foreach ($marketFormData->getMarketLanguage() as $market_language)
{
$market_language->setName("My market name");
$market_language->setMarketMain($marketFormData);
$em->persist($market_language);
}
$em->flush();
But it is only way to persist related entities? To clone it set to null, persist parent entity, and then set it back, add references and flush all. I think I have missed something here.
I think that your entities is mapped wrong. The entity must have an annotation about ID and another to relation.
And also, when you don't have a primary key with autoincrement, it is necessary declare the class constructor, passing both values as mentioned in http://doctrine-orm.readthedocs.org/en/latest/tutorials/composite-primary-keys.html
It should look like this:
class MarketLanguage
{
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(name="market_id", type="integer", nullable=false)
*/
private $marketId;
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(name="lang_id", type="integer", nullable=false)
*/
private $langId = 1;
/**
* #var \Acme\CMSBundle\Entity\MarketMain
*
* #ORM\ManyToOne(targetEntity="\Acme\CMSBundle\Entity\MarketMain", inversedBy="marketLanguage")
* #ORM\JoinColumn(name="market_id", referencedColumnName="id")
*/
private $marketMain;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=200, nullable=true)
*/
private $name;
public function __construct($marketId, $langId) {
$this->marketId = $marketId;
$this->langId = $langId;
}
}
Did you try remove flush($marketFormData) ?:
$form = $this->createForm(new MarketMainType(),$marketMain);
$form->handleRequest($request);
$marketFormData = $form->getData();
$em->persist($marketFormData);
// $em->flush($marketFormData); // remove that flush
foreach ($marketFormData->getMarketLanguage() as $market_language)
{
$market_language->setName("My market name");
$market_language->setMarketMain($marketMain);
$em->persist($market_language);
}
$em->flush();
maybe problem is that you are trying flush $marketFormData which contains MarketLanguages which are not persised ? Not sure am i right, didn't tested this.
EDIT maybe this work:
$form = $this->createForm(new MarketMainType(), new MarketMain());
$form->handleRequest($request);
$marketFormData = $form->getData();
foreach ($marketFormData->getMarketLanguage() as $market_language)
{
$market_language->setName("My market name");
$market_language->setMarketMain($marketMain);
}
$em->persist($marketFormData);
$em->flush($marketFormData);

Doctrine2 One to One Relation and extra table

I have 3 tables:
users, users_profile and extra table users_addresses.
User class:
class User
{
/**
* #ORM\Id()
* #ORM\Column(name="user_id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
* #var int
*/
private $userId;
/**
* #ORM\OneToOne(targetEntity="Entity\UserProfile", cascade={"persist", "remove"}, mappedBy="user", fetch="LAZY")
* #var Entity\UserProfile
*/
private $profile;
/**
* #param Entity\UserProfile $profile
*/
public function setProfile(UserProfile $profile) {
$this->profile = $profile;
$profile->setUser($this);
return $this;
}
(...)
}
User profile class
class UserProfile
{
/**
* #ORM\Id()
* #ORM\Column(name="profile_id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
* #var int
*/
private $profileId;
/**
* #ORM\OneToOne(targetEntity="Entity\User", inversedBy="profile")
* #ORM\JoinColumn(name="user_id", referencedColumnName="user_id")
* #var Entity\User
*/
private $user;
/**
* #ORM\ManyToMany(targetEntity="Entity\Address", cascade={"persist"})
* #ORM\JoinTable(name="users_addresses",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="user_id")},
* inverseJoinColumns={#ORM\JoinColumn(name="address_id", referencedColumnName="address_id")}
* )
**/
private $addresses;
/**
* #param Address $address
*/
public function addAddress(Address $address) {
if (!$this->addresses->contains($address)) {
$this->addresses->add($address);
}
return $this;
}
(...)
}
Address class
class Address
{
/**
* #ORM\Id()
* #ORM\Column(name="address_id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
* #var int
*/
private $addressId;
/**
* #ORM\Column(type="string", length=128, nullable=false)
* #var string
*/
private $city;
(...)
}
Add user with profile data and address
$userAddress = new \Entity\Address();
$userAddress->setStreet($street);
$userAddress->setCity($city);
$userAddress->setCountry($country);
$userAddress->setState($state);
$userAddress->setZipCode($zipCode);
$userProfile = new \Entity\UserProfile();
$userProfile->addAddress($userAddress);
$user = new \Entity\User();
$user->setProfile($userProfile);
$em->persist($user);
$em->flush();
The problem i am having at the moment is that the entities persist ok, but the 'user_id' in the join table ('users_addresses') is not correct ('user_id' = 0, 'address_id' = correct). Table 'addresses' must not contain extra field like 'user_id'. What am I doing wrong?
I need a structure like this:
Your modeling shows that tables users and users_profile share the same primary key, namely user_id, and thus that column is also a foreign key toward users in users_profile.
Hence, you should replace profile_id in the Php annotations by user_id, remove the key auto generation directive, and update (or delete and recreate) the schema.
class UserProfile {
/**
* #ORM\Id
* #ORM\Column(name="user_id", type="integer")
* #var int
*/
private $profileId;
/* ... */
}
Then
$ doctrine orm:info --verbose
Found 3 mapped entities:
[OK] Address
[OK] User
[OK] UserProfile
$ doctrine orm:schema-tool:create
ATTENTION: This operation should not be executed in a production environmnent.
Creating database schema...
Database schema created successfully!
Or use the update command if you already have a schema.
You could also rename the php class attribute ($profileId), for "consistency" with the DB model, but I don't think it matters much, unless you have code which relies on a specific name.

How to add extra WHERE clauses when retrieving relationships in Doctrine2

I have two entities Post and Comment.
Structure:
Post:
id
title
body
Comment:
id
post_id
body
active
class Post
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="title", type="string", length=255)
*/
private $title;
/**
* #ORM\Column(name="body", type="text")
*/
private $body;
/**
* #ORM\OneToMany(
* targetEntity="Comment",
* mappedBy="post"
* )
*/
private $comments;
class Comment
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="body", type="text")
*/
private $body;
/**
* #ORM\ManyToOne(
* targetEntity="Post",
* inversedBy="comments"
* )
* #ORM\JoinColumn(
* name="post_id",
* referencedColumnName="id"
* )
*/
private $post;
As a result when I want to get all comments for a post I use $post->getComments() and it works.
How I can add extra Where clauses into this relationship if I want to get only posts with active = 1.
I know that I can do it by DQL or queryBuilder but I want to know how I can do it by mapping
I think the cleanest way to retrieve only active comments is to use Doctrine's Criteria object in the getComments method of your Post entity
use Doctrine\Common\Collections\Criteria;
and
public function getComments()
{
$criteria = Criteria::create();
$criteria->where(Criteria::expr()->eq('active', 1));
return $this->comments->matching($criteria);
}
Edit
If you want to prevent multiple queries each time you retrieve the active comments, you'll need to store them in a local variable. Instead of modifying getComments, you could add $active_comments and getActiveComments, which will populate $active_comments and only query the db if $active_comments is false.
class Post {
private $active_comments;
public function getActiveComments()
{
if(!$this->active_comments) {
$criteria = Criteria::create();
$criteria->where(Criteria::expr()->eq('active', 1));
$this->active_comments = $this->comments->matching($criteria);
}
return $this->active_comments;
}

Categories