[Doctrine] Koppeltabel naar meerdere andere entities

Pagina: 1
Acties:

Onderwerpen

Vraag


Acties:
  • 0 Henk 'm!

  • JSchut
  • Registratie: Februari 2002
  • Laatst online: 09:24
In Symfony 3 (Doctrine ORM) wil ik voor verschillende entities een custom url opslaan.
Met Laravel (Eloquent ORM) heb ik dat opgelost met polymorphic relations.
Echter krijg ik het in Doctrine niet voor elkaar.

Ik probeer de volgende (uitgeklede) inrichting te krijgen:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
Entity: Page
id
name

Entity News
id
name

Entity CustomUrl
id
entity
entity_id
url

Hierbij moet een record in CustomUrl bijvoorbeeld (1, page, 1, testurl) worden.

Nu heb ik gekeken naar inheritance. Hier kan ik het wel mee voor elkaar krijgen denk ik maar dit lijkt mij niet de juiste oplossing. Het klopt natuurlijk niet om Page een URL entity te laten extenden.

Edit:
Nu kan ik natuurlijk een CustomUrl entity maken die niet weet wat voor entity hij is
code:
1
2
3
Entity CustomUrl
id
url

En dan een one to one relatie maken. Maar ik wil wel graag de entity opslaan in de tabel. Mischien voor dit specifieke voorbeeld niet geheel nodig. Maar voor andere zaken kan dit wel nodig zijn. Bijvoorbeeld om te kijken of een waarde uniek is voor een bepaalde entity.

[ Voor 26% gewijzigd door JSchut op 02-11-2017 13:33 ]

PSN jschut_82 | Xbox: JSchut82

Beste antwoord (via JSchut op 07-11-2017 15:23)


  • drm
  • Registratie: Februari 2001
  • Laatst online: 09-06 13:31

drm

f0pc0dert

Dit is inderdaad een beetje een kip/ei probleem dat je hebt in Doctrine. Je wilt op basis van een nieuwe entity, waarvan je het id nog niet weet iets in de database opslaan waar het id wel voor nodig is. Daarvoor heb je een persist en een flush nodig, en dat zijn nou precies de events waarop je probeert in te haken...

Intern (in Doctrine) wordt dit opgelost in een database transactie in de UnitOfWork (eigenschap van de EntityManager). Je kunt hier wel op inhaken, maar daar zitten wel echt een hoop haken en ogen aan die ik niet meer zo 1-2-3 uit mijn hoofd kan reproduceren. Suffice to say: als je genoeg tijd hebt om het uit te zoeken kun je daar wel uitkomen, maar dan moet je wel bereid zijn de logica van de UnitOfWork te doorgronden. Dat kost je wel even wat tijd.

Het is iets minder mooi, maar wel eenvoudiger om een nieuwe entity gewoon te beschouwen als een "onverwerkte" entity nadat hij is opgeslagen, want dan kun je dezelfde logica toepassen als wanneer je een bestaande zou bewerken. Ik heb vergelijkbare issues daarom vaker opgelost door in de postFlush() een nieuwe persist + flush te doen, waarbij de subscriber zelf ervoor zorgt dat er geen infinite recursie optreedt. Je zou er ook voor kunnen kiezen om daarvoor een aparte Doctrine DBAL connectie te gebruiken die geen weet heeft van je ORM. Daar is ook wel wat voor te zeggen; immers produceer je afgeleide metadata en niet zozeer iets dat per se onderdeel uit moet maken van je relationele model.

Zie https://github.com/zicht/...teAliasSubscriber.php#L91 voor een voorbeeldje van een vergelijkbare strategie en https://github.com/zicht/.../EventSubscriber.php#L113 voor een voorbeeld van een ingewikkeldere die zich ook bezig houdt met de UOW.

Music is the pleasure the human mind experiences from counting without being aware that it is counting
~ Gottfried Leibniz

Alle reacties


Acties:
  • 0 Henk 'm!

  • phex
  • Registratie: Oktober 2002
  • Nu online
Misschien niet helemaal de richting waarop je zoekt, maar je zou een trait kunnen maken die alle shared field annotations voor de entities bevat. Als je je schema update (of migratie maakt) worden automatisch die velden aan je entity toegevoegd.

Wil je perse alle values in 1 gedeelde tabel hebben kun je misschien kijken naar inheritance mapping: http://docs.doctrine-proj.../inheritance-mapping.html maar dat is complexer.

Eigenlijk is news ook waarschijnlijk een page, dus dan zou alles page extenden?

Of je kan de tweakers methodiek gebruiken waarbij je alle entity types afvangt in de router met een prefix:

nieuws: Apples leveringen van iPhones, iPads en Macs stijgen

@Route("/nieuws/{id}/{slug}.html")

reviews: Google Pixel 2 - Snel en recht door zee

@Route("/reviews/{id}/{slug}.html")

[ Voor 38% gewijzigd door phex op 03-11-2017 00:29 ]


Acties:
  • 0 Henk 'm!

  • JSchut
  • Registratie: Februari 2002
  • Laatst online: 09:24
