doctrine2: Entity's association changes after first call - php

i am using the latest version of doctrine: 2.3
when you call a generated association function, the first time everything is fine:
$authors = $book->getBookToAuthors();
//$authors = array(5)
but the second time instead of returning the array of all associations it returns the last hydrated entity:
$authors = $book->getBookToAuthors();
//$authors = BookToAuthor entity
that happens even when there is nothing else happening:
$authors = $book->getBookToAuthors(); //will work
$authors = $book->getBookToAuthors(); //won't work
the function of getBookToAuthors() is:
public function getBookToAuthors()
{
return $this->bookToAuthors;
}
and the mapping is as follows:
/**
* #var BookToAuthor[]
*
* #OneToMany(targetEntity="BookToAuthor", mappedBy="book", cascade={"persist"})
* #JoinColumn(name="id", referencedColumnName="book_id", onDelete="cascade")
*/
private $bookToAuthors;
please advise. i don't know what to do... :-(

sorry sorry sorry
it was a mistake in the association target side.
the target had One-To-One association instead of Many-To-One
if you have this problem make sure the association type in both sides is matching

Related

How to be warned a many-to-many relation gonna be update?

I want to do a specific treatment when a specific field is updated.
The obvious way is to do it with event preUpdate, and see what fields are updated. It works fine ... except for a many-to-many field. It triggers the event, but the ChangeSet is empty.
/**
* #ORM\PreUpdate
*/
public function updateDate(PreUpdateEventArgs $event){
$changeSet = $event->getEntityChangeSet();
$res = "";
foreach($changeSet as $key => $change){
$line = $key." : ".$event->getOldValue($key)." || ".$event->getNewValue($key);
$res .= $line;
}
}
In $res all my fields are modified except for the many-to-many field.
Also, I'm trying to do it in a listener, but I can't find how to extract the fields which are updated from the entityManager.
Thank you.
More informations :
Relation from the update entity :
/**
* #var Status
*
* #ORM\ManyToMany(targetEntity="User", inversedBy="projectsSupervisor", cascade={"persist"})
* #ORM\JoinTable(name="projects_supervisors")
*/
protected $supervisors;
From the other side :
/**
* #var Project
*
* #ORM\ManyToMany(targetEntity="Task", mappedBy="users")
*/
protected *tasks
Symfony version : 3.1.10
It is not possible to track changes made to an many-to-many-association. See here :
Changes made only to the inverse side of an association are ignored.
Make sure to update both sides of a bidirectional association (or at
least the owning side, from Doctrine’s point of view)
Additionally, ::getEntityChangeSet() is only useful for regular fields, not associations. For One-To-Many-Associations, you can use $unitOfWork->getScheduledCollectionUpdates() :
foreach ($uow->getScheduledCollectionUpdates() as $collectionUpdate) {
/** #var $collectionUpdate \Doctrine\ORM\PersistentCollection */
if ($collectionUpdate->getOwner() === $entity) {
// This entity has an association mapping which contains updates.
$collectionMapping = $collectionUpdate->getMapping();
print_r($collectionMapping); // Investigate this further
}
}
A practical example is viewable in my github repository "DoctrineWatcher" which does exactly the same (line 196+).

Doctrine: Custom repository to determine UniqueEntity does not work

In order to solve a problem I asked about earlier, I am trying to create a custom repository function that will determine whether an instance of Repair is unique, based on the device, name, and colors constraints.
Here's my Doctrine Annotation for class Repair. Mind that the device property is Many To One (many Repairs for one Device), and that colors is Many to Many.
/**
* #ORM\Table(name="repair")
* #ORM\Entity(repositoryClass="AppBundle\Repository\RepairRepository")
* #UniqueEntity(fields={"name", "device", "colors"}, repositoryMethod="getSimilarRepairs", message="Repair {{ value }} already exists for this name, device and colour combination.")
*/
This is my RepairRepository.php, in which $criteria['colors'] is an array.
public function getSimilarRepairs(array $criteria) {
$builder = $this->createQueryBuilder('r')
->where('r.device = :device')
->andWhere('r.colors = :colors')
->andWhere('r.name = :name')
->setParameters(['deviceid'=>$criteria['device'],'colors'=>$criteria['colors'],'name'=>$criteria['name']]);
return $builder->getQuery();
}
I have three problems that can probably be brought back to one:
editing: with every change, causing a duplicate or not, I get the message that a duplicate entity exists.
editing: despite the error message, name changes are performed anyway!
adding: I can create as many duplicates as I like, there never is an error message.
Your problem is that the colors relation is a ManyToMany.
In SQL you can not query '=' on this relation.
It is very complicated, that's why Doctrine (and we probably) can't make it alone .
A partial solution to build a query :
public function getSimilarRepairs(array $criteria) {
$builder = $this->createQueryBuilder('r')
->where('r.device = :device')
->andWhere('r.name = :name')->setParameter('name',$criteria['name'])
->andWhere('r.colors = :colors')->setParameter('deviceid',$criteria['device']);
// r matches only if each of your colors exists are related to r :
$i=0;
foreach($criteria['colors'] as $color){
$i++;
$builder->join('r.colors','c'.$i)->andWhere('c = :color'.$i)->setParameter('color'.$i,$color);
}
// Then you had also to check than there is no other color related to r :
// I don't know how
return $builder->getQuery();
}
But let me propose another solution :
In your repair entity, your can store a duplicate of your related colours :
/**
* #var string
*
* #ORM\Column(name="name_canonical", type="string")
*/
private $serializedColors;
set it with doctrine lifecycle events :
/**
* #ORM\PrePersist
* #ORM\PreUpdate
*/
public function updateColors()
{
$serializedColors = '';
foreach($this->colors as $color){
$serializedColors .= $color->getId().'#';
}
$this->serializedColors = $serializedColors;
}
Don't forget to add #HasLifecycleCallbacks
Then change your UniqueEntityConstraint to fields={"name", "device", "serializedColors"}, forget the custom query, and it will work.

