Cancellazione su relazione oneToMany

14 views
Skip to first unread message

Matteo

unread,
Apr 7, 2017, 6:03:42 AM4/7/17
to Gruppo symfony
Ciao a tutti, ho 2 entità, User e UserPermission in relazione oneToMany. Uno User può avere più UserPermission.

Quando faccio qualcosa come $user->addPermission($permission);

dove addPermission() è qualcosa del genere:

$this->userPermissions = ArrayCollection();

public function addPermission($permission)
{
   ...
   $this->userPermissions->add($permission);
   ...
}

Poi faccio $this->userRepository->save($user);

viene salvato tutto nella tabella user_permissions

Questo lo schema di User:

oneToMany:
  userPermissions:
    targetEntity: Domain\Model\Permission\UserPermission
    cascade: ["all"]
    mappedBy: user

Questo invece di UserPermisson:

  manyToOne:
    user:
      targetEntity: Domain\Model\User\User
      inversedBy: userPermissions
      cascade: ["all"]
      joinColumn:
        name: user_id
        referencedColumnName: id

Ho problemi invece nella rimozione:

 $user->removePermission($permission);

public function removePermission($permission)
{
   $this->userPermissions->removeElement($permission);
}

$this->userRepository->save($user);

E dalla tabella user_permissions non mi spariscono le righe... Anche se la collezione userPermissions effettivamente lavora, nel senso che removeElement($permission) mi toglie l'elemento dalla collezione. Ma non capisco come mai questa rimozione non viene persistita.
Il cascade: ["all"] non dovrebbe aiutarmi in questo?

Grazie

Massimiliano Arione

unread,
Apr 7, 2017, 7:29:20 AM4/7/17
to symfony-it
Se vuoi un cascade anche a livello di db, devi specificarlo dentro a "joinColumn"

ciao
M.

Luca Genuzio

unread,
Apr 7, 2017, 8:00:58 AM4/7/17
to symfo...@googlegroups.com
Ciao, 
se non sbaglio il cascade serve quando rimuovi lo user e non permision.
Prova a dare un'occhiata qui:

Luca

--
Hai ricevuto questo messaggio perché sei iscritto al gruppo "symfony-it" di Google Gruppi.
Visita questo gruppo all'indirizzo https://groups.google.com/group/symfony-it.

Matteo

unread,
Apr 7, 2017, 9:01:51 AM4/7/17
to Gruppo symfony
Ho provato ad aggiungere il cascade al join ma niente da fare. Riecco gli schemi:

Domain\Model\User\User:
  type: entity
  table: users
  repositoryClass: Infrastructure\Domain\Model\User\Doctrine\DoctrineUserRepository
  id:
    userId:
      type: UserId
      column: id
      nullable: false
  oneToMany:
    userPermissions:
      targetEntity: Domain\Model\Permission\UserPermission
      cascade: ["all"]
      mappedBy: user
  fields:
    ...



Domain\Model\Permission\UserPermission:
  type: entity
  table: user_permissions
  uniqueConstraints:
    permission_per_user:
      columns: user_id, permission_id
  id:
    userPermissionId:
      type: UserPermissionId
      column: id
      nullable: false
  manyToOne:
    user:
      targetEntity: Domain\Model\User\User
      inversedBy: userPermissions
      cascade: ["all"]
      joinColumn:
        cascade: ["all"]
        name: user_id
        referencedColumnName: id
    permission:
      targetEntity: Domain\Model\Permission\Permission
      cascade: ["all"]
      joinColumn:
        name: permission_id
        referencedColumnName: id
  fields:
    ...

Guardando anche il link indicatomi da Luca, mi sembra di capire che dovrei fare qualcosa come 

public function removePermission($permission)
{
   $permission->setUser(null);
   $this->userPermissions->removeElement($permission);
}

ma così facendo ottengo altri errori; gli id delle mie entità sono estensioni di Doctrine\DBAL\Types\GuidType quindi ho un metodo convertToDatabaseValue() che si occupa di tornare l'id "scalare" dall'id "classe"

public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
    return $value->id();
}

Avendo settato user = null in removePermission(), $value è null e nasce l'erroe...

Al massimo facendo questo

public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
    return $value ? $value->id() : null;
}

Ottengo che alla chiamata di $this->userRepository->save($user);
nella tabella user_permissions, le righe che dovrebbero essere cancellate, hanno la colonna user_id a null

pie'

unread,
Apr 7, 2017, 9:07:53 AM4/7/17
to symfo...@googlegroups.com

Matteo

