MM-Relation in Repository abfragen

  • Typo3Cuckoo Typo3Cuck...
    Sternenflotten-Admiral
    0 x
    184 Beiträge
    0 Hilfreiche Beiträge
    30. 05. 2011, 17:37

    Hi,

    ich möchte in der Ausgabe (Frontend) die Datensätze filtern. Dazu habe ich eine m:n Relation zwischen zwei Domain Models erstellt (Car und Category). Was ich noch nicht verstehe ist: wie kann ich im Repository nach dieser Relation abfragen?

    Hab folgendes in [b]CarRepository.php[/b] eingetragen, jedoch funktioniert das nicht:

    1. <?php
    2. /**
    3.  * Finds Cars of a specific Category
    4.  *
    5.  * @param Tx_Extbase_Persistence_ObjectStorage<Tx_MyExt_Domain_Model_Category> $category
    6.  *
    7.  * @return Tx_MyExt_Domain_Model_Car
    8.  */
    9. public function findByCategory($category) {
    10. t3lib_div::debug($category, '$category');
    11. $query = $this->createQuery();
    12. $query->matching($query->equals('categories', $category));
    13. $query->setOrderings(array('crdate' => Tx_Extbase_Persistence_QueryInterface::ORDER_DESCENDING));
    14. return $query->execute();
    15. }
    16. ?>

    Das Datenbankfeld [b]categories[/b] gehört zum Domain Model [b]Car[/b]. Darin steht ja nur die Anzahl der verknüpften Kategorien. Die eigentliche Verknüpfung zwischen einem [b]Car[/b] und einer [b]Category[/b] steht ja in der Tabelle [b]tx_myext_car_category_mm[/b]. Wie kann ich diese im Repository von [b]Car[/b] abfragen?

    Die Debug-Ausgabe von $category (erste Zeile in der Funktion) gibt mir die UID der übergebenen Kategorie aus. Das müsste man doch irgendwie als [b]uid_foreign[/b] nutzen können, aber wie?

    Vielen Dank schon mal!


  • Typo3Cuckoo Typo3Cuck...
    Sternenflotten-Admiral
    0 x
    184 Beiträge
    0 Hilfreiche Beiträge
    31. 05. 2011, 09:47

    Mittlerweile bin ich etwas weiter (wäre aber dennoch schön, wenn erfahrenere Leute mit helfen könnten :-))

    Ich hab mir die [b]ObjectStorage[/b] Klasse von extbase mal zur Brust genommen und die Methode [b]Tx_Extbase_Persistence_ObjectStorage::contains()[/b] gefunden. Damit hab ich es nun hingekriegt, meine [b]Car[/b] Objekte nach der übergebenen [b]Category[/b] zu filtern

    CarRepository.php:

    1. <?php
    2. /**
    3.  * Finds Cars of a specific Category
    4.  *
    5.  * @param Tx_MyExt_Domain_Model_Category $category
    6.  *
    7.  * @return Tx_MyExt_Domain_Model_Car
    8.  */
    9. public function findByCategory($category) {
    10. $cars = $this->findAll();
    11. foreach($cars as $key => $val) {
    12. if(!$val->getCategories()->contains($category)) {
    13. unset($discounts[$key]);
    14. }
    15. }
    16. return $cars;
    17. }
    18. ?>

    In der [b]CarController[/b] Klasse wird die Repository-Funktion wie folgt aufgerufen:

    CarController.php:

    1. <?php
    2. /**
    3.  * List all cars
    4.  *
    5.  * @param Tx_MyExt_Domain_Model_Category $category
    6.  *
    7.  * @return string The rendered HTML string
    8.  */
    9. public function listAction($category=NULL) {
    10. $allCars = $this->carRepository->findAll();
    11. if($category!==NULL) {
    12. $allCars = $this->carRepository->findByCategory($category);
    13. }
    14. }
    15. ?>

    Der aufmerksame Leser wird erkennen, dass ich die @param Annotation des Parameters $category verändert habe. In dem oberen Beitrag hab ich ja geschrieben, dass ich durch die @param Annotation im oberen Beitrag lediglich die UID der Kategorie erhalten habe. Für die Funktion "contains()" brauch ich jedoch das gesamte Object. Wieso ich nur die UID erhalten habe weiß ich bisher noch nicht. Vielleicht kann mir das einer erklären? :)

    Das funktioniert nun so wie ich es möchte.

    Jedoch hab ich dazu einige Fragen:

    - Ist das der "richtige Weg" um mit MM Relationen umzugehen?
    - Muss ich überhaupt darauf achten, dass ich es mit einer solchen Relation zwischen zwei Objekten zu tun habe oder übernimmt das das Data Mapping für mich und ich kann hier nach Herzenslust mit den Objekten hantieren?
    - Wenn ich einen Datensatz der Klasse "Car" im Backend lösche, dann bleibt der Eintrag in der MM-Tabelle zwischen Car und Category bestehen? Wieso wird das nicht automatisch mitgelöscht?

  • oliver_g oliver_g
    R2-D2
    0 x
    71 Beiträge
    0 Hilfreiche Beiträge
    09. 06. 2011, 11:19

    Hi, ich hab momentan das gleiche Problem. Ich bekomme nur die Uids, nicht aber die ganzen Objekte. Ich denke, das ganze Repository-Konzept kann man vergessen bzgl. Relationen.

    Außerdem ist das alles nicht besonders performant, da anstatt einer Verbundabfrage viele Einzelabfragen durchgeführt werden.

    Ich denke, die "Abstraktion" von einem bestimmten SQL System bringt einem in der Praxis sowieso nichts, schon gar nicht bei Typo3. Daher werde ich die Relationssachen mit der $query->statement()-Methode umsetzen. Dann kann ich meinen Join schreiben, die Datenbank holt sich die Daten recht effizient.

    Nicht umsonst hat Zend das Model in seinem Framework weggelassen, es bringt einfach nichts, von einer Datenbank zu abstrahieren und es gibt ca. 1 Million Möglichkeiten ein Modell umzusetzen. Das Repository-Konzept hat da wohl eher akademischen Nutzen.

    Gruß

  • oliver_g oliver_g
    R2-D2
    0 x
    71 Beiträge
    0 Hilfreiche Beiträge
    10. 06. 2011, 08:09

    So, ich habe bei den zurückgelieferten Ids einfach noch mal ein "findByUid" durchgeführt, damit hab ich zumindestens die Datensätze. Ist zwar nicht performant aber dafür bin ich noch nicht auf SQL-Ebene hinangestiegen.

    Also ungefähr so:

    class.ObjectYRepository:

    1. public function findByObjectX(Tx_Myext_Domain_Model_ObjectX $objX,) {
    2.  
    3.  
    4. $query = $this->createQuery();
    5. $query->getQuerySettings()->setRespectStoragePage(FALSE);
    6. $query->matching(
    7. $query->logicalAnd(
    8. $query->contains('objXLink', $objX),
    9. $query->equals('pid', $this->storagePid),
    10.  
    11. )
    12. );
    13.  
    14. $query->setLimit($this->maximumRecords);
    15.  
    16. return $this->findByUid($query->execute());
    17. }

    Gruß
    Oliver

  • Typo3Cuckoo Typo3Cuck...
    Sternenflotten-Admiral
    0 x
    184 Beiträge
    0 Hilfreiche Beiträge
    18. 10. 2011, 15:39

    Hi,

    ich bin schon wieder an einem ähnlichen Problem dran.

    Um nur kurz auf meinen ersten Beitrag in diesem Thema zurückzukommen, der Fehler lag (aus heutiger Sicht) bei der Query Abfrage mit [b]equals[/b]. Ich hätte da schon mit [b]contains[/b] arbeiten sollen, dann wäre das Problem gelöst und ich müsste nicht den Umweg über for-Schleifen machen.

    Sollte also so funktionieren:

    1. <?php
    2. /**
    3.  *
    4.  * Returns all objects of this repository with matching category
    5.  *
    6.  * @param Tx_MyExt_Domain_Model_Category $category
    7.  *
    8.  * @return Tx_Extbase_Persistence_QueryResult
    9.  */
    10. public function findByCategory(Tx_MyExt_Domain_Model_Category $category) {
    11. $query = $this->createQuery();
    12. $query->matching($query->contains('categories', $category));
    13. $result = $query->execute();
    14. return $result;
    15. }
    16. ?>

    So, jetzt komm ich zu meinem neuen Problem:

    die hier beschriebene Lösung funktioniert, wenn man nur auf eine einzige Beziehung prüft, d.h. [b]$category[/b] darf nur ein Domain Model beinhalten. Sobald ich jedoch auf zwei oder mehr Kategorien prüfen möchten, funktioniert das nicht mehr.

    Beispielanwendung:

    Ich habe mehrere Bilder, welche jeweils mehreren Kategorien zugeordnet sind.
    Nun möchte ich alle Bilder ausgeben, welche in Kategorie 1 und in Kategorie 2 sind.

    Momentan mache ich das so, dass ich mir über das BildRepository alle Bilder hole und dann jedes einzelne Bild auf Kategoriezugehörigkeit prüfe. Das ist jedoch nicht sehr schön und es muss bestimmt besser gehen.

    Das ganze sieht etwa so aus (Bild = Media):

    1. <?php
    2. public function listAction() {
    3. // gewünschte Kategorien holen
    4. $selectedCategories = $this->categoryRepository->findByUids(array(1, 6))->toArray();
    5.  
    6. // Alle Bilder holen
    7. $medias = $this->mediaRepository->findAll()->toArray();
    8.  
    9. // Jedes Bild einzeln prüfen
    10. foreach($medias as $key => $media) {
    11. foreach($selectedCategories as $category) {
    12. // Entfernt das Bild sobald mindestens eine Kategorie fehlt
    13. if(!$media->getCategory()->contains($category))
    14. unset($medias[$key]);
    15. }
    16. }
    17. $this->view->assign('categories', $selectedCategories);
    18. $this->view->assign('medias', $medias);
    19. }
    20. ?>

    Bei mehrerern tausend Bildern kann das ganz schön dauern. Also keine gute Lösung!

    Wie kann ich schon im Repository auf mehrere MM-Relationen prüfen?

    Danke für eure Hilfe :)

  • freshman17 freshman1...
    Sternenflotten-Admiral
    0 x
    217 Beiträge
    2 Hilfreiche Beiträge
    18. 10. 2011, 16:26

    Du kannst die "statement"-Methode des Query-Objektes benutzen und damit auf den vollen Sprachumfang von SQL zurückgreifen.
    Das Mapping wird für dich ebenfalls übernommen (auf jeden Fall ab extbase 1.3) aber nur für das korrespondente Model (in diesem Fall Tx_MyExt_Domain_Model_Category).

    Einen Nachteil hat es und zwar wird die "statement"-Methode in FLOW3 nicht unterstützt.

    1. class Tx_MyExt_Domain_Repository_CategoryRepository extends Tx_Extbase_Persistence_Repository {
    2.  
    3. /**
    4. * findImagesByFoo
    5. *
    6. * @return Tx_Extbase_Persistence_QueryResult
    7. */
    8. public function findImagesByFoo() {
    9. $sql = 'SELECT DISTINCT category.* FROM tx_myext_domain_model_category AS category
    10. ......
    11. WHERE image = ....';
    12. return $this->createQuery()->statement($sql)->execute();
    13. }
    14.  
    15. }

  • Typo3Cuckoo Typo3Cuck...
    Sternenflotten-Admiral
    0 x
    184 Beiträge
    0 Hilfreiche Beiträge
    18. 10. 2011, 16:31

    Hi,

    danke für deinen Vorschlag, aber Statements sind keine gute Lösung. Ich möchte nicht auf SQL Basis arbeiten müssen. Außerdem wird das, wie du schon sagst, nicht voll unterstützt. Statements sind eher eine Notlösung.

    Gibts da nichts anderes?

  • freshman17 freshman1...
    Sternenflotten-Admiral
    0 x
    217 Beiträge
    2 Hilfreiche Beiträge
    18. 10. 2011, 16:49

    evtl. könntest du es mit der Pfadschreibweise lösen

    in Tx_MyExt_Domain_Repository_CategoryRepository:

    1. $query->equals('media.category', 'bar');

    Ansonsten nur die Listen-Lösung die du jetzt schon hast oder die Statements, da gibt es sonst nichts!

  • Typo3Cuckoo Typo3Cuck...
    Sternenflotten-Admiral
    0 x
    184 Beiträge
    0 Hilfreiche Beiträge
    18. 10. 2011, 16:57

    Hmm, ist zwar nicht das was ich erhofft habe aber wenns wirklich nicht anders geht, dann kann ich auch nix machen.

    Danke dir! :)

  • FazzyX FazzyX
    Padawan
    0 x
    49 Beiträge
    0 Hilfreiche Beiträge
    01. 06. 2012, 14:56

    Ich hatte ein ähnliches Problem, welches ich wie folgt lösen konnten.

    Zunächst muss die Deklaration vom Attribute Kategorie stimmen.

    1. /**
    2.   * category
    3.   * @var Tx_Extbase_Persistence_ObjectStorage<Tx_MyExt_Domain_Model_Category>
    4.   */
    5. protected $category;

    Der findByCategories Methode im Repository wird eine Liste mit Kategorie Objekten übergeben.
    Daraus wird das Query erstellt.

    1. /**
    2.   *
    3.   * @param array $categories of Tx_MyExt_Domain_Model_Category objects
    4.   * @return Tx_MyExt_Domain_Model_Car
    5.   */
    6. public function findByCategories($categories) {
    7.  
    8. $query = $this->createQuery();
    9.  
    10. foreach ($categories as $category) {
    11. $constraint[] = $query->contains('category', $category);
    12. }
    13.  
    14. $result = $query->matching($query->logicalAnd($query->logicalAnd($constraint)))->execute();
    15.  
    16. if (count($result) > 0) {
    17. return $result;
    18. }
    19. }

    Daraus ergibt sich dann ein Kombinationsfilter.

    Grüße Claus