Doctrine2 join column

I have defined the follow entity in doctrine2 (with symfony).
/**
*
* #ORM\Table(name="order")
* #ORM\Entity
*/
class Order
/**
* #var integer
*
* #ORM\Column(name="personid", type="integer", nullable=false)
*/
private $personid;
/**
* #ORM\OneToOne(targetEntity="People")
* #ORM\JoinColumn(name="personid", referencedColumnName="personid")
*/
private $person;
public function getPersonId()
{
return $this->personid;
}
public function getPerson()
{
return $this->person;
}
}
I realize that if I call $order->getPersonId() it return always an empty value and I have to call the getPerson()->getId() method to get the correct personid.
Could anyone explain me why the variable $personid is not filled?
Should I to delete the column id used for the join if I defined one?
Thanks
Gisella
You should remove private $personid;, it's better to work with objects only in an ORM.
It's not a problem if you get the ID with $order->getPerson()->getId(), because Doctrine won't load the complete entity. The People entity will only be loaded if you call an other field than the join key.
You can still have a getter shortcut like this :
public function getPersonId()
{
return $this->getPerson()->getId();
}
Edit :
You can also still work with "ID" if you use Doctrine references, like this :
$order->setPerson($em->getReference('YourBundle:People', $personId));
With this way, Doctrine won't perform a SELECT query to load data of the person.
You don't need to have the $personid field when you already have the $person field.
$people contains the People object (with all People's attributes including the id).
Moreover, when doctrine translate your object into sql tables, he knows that he have to join with th id so it will create a field (in database) named personid. (It's the name that you defined in your ORM)
/**
* #ORM\OneToOne(targetEntity="People")
* #ORM\JoinColumn(name="personid", referencedColumnName="personid")
*/
private $person;
Sorry for bad english :p

Symfony Doctrine One to Many does not insert foreign key

I am having annoying problems with persisting an entity with one or more OneToMany-Childs.
I have a "Buchung" entity which can have multiple "Einsatztage" (could be translated to an event with many days)
In the "Buchung entity I have
/**
* #param \Doctrine\Common\Collections\Collection $property
* #ORM\OneToMany(targetEntity="Einsatztag", mappedBy="buchung", cascade={"all"})
*/
private $einsatztage;
$einsatztage is set to an ArrayCollection() in the __constructor().
Then there is the "Einsatztag" Entity which has a $Buchung_id variable to reference the "Buchung"
/**
* #ORM\ManyToOne(targetEntity="Buchung", inversedBy="einsatztage", cascade={"all"})
* #ORM\JoinColumn(name="buchung_id", referencedColumnName="id")
*/
private $Buchung_id;
Now If I try to persist an object to the database the foreign key of the "Einsatztag" Table is always left empty.
$buchung = new Buchung();
$buchung->setEvent( $r->request->get("event_basis"));
$buchung->setStartDate(new \DateTime($r->request->get("date_from")));
$buchung->setEndDate(new \DateTime($r->request->get("date_to")));
$von = $r->request->get("einsatz_von");
$bis = $r->request->get("einsatz_bis");
$i = 0;
foreach($von as $tag){
$einsatztag = new Einsatztag();
$einsatztag->setNum($i);
$einsatztag->setVon($von[$i]);
$einsatztag->setBis($bis[$i]);
$buchung->addEinsatztage($einsatztag);
$i++;
}
$em = $this->getDoctrine()->getManager();
$em->persist($buchung);
foreach($buchung->getEinsatztage() as $e){
$em->persist($e);
}
$em->flush();
Firstly, you have to understand that Doctrine and Symfony does not work with id's within your entities.In Einsatztag entity, your property should not be called $Buchung_id since it's an instance of buchung and not an id you will find out there.
Moreover, in your loop, you add the Einsatztag to Buchung. But do you process the reverse set ?
I do it this way to always reverse the set/add of entities.
Einsatztag
public function setBuchung(Buchung $pBuchung, $recurs = true){
$this->buchung = $pBuchung;
if($recurs){
$buchung->addEinsatztag($this, false);
}
}
Buchung
public function addEinsatztag(Einsatztag $pEinsatztag, $recurs = true){
$this->einsatztages[] = $pEinsatztag;
if($recurs){
$pEinsatztag->setBuchung($this, false);
}
}
Then, when you will call
$buchung->addEinsatztag($einsatztag);
Or
$einsatztag->set($buchung);
The relation will be set on both side making your FK to be set. Take care of this, you'll have some behavior like double entries if you do not use them properly.
SImplier , you can use default getter/setters and call them on both sides of your relation, using what you already have, like following:
$einsatztag->set($buchung);
$buchung->addEinsatztag($einsatztag);
Hope it helped ;)
First of all, don't use _id properties in your code. Let it be $buchung. If you want it in the database, do it in the annotation. And this also the reason, why it's not working. Your are mapping to buchung, but your property is $Buchung_id
<?php
/** #ORM\Entity **/
class Buchung
{
// ...
/**
* #ORM\OneToMany(targetEntity="Einsatztag", mappedBy="buchung")
**/
private $einsatztage;
// ...
}
/** #ORM\Entity **/
class Einsatztag
{
// ...
/**
* #ORM\ManyToOne(targetEntity="Product", inversedBy="einsatztage")
* #JoinColumn(name="buchung_id", referencedColumnName="id")
**/
private $buchung;
// ...
}
You don't have to write the #JoinColumn, because <propertyname>_id would the default column name.
I'm going to ignore the naming issue and add a fix to the actual problem.
You need to have in the adder method a call to set the owner.
//Buchung entity
public function addEinsatztage($einsatztag)
{
$this->einsatztags->add($einsatztag);
$ein->setBuchung($this);
}
And to have this adder called when the form is submitted you need to add to the form collection field the by_reference property set to false.
Here is the documentation:
Similarly, if you're using the CollectionType field where your underlying collection data is an object (like with Doctrine's ArrayCollection), then by_reference must be set to false if you need the adder and remover (e.g. addAuthor() and removeAuthor()) to be called.
http://symfony.com/doc/current/reference/forms/types/collection.html#by-reference