unread,
Apr 7, 2017, 9:16:07 AM4/7/17
to Gruppo symfony
Grazie pie', lo avevo già letto insieme a tanti altri articoli.
Ora credo di aver trovato una soluzione e spero di avere un feedback positivo da voi :D

Per prima cosa, nello schema di User (che trovate completo sopra) ho aggiunto orphanRemoval:

...
userPermissions:
  targetEntity: UserAuthority\Domain\Model\Permission\UserPermission
  orphanRemoval: true
  cascade: ["all"]
  mappedBy: user
...

Questo faceva si che le righe da user_permission venissero si cancellate, ma cancellava anche altre righe in un'altra tabella (UserPermission, come vedete dallo schema sopra ha una relazione manyToOne a Permission, e nella tabella permission si cancellava le righe, cosa non voluta).
Allora ho modificato anche lo schema di UserPermission (completo lo trovate sopra), in questo modo

...
permission:
  targetEntity: UserAuthority\Domain\Model\Permission\Permission
  cascade: ["persist"] (prima era cascade: ["all"]
  joinColumn:
    name: permission_id
    referencedColumnName: id
...

E ora tutto pare funzionare a dovere...
Che ne dite? E' un approccio corretto?

pie'

unread,
Apr 7, 2017, 9:17:37 AM4/7/17
to symfo...@googlegroups.com
direi di si

Matteo

unread,
Apr 7, 2017, 9:22:26 AM4/7/17
to Gruppo symfony
Ho scoperto anche che con le modifiche allo schema che vi ho mostrato sopra, non ho bisogno neppure di fare 

$permission->setUser(null); (come da esempio sopra)

Massimiliano Arione

unread,
Apr 7, 2017, 10:11:09 AM4/7/17
to symfony-it
In joinColumn vanno istruzioni a livello db, non a livello di ORM, quindi l'opzione corretta è onDelete: cascade

ciao
M.
Message has been deleted

Massimiliano

unread,
Apr 7, 2017, 11:21:52 AM4/7/17
to symfony-it
Ma il cascade non serve per eliminare tutti gli oggetti figli di un oggetto (oggetto padre) quando vuoi eliminare quell'oggetto? Nel tuo caso perché vuoi usare un cascade su un figlio? Se l'oggetto user (il padre) ha delle userPermissions (i figli l'onDelete=Cascade lo inserirei in questa joincolumn) il cascade on delete ha senso in questo caso se voglio eliminare uno user e di conseguenza mi elimina (non avendo più senso di esistere perché rimarrebbero orfane)  le userPermissions.
Se ho capito male illuminami ;)

Ciao

Matteo

unread,
Apr 7, 2017, 5:52:21 PM4/7/17
to Gruppo symfony
Massimiliano A. attualmente in joinColumn non ho nessun cascade. L'ho risolta in questo modo:

Domain\Model\Permission\UserPermission:
  type: entity
  id:
    userPermissionId:
      type: UserPermissionId
      column: id
      nullable: false
  manyToOne:
    user:
      targetEntity: Domain\Model\User\User
      inversedBy: userPermissions
      joinColumn:
        name: user_id
        referencedColumnName: id
    permission:
      targetEntity: Domain\Model\Permission\Permission
      cascade: ["persist"]
      joinColumn:
        name: permission_id
        referencedColumnName: id
  fields:
    ...

Domain\Model\User\User:
  type: entity
  id:
    userId:
      type: UserId
      column: id
      nullable: false
  manyToOne:
    role:
      targetEntity: Domain\Model\Role\Role
      cascade: ["persist"]
      joinColumn:
        name: role_id
        referencedColumnName: id
  oneToMany:
    userPermissions:
      targetEntity: Domain\Model\Permission\UserPermission
      orphanRemoval: true
      cascade: ["all"]
      mappedBy: user
  fields:
    ...


Massimiliano P. non credo di aver capito il tuo ragionamento. Ad ogni modo il cascade serve in generale a eseguire operazioni in cascata. Non solo a cancellare

--

Massimiliano

unread,
Apr 7, 2017, 6:01:20 PM4/7/17
to symfony-it
Si hai ragione lo so, mi sono espresso male, tu avevi problemi su $user->removePermission($permission); e allora intendevo la cascade on delete. ;)

Matteo

unread,
Apr 10, 2017, 1:04:42 PM4/10/17
to Gruppo symfony
Purtroppo ho preso un abbaglio e ancora non funziona come vorrei.
Faccio un passo indietro raccontandovi ciò che dovrei raggiungere (cosa che magari fa ragionare meglio anche me).

