Doctrine 2 entity caching while persisting - php

I am having an issue with Doctrine 2 and can not find a solution for it. It seems that when persisting an entity (and flushing it). When trying to search for it in DB using the assigned id while within the same PHP process doctrine is giving me the cached version of the entity instead of building a proper one with all correct relations. My problem with that is that my entity has some Many to many or many to one connections and if i get the the cached version all those properties come back as null instead of an instance of \Doctrine\Common\Collections\Collection.
Is there any way I can force doctrine to generate or update my entity to assign all those connections, or at least force it to serch for that entity insead of giving it to me from cache?
As requested examples:
class A extends AbstractDoctrineEntity {
/**
* #var int
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #var \Core\Entity\B
* #ORM\ManyToOne(targetEntity="Core\Entity\B")
*/
protected $relationB;
/**
* #var \Doctrine\Common\Collections\Collection
* #ORM\ManyToMany(targetEntity="Core\Entity\C", inversedBy="relationA", fetch="EXTRA_LAZY")
*
*/
protected $relationC;
//getters and setters
}
class B extends AbstractDoctrineEntity {
/**
* #var int
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #var \Core\Entity\A
* #ORM\OneToMany(targetEntity="Core\Entity\A")
*/
protected $relationA;
//getters and setters
}
class B extends AbstractDoctrineEntity {
/**
* #var int
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #var \Core\Entity\A
* #ORM\ManyToMany(targetEntity="Core\Entity\A", mappedBy="realtionC", cascade={"persist", "remove")
*/
protected $relationA;
//getters and setters
}
// Service
class SomeService implements ServiceLocatorAwareInterface, MultiRepositoryServiceInterface {
use ServiceLocatorAwareTrait, MultiRepositoryServiceTrait;
public function createA(\Core\Entity\B $b) {
$a = new \Core\Entity\A();
$a->setB($b);
$this->getRepository('a')->persist($a); // gets entity manager persists entity $a and flushes
return $a;
/**
* returns an object
* a = {
* id = 2,
* relationB = Core\Entity\B,
* ralationC = null,
*/
}
}
public function getA($id){
$a = $this->getRepository('a')->persist($a); // retrieves object manager and queries by id;
return $a;
//**
* returns an object
* a = {
* id = 2,
* relationB = Core\Entity\B,
* ralationC = \Doctrine\Common\Collections\Collection, //which is an empty collection prototype and exactly what i need to retrieve in the first case when persisting the entity.
* }
*/
}
}
EDIT:
Found a solution, this may not be the nicest way to do this, so if someone knows a better way please update, but for now it works:
-----
public function createA(\Core\Entity\B $b) {
$a = new \Core\Entity\A();
$a->setB($b);
$this->getRepository('a')->persist($a); // gets entity manager persists entity $a and flushes
$this->getRepository('a')->detatch($a); // removes entity from management
return $this->getRepository('a')->find($a->getId());
/**
* returns an object
* a = {
* id = 2,
* relationB = Core\Entity\B,
* ralationC = \Doctrine\Common\Collections\Collection,
*/
}
}
----
This has a downside of re-querying after detachment and has a negative performance impact. If someone knows of a better solution it would be much appreciated.

Related

Inverse sides always returns an empty collection for a ManyToMany relationship

This is something I've done before, so I'm quite confused as to why it's not working.
I have two entities Question and Qresponse. Question is the owning side and Qresponse is the inverse side. When I use doctrine to find all Questions, the qresponses property is always empty.
//$questions is populated, but the qresponses property is always empty
$questions = $this->getDoctrine()->getManager()->getRepository(Question::class)->findAll();
Why is it empty ? What am I doing wrong?
Snippet of Owning side: Question
/**
* Class Question
* #package Entity
*
* #ORM\Entity
*/
class Question
{
public function __construct()
{
$this->qresponses = new ArrayCollection();
}
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/** #var ArrayCollection $responses
* #ORM\ManyToMany(targetEntity="Qresponse", mappedBy="questions", cascade={"persist"})
*/
private $qresponses;
}
Snippet of Inverse side: Qresponse
/**
* Class Response
* #package Entity
*
* #ORM\Entity
*/
class Qresponse
{
public function __construct()
{
$this->questions = new ArrayCollection();
}
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var ArrayCollection $question
* #ORM\ManyToMany(targetEntity="Question", inversedBy="qresponses", cascade={"persist"})
* #ORM\JoinTable(name="qresponse_question")
*/
private $questions;
}
Image of database that is populated.
Image from the profiler in symfony showing that qresponses is empty...
You are doing nothing wrong, it's just a typical doctrine hydration issue.
Doctrine by default uses lazy loading, that means associations are only loaded when needed (e.g. when $question->getQResponses()->first()->getId() is called). You can easily change it by setting doctrine fetch option to EAGER in your association:
/**
* #var ArrayCollection $responses
* #ORM\ManyToMany(targetEntity="Qresponse", mappedBy="questions", cascade={"persist"}, fetch="EAGER")
*/
private $qresponses;