Trait heb ik aan gedacht. Deze gebruik ik nu ook alleen op een andere manier.

Ik heb een trait gemaakt om de relatie te maken naar URLRewrite.
Deze zorgt er ook voor dat de UrlRewrite wordt aangemaakt wanneer je de Entity opslaat.

Enige wat me nog niet lukt is automatisch het target path (page/1) toe te voegen aan de UrlRewrite.

Wat ik momenteel doe is via een extra functie in de trait het target path genereren. Bij het opslaan roep ik deze dus zelf aan na het toevoegen (na de flush) en tijdens het updaten.

code:
1
2
3
4
5
6
$em->persist($page);
$em->flush();

$page->generateTargetPath();
$em->persist($page);
$em->flush();


Niet ideaal maar voor nu werkt het.

Het doel is dat ik als er geen url gevonden wordt ik in 1 tabel kan kijken of er een custom url is en deze doorsluizen werkingvan naar de ruwe url. Deze custom urls kunnen alles zijn en hebben geen prefix.

Inheritance kan ook maar dat is wel wat vreemd. UrlRewrite is namelijk niet een abstracte Page.

Misschien kan ik het laatste stukje nog oplossen met een postFlush event. Daarin checken of de Entity de trait gebruikt en zo ja op de entity de generate functie aanroepen en persisten.

PSN jschut_82 | Xbox: JSchut82


Acties:
  • 0 Henk 'm!

  • NMe
  • Registratie: Februari 2004
  • Laatst online: 09-09 13:58

NMe

Quia Ego Sic Dico.

Normaal gesproken doe je dit met polymorfisme. Je hebt een abstracte UrlEntity en leidt daar PageUrlEntity en NewsUrlEntity van af. In dit geval zou ik kiezen voor single table inheritance.

Als je dat eenmaal hebt kun je in de abstracte UrlEntity een abstracte getEntity-method maken als je wil die afhankelijk van welk afgeleid type hij heeft het juiste object teruggeeft in één generieke functie. Op deze manier hoef je je referentiële integriteit niet op te geven, heb je nettere code én staat alles alsnog gewoon in één tabel bij elkaar.

[ Voor 8% gewijzigd door NMe op 03-11-2017 19:42 ]

'E's fighting in there!' he stuttered, grabbing the captain's arm.
'All by himself?' said the captain.
'No, with everyone!' shouted Nobby, hopping from one foot to the other.


Acties:
  • 0 Henk 'm!

  • phex
  • Registratie: Oktober 2002
  • Nu online
Als je nog acties wil doen op je entity tijdens of na het opslaan kun je hier naar kijken:
http://symfony.com/doc/cu.../lifecycle_callbacks.html

Acties:
  • 0 Henk 'm!

  • JSchut
  • Registratie: Februari 2002
  • Laatst online: 09:24
phex schreef op maandag 6 november 2017 @ 17:32:
Als je nog acties wil doen op je entity tijdens of na het opslaan kun je hier naar kijken:
http://symfony.com/doc/cu.../lifecycle_callbacks.html
Dat gaat helaas niet. De id is dan nog niet bekend. Dat is pas na de flush in postFlush bekend. Maar dat is geen lifecycle functie.

PSN jschut_82 | Xbox: JSchut82


Acties:
  • 0 Henk 'm!

  • phex
  • Registratie: Oktober 2002
  • Nu online
postPersist heeft in tegenstelling wat de naam suggereert wel het id van de entity.

Acties:
  • 0 Henk 'm!

  • JSchut
  • Registratie: Februari 2002
  • Laatst online: 09:24
Ik dacht dat ik dat geprobeerd had en dat dat niet werkte. Mmm morgen nog maar eens proberen dan.

PSN jschut_82 | Xbox: JSchut82


Acties:
  • 0 Henk 'm!

  • JSchut
  • Registratie: Februari 2002
  • Laatst online: 09:24
phex schreef op maandag 6 november 2017 @ 21:09:
postPersist heeft in tegenstelling wat de naam suggereert wel het id van de entity.
Ik heb het nogmaals geprobeerd. Je hebt de id inderdaad. Echter weet ik het probleem weer. Het lukt daar niet meer om de wijzigen te persisten.

Uit de docs:
postUpdate, postRemove, postPersist
The three post events are called inside EntityManager#flush(). Changes in here are not relevant to the persistence in the database, but you can use these events to alter non-persistable items, like non-mapped fields, logging or even associated classes that are not directly mapped by Doctrine.

[ Voor 3% gewijzigd door JSchut op 07-11-2017 09:17 ]

PSN jschut_82 | Xbox: JSchut82


Acties:
  • Beste antwoord
  • 0 Henk 'm!

  • drm
  • Registratie: Februari 2001
  • Laatst online: 09-06 13:31

drm

f0pc0dert

Dit is inderdaad een beetje een kip/ei probleem dat je hebt in Doctrine. Je wilt op basis van een nieuwe entity, waarvan je het id nog niet weet iets in de database opslaan waar het id wel voor nodig is. Daarvoor heb je een persist en een flush nodig, en dat zijn nou precies de events waarop je probeert in te haken...