Solite due entità, User e UserPermission, one-to-many Bidirezionale. (In realtà potrebbe essere anche unidirezionale con una many-to-many, ma purtroppo nella tabella user_permissions ho dovuto aggiungere una colonna "extra" e quindi non ho potuto lasciare a doctrine la sua totale gestione).

Detto ciò, Ho un repository per User, con il quale riesco ad aggiungere tanti UserPermission con 
class User {
   ...
   $this->userPermissions = ArrayCollection()
   ....
   public function addPermission($permission)
   {
      ...
      $this->userPermissions->add($permission);
      ...
   }
   ...
}

e poi $this->userRepository->save($user);

Riesco anche a cancellarli facendo ad esempio

$this->user->getPermissions()->clear();

$this->userrepository->save($user);

Dove nasce il problema:

Dall'esterno arriva la richiesta di modificare i permessi dell'utente X. La modifica permette di:
1) Aggiungere permessi alla collezione $this->userPermissions che prima non c'erano
2) Modificare lo stato del permesso (attivo - non attivo) già presente nella collezione tramite l'uso della colonna extra aggiunta (della quale vi ho parlato sopra)
3) Rimuovere il permesso dalla collezione

La "furbata" che speravo di poter fare è ignorare cosa la collezione già contiene, svuotarla con un clear e rimetterci tutto come da richiesta dato che dal client non mi arrivano solo le modifiche ma mi arriva tutto, un intero "dump" di come la situazione permessi dovrebbe essere. Ed è qui che nasce la noia, perchè sto lavorando in una transazione quindi ecco cosa succede:

1) Inizia la transazione
2) Svuoto la collezione ($this->user->getPermissions()->clear();)
3) Aggiungo ciò che arriva dalla richiesta alla collezione (loop -> $user->addPermission($permission) )
4) $this->userRepository->save($user) - Attenzione qui eseguo solo $this->_em->persist($user)
5) flush
7) commit

Quindi da quello che ho capito, Doctrine dice: "Hai aggiunto (punto 3) un'entità alla collezione che prima (punto 2) mi hai chiesto di togliere, quindi di base non la tolgo" e questo fa si che al flush, viene persistito un record già esistente e via con l'errore "Integrity constraint violation: 1062 Duplicate entry".

Ovviamente questo non si verifica se non ho nulla da cancellare (in quanto la collezione è vuota) o se non ho nulla da aggiungere o se aggiungo qualcosa che non c'era prima.

Spero di essere stato chiaro ma spero che qualcuno mi dia una mano a ragionarci su :)

Grazie in anticipo


Il giorno 8 aprile 2017 00:01, Massimiliano <pompo...@gmail.com> ha scritto:
Si hai ragione lo so, mi sono espresso male, tu avevi problemi su $user->removePermission($permission); e allora intendevo la cascade on delete. ;)

--

pie'

unread,
Apr 10, 2017, 2:13:23 PM4/10/17
to symfo...@googlegroups.com
ma se provi ad rimuovere le(associazioni) permission uno ad uno che succede?

public function removeAll(){

Matteo

unread,
Apr 10, 2017, 2:33:59 PM4/10/17
to Gruppo symfony
Ciao pie' grazie della risposta, si (Se intendi l'esempio che sto per mostrarti).
Invece di fare 

$user->getUserPermissions()->clear();

ho fatto:

foreach ($user->getUserPermissions() as $userPermission) {

    $user->removeUserPermission($userPermission->getPermission());

    $user->repository->removePermission($userPermission)
}

 $user->repository->removePermission è il seguente

public function removePermission(UserPermission $userPermission)
{
    $this->_em->remove($userPermission);
}

e $user->removeUserPermission è:

public function removeUserPermission($permission)
{
    $this->userPermissions->remove($permission);
}

Quindi lo tolgo sia dalla collezione che in maniera esplicita tramite il repository
Ma non cambia nulla... Rimane il fatto che se cancello qualcosa che nella stessa transazione verrà riaggiunto, la riga dal db non viene cancellata

pie'

unread,
Apr 10, 2017, 3:00:18 PM4/10/17
to symfo...@googlegroups.com
e allora controlla se la collection contiene elemento:

if( !$user->getUserPermission()->contains($permission) ){
 $user->getUserPermission()->add($permission);
}

oppure magari fai un detach

Matteo

unread,
Apr 10, 2017, 3:12:52 PM4/10/17
to Gruppo symfony
La collezione non lo contiene... come vi dicevo, presa singolarmente la cancellazione funziona. E' quando riaggiungo subito dopo un elemento che era già nella collezione che nasce il problema

pie'