Retrieve nested object with OneToMany/ManyToOne doctrine relationship

I want to do a one to many / many to one relationship between two DAO.
After annoting properties, I have an unexpected and unlimited object in the result.
/**
* TicketSponsorDAO
*
* #ORM\Table(name="ticket_sponsor")
* #ORM\Entity
*/
class TicketSponsorDAO {
/**
* #var int
*
* #ORM\Column(name="ticket_id", type="integer")
*/
private $ticketId;
/**
* #ORM\ManyToOne(targetEntity="TicketDAO", inversedBy="sponsors")
* #ORM\JoinColumn(name="ticket_id", referencedColumnName="id")
*/
private $ticket;
...
}
And
/**
* TicketDAO
*
* #ORM\Table(name="ticket")
* #ORM\Entity
*/
class TicketDAO
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="TicketSponsorDAO", mappedBy="ticket")
*/
private $sponsors;
public function __construct() {
$this->sponsors = new ArrayCollection();
}
...
}
When I execute:
$sponsorEm = $em->getRepository(TicketDAO::class);
$spo = $sponsorEm->find("2");
var_dump($spo);
I have good properties about the ticket DAO, but the relation doesn't work and I have an unlimited object which is returned.
So in the MySQL database, I have the foreign key, the FK index and the primary key which are here.
The var_dump:
I follow this documentation: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#one-to-many-bidirectional
Use symfony dumper as you are having a circular reference.
dump($spo);
Hi var_dump() will return your the type of objects and classes.
you should try to fetch the propertes like this
$spo->getSponsers();
it will return you an array or may be an collection;
For me it looks like one ticket have many sponsors and in this case I see only one problem. There is no auto_increment id in the TicketSponsorDao table, but there is a ticket_id column and I don't understand the purpose of that.
/**
* TicketSponsorDAO
*
* #ORM\Table(name="ticket_sponsor")
* #ORM\Entity
*/
class TicketSponsorDAO {
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
...
}
There is a recursion between the objets it's logic with OneToMany/ManyToOne relationship because the object of One is referred in the object of Many, ...
The var_dump doesn't manage it correctly.
To display the object I use dump from Symfony and it's good!
We can add a __toString with return serialize($this) into DAO class if the dump is displayed in browser.
Otherwise, we have an error:
... ENTITY could not be converted to string ...

Doctrine produces Integrity constraint violation 1451