Intern (in Doctrine) wordt dit opgelost in een database transactie in de UnitOfWork (eigenschap van de EntityManager). Je kunt hier wel op inhaken, maar daar zitten wel echt een hoop haken en ogen aan die ik niet meer zo 1-2-3 uit mijn hoofd kan reproduceren. Suffice to say: als je genoeg tijd hebt om het uit te zoeken kun je daar wel uitkomen, maar dan moet je wel bereid zijn de logica van de UnitOfWork te doorgronden. Dat kost je wel even wat tijd.

Het is iets minder mooi, maar wel eenvoudiger om een nieuwe entity gewoon te beschouwen als een "onverwerkte" entity nadat hij is opgeslagen, want dan kun je dezelfde logica toepassen als wanneer je een bestaande zou bewerken. Ik heb vergelijkbare issues daarom vaker opgelost door in de postFlush() een nieuwe persist + flush te doen, waarbij de subscriber zelf ervoor zorgt dat er geen infinite recursie optreedt. Je zou er ook voor kunnen kiezen om daarvoor een aparte Doctrine DBAL connectie te gebruiken die geen weet heeft van je ORM. Daar is ook wel wat voor te zeggen; immers produceer je afgeleide metadata en niet zozeer iets dat per se onderdeel uit moet maken van je relationele model.

Zie https://github.com/zicht/...teAliasSubscriber.php#L91 voor een voorbeeldje van een vergelijkbare strategie en https://github.com/zicht/.../EventSubscriber.php#L113 voor een voorbeeld van een ingewikkeldere die zich ook bezig houdt met de UOW.

Music is the pleasure the human mind experiences from counting without being aware that it is counting
~ Gottfried Leibniz


Acties:
  • +1 Henk 'm!

  • JSchut
  • Registratie: Februari 2002
  • Laatst online: 09:24
drm schreef op dinsdag 7 november 2017 @ 13:36:
Dit is inderdaad een beetje een kip/ei probleem dat je hebt in Doctrine. Je wilt op basis van een nieuwe entity, waarvan je het id nog niet weet iets in de database opslaan waar het id wel voor nodig is. Daarvoor heb je een persist en een flush nodig, en dat zijn nou precies de events waarop je probeert in te haken...

Intern (in Doctrine) wordt dit opgelost in een database transactie in de UnitOfWork (eigenschap van de EntityManager). Je kunt hier wel op inhaken, maar daar zitten wel echt een hoop haken en ogen aan die ik niet meer zo 1-2-3 uit mijn hoofd kan reproduceren. Suffice to say: als je genoeg tijd hebt om het uit te zoeken kun je daar wel uitkomen, maar dan moet je wel bereid zijn de logica van de UnitOfWork te doorgronden. Dat kost je wel even wat tijd.

Het is iets minder mooi, maar wel eenvoudiger om een nieuwe entity gewoon te beschouwen als een "onverwerkte" entity nadat hij is opgeslagen, want dan kun je dezelfde logica toepassen als wanneer je een bestaande zou bewerken. Ik heb vergelijkbare issues daarom vaker opgelost door in de postFlush() een nieuwe persist + flush te doen, waarbij de subscriber zelf ervoor zorgt dat er geen infinite recursie optreedt. Je zou er ook voor kunnen kiezen om daarvoor een aparte Doctrine DBAL connectie te gebruiken die geen weet heeft van je ORM. Daar is ook wel wat voor te zeggen; immers produceer je afgeleide metadata en niet zozeer iets dat per se onderdeel uit moet maken van je relationele model.

Zie https://github.com/zicht/...teAliasSubscriber.php#L91 voor een voorbeeldje van een vergelijkbare strategie en https://github.com/zicht/.../EventSubscriber.php#L113 voor een voorbeeld van een ingewikkeldere die zich ook bezig houdt met de UOW.
Thanks voor het zetje in de juiste richting!

De postslush hetb ik ook aangedacht. Maar dit vond ik inderdaad een minder mooie manier. Als het niet anders had gekunt dan had ik dat gedaan. Maar het kan dus wel anders :)

Het werkt nu!

Heb nu een Trait met de relatie de get en de set. Deze kan ik nu aan een entity toevoegen en dan werkt het direct.
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
   /**
     * @ORM/PostPersist
     */
    public function urlRewritePostPersist(LifecycleEventArgs $args) {
        $em = $args->getEntityManager();
        $entity = $args->getEntity();

        $targetPath = strtolower((new \ReflectionClass($this))->getShortName()).'/'.$entity->getId();

        $entity->getUrlRewrite()->setTargetPath($targetPath);

        $uow = $em->getUnitOfWork();
 
        $uow->recomputeSingleEntityChangeSet(
          $em->getClassMetaData(get_class($entity)),
          $entity
        );
    }

[ Voor 4% gewijzigd door JSchut op 07-11-2017 19:49 ]

PSN jschut_82 | Xbox: JSchut82

Pagina: 1