unread,
Apr 10, 2017, 3:17:23 PM4/10/17
to symfo...@googlegroups.com
appunto, se la collezione già lo contiene non lo togliere aggiornalo solamente


Matteo

unread,
Apr 10, 2017, 4:03:02 PM4/10/17
to Gruppo symfony
Si ma così decade la mia idea del collection->clear() risparmiandomi la logica del "c'è o non c'è"  :)
Inoltre senza il clear devo dare un ulteriore controllo per vedere quali devo togliere perchè mancanti nella richiesta (rimossi quindi dal client)
Dimmi se non mi sono spiegato bene!

Luca Genuzio

unread,
Apr 11, 2017, 2:21:04 AM4/11/17
to symfo...@googlegroups.com
Ciao,
so che è ciò che non volevi fare, ma prova a rimuovere i permission da cancellare con em->remove($permission); 


Luca

Matteo

unread,
Apr 11, 2017, 2:46:03 AM4/11/17
to Gruppo symfony
Ciao Luca grazie della risposta.
Come scritto qualche messaggio fa è una prova che ho fatto

Invece di fare 

$user->getUserPermissions()->clear();

ho fatto:

foreach ($user->getUserPermissions() as $userPermission) {

    $user->removeUserPermission($userPermission->getPermission());

    $user->repository->removePermission($userPermission)
}

 $user->repository->removePermission è il seguente

public function removePermission(UserPermission $userPermission)
{
    $this->_em->remove($userPermission);
}

e $user->removeUserPermission è:

public function removeUserPermission($permission)
{
    $this->userPermissions->remove($permission);
}

Quindi lo tolgo sia dalla collezione che in maniera esplicita tramite il repository
Ma non cambia nulla... Rimane il fatto che se cancello qualcosa che nella stessa transazione verrà riaggiunto, la riga dal db non viene cancellata

Questa sembra la parte inerente il mio caso ma non capisco bene cosa dedurne:


You can also clear the contents of a whole collection using the Collections::clear() method. You should be aware that using this method can lead to a straight and optimized database delete or update call during the flush operation that is not aware of entities that have been re-added to the collection.
Say you clear a collection of tags by calling $post->getTags()->clear(); and then call $post->getTags()->add($tag). This will not recognize the tag having already been added previously and will consequently issue two separate database calls.

Luca Genuzio

unread,
Apr 11, 2017, 3:42:54 AM4/11/17
to symfo...@googlegroups.com
Scusami, avevo letto superficialmente e non capito che inietti il repository nell'entity (io non lo farei, ma qui son gusti e scelte)

Questo però non mi torna, ma tu dici che funziona
----------------------------------
foreach ($user->getUserPermissions() as $userPermission) {

    $user->removeUserPermission($userPermission->getPermission());

    $user->repository->removePermission($userPermission)
}
----------------------------------

Me lo immaginavo così:
----------------------------------
foreach ($user->getUserPermissions() as $userPermission) {

    $user->removeUserPermission($userPermission);

    $user->repository->removePermission($userPermission)
}
----------------------------------

Ricontrolla che le permission siano state rimosse della collection prima del flush (un bel dump). 
Domanda: potrebbe essere che doctrine si incasina con il cascade: ["all"] e un errata rimozione dalla collection?

Matteo

unread,
Apr 11, 2017, 3:58:37 AM4/11/17
to Gruppo symfony
Il repository nell'entity? no no non lo faccio...
Ho un servizio, un repository e i 2 modelli

$user->removeUserPermission($userPermission->getPermission()); è corretto per "questioni interne"
ma al fine del nostro esempio va bene anche $user->repository->removePermission($userPermission)

E si, i permessi vengono effettivamente tolti dalla collection dell'entity 

Use {
...
private $userPermissions;
....
}

Non penso sia il cascade all il problema, ma il fatto che tolgo qualcosa che poi rimetto... quindi lui dice, perchè cancellarlo?

Ora sto provando una strada più dispendiosa, ossia prima aggiungo alla collection i permessi in arrivo dalla richiesta, aggiungendo quindi quelli nuovi e aggiornando quelli esistenti
Dopo di che faccio un altro ciclo per rimuovere dalla collection i permessi che non sono più presenti dalla richiesta.

Sembra funzionare anche se devo fare un ciclo in più...

Faccio sapere se è ok

Massimiliano Arione

unread,
Apr 11, 2017, 4:37:51 AM4/11/17
to symfony-it
Io sinceramente non ho capito perché ti incaponisci su questa logica, probabilmente con una semplice confronto tra la collection esistente e quella nuova te la saresti già cavata

ciao
M.
Reply all
Reply to author
Forward
0 new messages