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
Related
I have an app that manages all of its users inside one entity (USER), implementing the UserInterface, as recommended.
Linked to it, I got the CUSTOMER and the EMPLOYEE entities: each one storing specific infos about the user.
You may ask why I'm doing like this: an EMPLOYEE can be a CUSTOMER in many WORKPLACES and may work in many WORKPLACES. In order to avoid repetition, the common info got centralized in USER and the attribute Roles in USER is always blank in db.
The goal here is to load my roles based on specific data stored in another entity (WORKPLACE) when an EMPLOYEE logs in.
My great question may live in the UserRepository: when symfony calls the repository looking for the user entered on the login form, doctrine delivers it with no roles. I put my roles and return the User to the Authenticator (the vanilla one, I havent touched it yet). After the end of every requery, Symfony checks for the user but then doctrine loads the ROLE_CLIENT (in other words - no privileges).
What I already tried and failed
Make another authenticator supporting the routes that need those specific rules (as seen here https://symfony.com/doc/current/security/guard_authentication.html )
Use the UserLoaderInterface inside UserRepository (as seen here https://symfony.com/doc/master/security/user_provider.html#using-a-custom-query-to-load-the-user )
Voters (I didnt see a way to code them to my needs)
The entities
class User implements UserInterface
{
/**
* #ORM\Id()
*/
private $cpf;
/**
* #ORM\Column
*
*/
private $email;
/**
* #ORM\Column
*/
private $nome;
/**
* #var string
*
* #ORM\Column
*/
private $telefone;
/**
* #var \DateTime|null
*
* #ORM\Column
*/
private $nascimento;
/**
* #var \DateTime|null
*
* #ORM\Column
*/
private $ultimoLogin;
/**
* #var string|null
*
* #ORM\Column
*/
private $endereco;
/**
* #var string|null
*
* #ORM\Column
*/
private $cidade;
/**
* #var string|null
*
* #ORM\Column
*/
private $uf;
/**
* #var int|null
*
* #ORM\Column
*/
private $cep;
/**
* #ORM\Column(name="Roles", type="json")
*/
private $roles = [];
/**
* #var string The hashed password
* #ORM\Column(name="password", type="string")
*/
private $password;
//vanilla getters and setters
Workplace entity
The booleans store the privileges i want to get
class LocalTrabalho
{
/**
* #var Configuracao
*
* #ORM\ManyToOne(targetEntity=Configuracao::class, inversedBy="localTrabalho")
* #ORM\JoinColumn(name="CNPJ", referencedColumnName="CNPJ", nullable=false)
* #ORM\Id
* the company unique code
*/
private $cnpj;
/**
* #var Funcionario
*
* #ORM\ManyToOne(targetEntity=Funcionario::class, inversedBy="localTrabalho")
* #ORM\JoinColumn(name="CPF_Funcionario", referencedColumnName="CPF", nullable=false)
* #ORM\Id
* the user-employee unique code
*/
private $cpfFuncionario;
/**
* #var bool
*
* #ORM\Column
* is this employee is active?
*/
private $ativo = 1;
/**
* #var bool
*
* #ORM\Column
*/
private $privilegioCaixa = 0;
/**
* #var bool
*
* #ORM\Column
*/
private $privilegioPrestador = 1;
/**
* #var bool
*
* #ORM\Column
*/
private $privilegioRecepcao = 0;
/**
* #var bool
*
* #ORM\Column
*/
private $privilegioAdministracao = 0;
Employee Entity
class Funcionario
{
/**
* #var int
*
* #ORM\Column
* #ORM\Id
* #ORM\GeneratedValue(strategy="NONE")
*/
private $cpf;
/**
* #var string|null
*
* #ORM\Column
*/
private $ctps;
/**
* #var string|null
*
* #ORM\Column
*/
private $foto;
An example USER tuple from db
# CodPFis, email, password, Roles, Nome, Telefone, Nascimento, Ultimo_login, Endereco, Cidade, UF, CEP
'89038252099', 'sophiejenniferteresinhaoliveira__sophiejenniferteresinhaoliveira#grupomozue.com.br', '$argon2id$v=19$m=65536,t=4,p=1$WEn7b64I9744kRJICEpaLA$jcYLDvh2bZsZPakMDGsncpbfIZwR6lN0QcgJOOSerK0', NULL, 'João da Silva', '', NULL, NULL, NULL, NULL, NULL, NULL
My last resource is asking here, after some long and wasted work hours, what should I do (or what i did wrong)?
I've seen some other threads asking similar questions, but they're outdated (im using the version 5):
Dynamic roles in symfony
Symfony 2 - Loading roles from database
Symfony2 - Dynamic Role Management
How to update roles in security token in Symfony 4 without re-logging in
Symfony User Logout After Role Change
PS: Someone tried this approach (https://github.com/symfony/symfony/issues/12025#issuecomment-562004005) and got success in symfony5?
Well, turns out that the best solution for my case was manipulate the User Repository. Doctrine uses them to operate the db so I just coded my rules inside and let the framework do the rest.
The algorithm is something like the shown below:
Find the user using the provided $id
If he/she works somewhere, load the ROLE constants into the User entity
If he/she does not have a job, load the profile as a common user
public function find($id, $lockmode=null, $lockversion = null){
$us = $this->createQueryBuilder('u')
->andWhere('u.cpf = :cpf')
->setParameter('cpf', $id['cpf'])
->getQuery()->getOneOrNullResult();
$lt = $this->_em->createQuery(
'SELECT t
FROM App\Entity\LocalTrabalho t
WHERE t.cpfFuncionario = :cpf'
)
->setParameters(['cpf' => $us->getCpf()])
->getResult();
if (count($lt) > 0){
$regras = ['ROLE_FUNCIONARIO'];
if ($lt[0]->getPrivilegioCaixa()) $regras[] = 'ROLE_CAIXA';
if ($lt[0]->getPrivilegioPrestador()) $regras[] = 'ROLE_PRESTADOR';
if ($lt[0]->getPrivilegioRecepcao()) $regras[] = 'ROLE_RECEPCAO';
if ($lt[0]->getPrivilegioAdministracao())
{
$regras = ['ROLE_ADMIN'];
}
$us->setRoles($regras);
}
return $us;
}
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 ...
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?
Weird and annoying issue:
A simple Doctrine query with a leftJoin on OneToMany relation is returning (and mapping, to be more precise) only one line of the joined entities set, but only on the distant pre-production server.
I get the expected full result set on my local Wamp system.
The Database is in SQL server...
app/config/parameters.yml
parameters:
database_driver_class: Realestate\MssqlBundle\Driver\PDODblib\Driver
It's a simple OneToMany relation, as there are at least hundreds working fine in the rest of the application.
The generated SQL query works fine when called directly (returning the correct array).
The getArrayResult() method too.
As I don't even manipulate the data result behind, the only missing part is the Doctrine Mapping, which seems to behave differently on the servers.
I put the codes so you can see, but it is the simplest basic school example you can find in any tutorial or in the Symfony doc.
The problem is certainly coming from some config or mysterious system environment behaviour.
Help
Facture.php
/**
* Facture
*
* #ORM\Table(name="GAF_Facture")
* #ORM\Entity(repositoryClass="GA\FacturationBundle\Repository\FactureRepository")
* #FactureAssert\Facture
*/
class Facture {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="GA\FacturationBundle\Entity\DetailFacture", mappedBy="facture", cascade={"persist"})
* #ORM\OrderBy({"numLigne" = "ASC"})
*/
private $details;
/**
* Get details
*
* #return ArrayCollection
*/
public function getDetails()
{
return $this->details;
}
/**
* Set details
*
* #return Facture
*/
public function setDetails($details)
{
$this->details = $details;
return $this;
}
public function __construct() {
$this->details = new ArrayCollection();
}
}
DetailFacture.php
/**
* DetailFacture
*
* #ORM\Table(name="GAF_DetailFacture")
* #ORM\Entity(repositoryClass="GA\FacturationBundle\Repository\DetailFactureRepository")
*/
class DetailFacture
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="GA\FacturationBundle\Entity\Facture", inversedBy="details")
* #ORM\JoinColumn(name="FactureId", referencedColumnName="id", nullable=false)
*/
private $facture;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set facture
*
* #param \GA\FacturationBundle\Entity\Facture $facture
* #return DetailFacture
*/
public function setFacture(\GA\FacturationBundle\Entity\Facture $facture)
{
$this->facture = $facture;
return $this;
}
/**
* Get facture
*
* #return \GA\FacturationBundle\Entity\Facture
*/
public function getFacture()
{
return $this->facture;
}
}
The simplified code snippet I use to test in the controller:
AnyController.php
...
public function anyAction(){
$dets = $this->em()->getRepository('GAFacturationBundle:Facture')
->createQueryBuilder('f')
->where('f = :facture')
->leftJoin('f.details', 'd')
->addSelect('d')
->setParameter('facture', $facture)
->getQuery()->getOneOrNullResult()
->getDetails();
foreach($dets as $d){
echo $d->getId() .' -- cc -- ';
}
exit;
}
Displays:
On the server:
29 -- cc --
Local:
29 -- cc -- 30 -- cc -- 31 -- cc -- 32 -- cc --
Duuuuuude!! ! ! ! ! !!
EDIT: NEW CLUE
I reproduced the issue on another query (same page, different parameters..)
I made a modification in the addSelect method of the queryBuilder, after having compared it to others working: I added '[' to the addSelect:
$qb
...
->addSelect(['ed', 'fr', ... ])
(instead of:
->addSelect('ed', 'fr, ... )
)
Kind of a random try..
But it worked!
Once.
Then coming back to a new instance of the page, the issue was there once again...
So I am now searching for some possible cache issue..
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.