As I cannot post the real code here, im using a substitute, that basically is the same, so please dont wonder if you find syntactic errors.
I have the following setup:
PHP 7.0,Symphony,Doctrine which is working with a MySQL database.
The classes are as following:
/*
* #ORM\Table(name="Postoffice")
* #ORM\Entity(repositoryClass="\AppBundle\Repsoitory\PostOfficeRepository")
*/
class Postoffice
{
/**
* Holds the Doctrine entity manager for database interaction
* #var EntityManager $em
*/
protected $em;
/**
* #var int
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(stragegy="AUTO")
*/
private $id;
/** #var ArrayCollection
* #ORM\ManyToMany(targetEntity="MailBox")
* #ORM\JoinTable(name="PostOfficeToMailBoxMapping",
* joinColumns={#ORM\JoinColumn(name="PostOfficeId",referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="MailBoxId",referencedColumnName="id",unique=true)})
*/
private $packets;
public function __construct(EntityManager $em)
{
$this->$packets = new ArrayCollection();
$this->em = $em;
}
}
/*
* #ORM\Table(name="PostStorage")
* #ORM\Entity(repositoryClass="\AppBundle\Repsoitory\PoststorageRepository")
*/
class Poststorage
{
/**
* Holds the Doctrine entity manager for database interaction
* #var EntityManager $em
*/
protected $em;
/**
* #var int
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(stragegy="AUTO")
*/
private $id;
/** #var ArrayCollection
* #ORM\ManyToMany(targetEntity="MailBox")
* #ORM\JoinTable(name="PostStorageToMailBoxesMapping",
* joinColumns={#ORM\JoinColumn(name="PoststorageId",referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="MailBoxId",referencedColumnName="id",unique=true)})
*/
private $MailBoxes;
public function __construct(EntityManager $em)
{
$this->MailBoxes = new ArrayCollection();
$this->em = $em;
}
public function delete()
{
//remove each box on its own
foreach ($this->Mailboxes as $iterMailBox)
$this->em->remove($iterMailBox);
$this->em->remove($this);
$this->em->flush();
}
}
/*
* #ORM\Table(name="MailBox")
* #ORM\Entity(repositoryClass="\AppBundle\Repsoitory\MailBoxRepository")
*/
class MailBox
{
/**
* #var int
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(stragegy="AUTO")
*/
private $id;
/** #var ArrayCollection
* #ORM\ManyToMany(targetEntity="Letter")
* #ORM\JoinTable(name="MailBoxToLetterMapping",
* joinColumns={#ORM\JoinColumn(name="MailBoxId",referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="LetterId",referencedColumnName="id",unique=true)})
*/
private $mailsInBox;
__construct()
{
$mailsInBox = new ArrayCollection();
}
}
class Letter
{
/**
* Holds the Doctrine entity manager for database interaction
* #var EntityManager $em
*/
protected $em;
/**
* #var int
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(stragegy="AUTO")
*/
private $id;
//lets assume this is a string
private $letterContent;
}
Now what im trying to model here is the following:
The Mailbox is somethinglike a container for a bunch of letters.
And a single mailbox can be at a certain point in its life only ba at one place at a time, and only at one. But the containers can be moved in between these two places. i.e. the Postoffice and the PostStorage.
The creation of these Mappings in the SQL database are not the Problem.
Letter N ------ 1 Mailbox (unidirectional with Join Tables)
MailBox N ------1 PostOffice or PostStorage (unidirectional with Join Tables)
The problem comes with the following feature. I want to be able to delete/merge/split individual containers and letters to and from containers and letters.
If im simply trying to delete a letter or Mailbox by using:
$em->remove($InstanceOfLetterOrMailBox);
$em->flush();
im getting the, in the title mentioned, "Integrity constraint violation".
As the Mailboxes can be at different places, i would really like to avoid to add refereces to the owner in the class, as it would bloat up the size of the class code very much, because i would like to add more places for the mailboxes to reside at in the future.
Ive spend the last few hours trying out different combinations of
Ondelete = CASCADE on different classes, but all I managed to do was either delete all classes from the db or not making a single delete, if im not getting the mentioned Error message back.
If you need more information let me know.
Thanks in advance.
I've found a solution to the problem. The problem was not that I was using the Cascade options incorrectly, but that i was trying to delete from an empty mailbox. In other words, I've been using a wrong index for my delete operation. The correct settings i am using now :
"cascade={"remove","persist"},orphanRemoval=true"
as a part of the many to many tag above the collection

Doctrine Filters: filtering only directly hydrated entities, not relation ones

How can I determine (within Doctrine filter) if entity is directly hydrated or was only joined or is fetched from other's entity relation?
I know how can it be filtered globally, this works (filter is enabled in Symfony's kernel.request listener):
Entities
/**
* Period
*
* #ORM\Table(name="period")
* #ORM\Entity(repositoryClass="Acme\DemoBundle\Entity\PeriodRepository")
*/
class Period {
/**
* #var integer
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Task", inversedBy="periods")
*/
private $task;
}
/**
* Task
*
* #ORM\Table(name = "task")
* #ORM\Entity(repositoryClass="Acme\DemoBundle\Entity\TaskRepository")
*/
class Task
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="type", type="integer")
*/
private $type;
/**
* #ORM\OneToMany(targetEntity="Period", mappedBy="task")
*/
private $periods;
}
Filter
namespace Acme\DemoBundle\Doctrine\Filter;
use Acme\DemoBundle\Entity\Task;
use Doctrine\ORM\Query\Filter\SQLFilter;
use Doctrine\ORM\Mapping\ClassMetadata;
/**
* DisableTasksFilter excludes tasks from Doctrine results.
*/
class DisableTasksFilter extends SQLFilter
{
const NAME = 'acme_disable_tasks';
/**
* #inheritdoc
*/
public function addFilterConstraint(ClassMetadata $entity, $alias)
{
// Ignore tasks with type = 1
if ($entity->getReflectionClass()->name === Task::class) {
return "$alias.type != 1";
}
return '';
}
}
BUT... (THE PROBLEM)
Filter should work only for direct fetching Task entities and SHOULD NOT filter Task relations in Period entities, nor joined entities in queries (it could break some logic).
In our app there are few places where Tasks are fetched:
TaskManager methods
TaskRepository used in TaskManager and directly
User->tasks relation (via helper methods which filter collection with ArrayCollection::filter())
Period->task relation
It would be much easier if it could be possible to filter it with globally registered filter instead of adding conditions to each place where Tasks are fetched.
Is it even possible?