Symfony2/Doctrine joined query

I need to create a simple query that produces a result set of a database entry plus the username of the person that posted it.
I've tried to setup the associations properly but I'm not sure if that's right either. I'm finding the whole idea of using these small string identifiers quite confusing. Surely there must be a simpler way of doing a join?
My two entities:
class Users
{
// ...
/**
* #ORM\Column(type="string")
* #ORM\OneToMany(targetEntity="Titles", mappedBy="addedBy")
*/
protected $username;
// ..
}
and
class Titles
{
// ....
/**
* #ORM\Column(type="string")
* #ORM\ManyToOne(targetEntity="Users", inversedBy="username")
*/
protected $addedBy;
// ....
}
with the following in the controller:
$titles = $em->createQueryBuilder()
->select('t.*', 'u.*')
->from('dvdLoggerdvdBundle:Titles', 't')
->leftJoin('t.addedBy', 'u')
->addOrderBy('t.title', 'DESC')
->getQuery()
->getResult();
I'm getting the following error:
[Semantical Error] line 0, col 69 near 'u ORDER BY t.title': Error: Class
dvdLogger\dvdBundle\Entity\Titles has no association named addedBy `
Update 1
I made all the changes suggested by Tom and did lots of reading!
It appears that in order to overcome the lazy loading feature I need to carry out a leftJoin. I have rewritten my query as follows:
public function getAllTitles()
{
// view all records in db
$titles = $this->createQueryBuilder('t')
->select('t, u')
->leftJoin('t.addedBy', 'u')
->addOrderBy('t.title', 'DESC');
return $titles->getQuery()->getResult();
}
I am getting a result set, but the addedBy is returning NULL when I dump the result set. As far as I'm aware shouldn't this pull the associated field in from the other table?
Best practice is to reference the entity by its id, you are trying to reference it using the username. The inversed field should also be a specific field not an existing one that holds data. And keep it mind this field is optional and defines the associations as bidirectional, for the specified use case you don't actually need it as you are joining from the Titles entity. I would advice reading the doc here http://symfony.com/doc/current/book/doctrine.html#entity-relationships-associations as well as here http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html
Bidirectional association (w/ inversed field)
First get rid of that line:
#ORM\Column(type="string")
In your $addedBy annotations and change inverseBy="username" to inversedBy="titles" (note the typo)
You optionaly could add
#ORM\JoinColumn(name="user_id", referencedColumnName="id")
Then in your Users Entity add
/**
*
* #ORM\OneToMany(targetEntity="Titles", mappedBy="addedBy")
*/
protected $titles;
And get rid of
* #ORM\OneToMany(targetEntity="Titles", mappedBy="addedBy")
In your $username annotations
Last make sure you update the database schema
Then your query should return the expected result.
Unidirectional association (w/out inversed field)
Get rid of
* #ORM\OneToMany(targetEntity="Titles", mappedBy="addedBy")
In your $username annotations
Then get rid of that line in your $addedBy annotations:
#ORM\Column(type="string")
As well as inverseBy="username"
You optionaly could add
#ORM\JoinColumn(name="user_id", referencedColumnName="id")
Last make sure you update the database schema
Then your query should return the expected result.

Categories