Doctrine 2 many-to-many with MappedSuperclass in Zend framework 2

I am new to Doctrine2 and trying to create entities for the following DB structure:
I want to have all machine parts as an array in one attribute of the machine class. I tried this:
class Machine {
....
/**
* #var array
* #ORM\OneToMany(targetEntity="MachineHasPart", mappedBy="machine", cascade={"persist", "remove"}, orphanRemoval=TRUE)
*/
private $parts;
....
public function getParts () {
return array_map(
function ($machineHasPart) {
return $machineHasPart->getPart();
},
$this->parts->toArray()
);
}
}
Where MachineHasPart is a #MappedSuperclass for the intermediate entities/tables (like machineHasCylinder etc), but it failed with:
An exception occurred while executing 'SELECT FROM machineHasPart t0'.
Should I restructure my database to use ORM here? Or there is a solution for my case?
You cannot query a #MappedSuperClass. This is also mentioned in the Doctrine2 documentation in chapter 6.1. Mapped Superclasses:
A mapped superclass cannot be an entity, it is not query-able and persistent
This means you have to either change the target entity to something queryable or you have to make MachineHasPart to a entity and change to single table inheritance.
When I look at your database structure I would suggest changing your Machine entity to have three independent relationships for the parts. One for Belt, one for Cylinder and one for Gear.
Then instead of a generic getParts you will have three methods getBelts, getCylinders and getGears.
If that is really not what you want then you can leave a comment.
UPDATE
You can solve it also with class inheritance. First make a base class Part that is also an entity and use it in the other classes Belt, Cylinder and Gear:
Part:
<?php
namespace Machine\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Part
*
* #ORM\Entity
* #ORM\Table("part")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="discriminator", type="string")
* #ORM\DiscriminatorMap({
* "part" = "Part",
* "gear" = "Gear",
* "cylinder" = "Cylinder",
* "belt" = "Belt",
* })
* #property int $id
*/
class Part
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var Machine
* #ORM\ManyToOne(targetEntity="Machine\Entity\Machine", inversedBy="parts")
* #ORM\JoinColumn(name="machine_id", referencedColumnName="id", nullable=true)
*/
protected $machine;
/**
* Get id.
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set id.
*
* #param int $id
* #return self
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
//... add setters and getters for machine as normal ...
}
Extend this class in your other parts:
Belt:
<?php
namespace Machine\Entity;
/**
* Belt
*
* #ORM\Entity
*/
class Belt extends Part
{
}
Cylinder:
<?php
namespace Machine\Entity;
/**
* Cylinder
*
* #ORM\Entity
*/
class Cylinder extends Part
{
}
Gear:
<?php
namespace Machine\Entity;
/**
* Gear
*
* #ORM\Entity
*/
class Gear extends Part
{
}
Now in your machine relate to the parts like as follows.
Machine:
<?php
namespace Machine\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Machine
*
* #ORM\Entity
* #ORM\Table("machine")
* #property int $id
*/
class Machine
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* Get id.
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set id.
*
* #param int $id
* #return self
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* #var Collection
* #ORM\OneToMany(targetEntity="Machine\Entity\Part", mappedBy="machine")
*/
protected $parts;
public function __constuct()
{
$parts = new ArrayCollection();
}
/**
*
* #return Collection
*/
public function getParts()
{
return $this->parts;
}
//... add setters and getters for parts as normal ...
}
Extend this class in your other parts:
Reading further in the Doctrine2 documentation in chapter 6.1. Mapped Superclasses (referred to by #Wilt):
... Furthermore Many-To-Many associations are only possible if the mapped superclass is only used in exactly one entity at the moment...
This means in this case the ORM mapping doesn't help. I cannot gather the data of all three entities MachineHasCylinder, MachineHasBelt and MachineHasGear through a MappedSupperclass at the same time.
I think using DQL or Native SQL is the only solution for this problem.

Categories