Zum Inhalt springen

MM-Relation in Repository abfragen

Erstellt am 30. Mai 2011 · 12 Antworten · letzte Antwort am 30. Oktober 2016

Tags: Datenbank, Frage

Unbekannter Benutzer ·

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 CarRepository.php eingetragen, jedoch funktioniert das nicht:

<?php
/**
 * Finds Cars of a specific Category
 * 
 * @param Tx_Extbase_Persistence_ObjectStorage<Tx_MyExt_Domain_Model_Category> $category
 * 
 * @return Tx_MyExt_Domain_Model_Car
 */
public function findByCategory($category) {
    t3lib_div::debug($category, '$category');
    $query = $this->createQuery();
	$query->matching($query->equals('categories', $category));
	$query->setOrderings(array('crdate' => Tx_Extbase_Persistence_QueryInterface::ORDER_DESCENDING));
	return $query->execute();
}
?>

Das Datenbankfeld categories gehört zum Domain Model Car. Darin steht ja nur die Anzahl der verknüpften Kategorien. Die eigentliche Verknüpfung zwischen einem Car und einer Category steht ja in der Tabelle tx_myext_car_category_mm. Wie kann ich diese im Repository von Car 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 uid_foreign nutzen können, aber wie?

Vielen Dank schon mal!

Unbekannter Benutzer ·

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

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

CarRepository.php:

<?php
/**
 * Finds Cars of a specific Category
 * 
 * @param Tx_MyExt_Domain_Model_Category $category
 * 
 * @return Tx_MyExt_Domain_Model_Car
 */
public function findByCategory($category) {
    $cars = $this->findAll();
    foreach($cars as $key => $val) {
      if(!$val->getCategories()->contains($category)) {
        unset($discounts[$key]);
      }
    }
    return $cars;
}
?>

In der CarController Klasse wird die Repository-Funktion wie folgt aufgerufen:

CarController.php:

<?php
/**
 * List all cars
 * 
 * @param Tx_MyExt_Domain_Model_Category $category
 * 
 * @return string The rendered HTML string
 */
public function listAction($category=NULL) {
    $allCars = $this->carRepository->findAll();
    if($category!==NULL) {
      $allCars = $this->carRepository->findByCategory($category);
    }
}
?>

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 ·

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 ·

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:

public function findByObjectX(Tx_Myext_Domain_Model_ObjectX $objX,) {
		
		
		$query = $this->createQuery();
		$query->getQuerySettings()->setRespectStoragePage(FALSE);
		$query->matching(
				$query->logicalAnd(
					$query->contains('objXLink', $objX),
					$query->equals('pid', $this->storagePid),
					
				)
		);
		
		$query->setLimit($this->maximumRecords);
		
		return $this->findByUid($query->execute());
	}

Gruß
Oliver

Unbekannter Benutzer ·

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 equals. Ich hätte da schon mit contains arbeiten sollen, dann wäre das Problem gelöst und ich müsste nicht den Umweg über for-Schleifen machen.

Sollte also so funktionieren:

<?php
/**
 * 
 * Returns all objects of this repository with matching category
 * 
 * @param Tx_MyExt_Domain_Model_Category $category
 * 
 * @return Tx_Extbase_Persistence_QueryResult
 */
public function findByCategory(Tx_MyExt_Domain_Model_Category $category) {
	$query = $this->createQuery();
	$query->matching($query->contains('categories', $category));
	$result = $query->execute();
	return $result;
}
?>

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. $category 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):

<?php
public function listAction() {
	// gewünschte Kategorien holen
	$selectedCategories = $this->categoryRepository->findByUids(array(1, 6))->toArray();
	
	// Alle Bilder holen
	$medias = $this->mediaRepository->findAll()->toArray();
	
	// Jedes Bild einzeln prüfen
	foreach($medias as $key => $media) {
		foreach($selectedCategories as $category) {
			// Entfernt das Bild sobald mindestens eine Kategorie fehlt
			if(!$media->getCategory()->contains($category))
				unset($medias[$key]);
		}
	}
	$this->view->assign('categories', $selectedCategories);
	$this->view->assign('medias', $medias);
}
?>

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 ·

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.

class Tx_MyExt_Domain_Repository_CategoryRepository extends Tx_Extbase_Persistence_Repository {

	/**
	 * findImagesByFoo
	 *
	 * @return Tx_Extbase_Persistence_QueryResult
	 */
	public function findImagesByFoo() {
		$sql = 'SELECT DISTINCT category.* FROM tx_myext_domain_model_category AS category
				......
				WHERE image = ....';
		return $this->createQuery()->statement($sql)->execute();
	}

}
Unbekannter Benutzer ·

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 ·

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

in Tx_MyExt_Domain_Repository_CategoryRepository:

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

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

FazzyX ·

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

Zunächst muss die Deklaration vom Attribute Kategorie stimmen.

/**
  * category
  * @var Tx_Extbase_Persistence_ObjectStorage<Tx_MyExt_Domain_Model_Category>
  */
protected $category;

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

/**
  * 
  * @param array $categories of Tx_MyExt_Domain_Model_Category objects
  * @return Tx_MyExt_Domain_Model_Car 
  */
public function findByCategories($categories) {

        $query = $this->createQuery();

        foreach ($categories as $category) {
                $constraint[] = $query->contains('category', $category);
        }

        $result = $query->matching($query->logicalAnd($query->logicalAnd($constraint)))->execute();

        if (count($result) > 0) {
                return $result;
        }
}

Daraus ergibt sich dann ein Kombinationsfilter.

Grüße Claus

qualle85 ·
Typo3Cuckoo schrieb

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

Sollte also so funktionieren:

<?php
/**
 * 
 * Returns all objects of this repository with matching category
 * 
 * @param Tx_MyExt_Domain_Model_Category $category
 * 
 * @return Tx_Extbase_Persistence_QueryResult
 */
public function findByCategory(Tx_MyExt_Domain_Model_Category $category) {
	$query = $this->createQuery();
	$query->matching($query->contains('categories', $category));
	$result = $query->execute();
	return $result;
}
?>

Hi,

zuerst mal danke, dass du deine Lösung geteilt hast. Stehe vor einem ähnlichem Problem (1:n) aber komme nicht weiter. Erhalte die Meldung "Unsupported relation for contains" bei folgender Funktion:

/**
    * Finds Labeltemplates of a specific Baseproduct
    * 
    * @param Tx_Extbase_Persistence_ObjectStorage<xxx_Domain_Model_Baseproduct> $baseproduct
    * 
    * @return xxxLabelgenerator_Domain_Model_Labeltemplate
    */
    public function findByCategory(xxx_Domain_Model_Baseproduct $baseproduct) {
        $query = $this->createQuery();
        return $query->matching($query->contains('baseproducts', $baseproduct))->execute();
    }

Habe nun als Workaround eine Query geschrieben

public function findByCategoryRaw($baseproduct) {
        $query = $this->createQuery();
        return $query->statement('SELECT * FROM xxx_domain_model_labeltemplate WHERE FIND_IN_SET('.intval($baseproduct).', baseproducts);')->execute();
    }

aber das ist ja auch nicht Sinn der Sache.

Was habe ich denn vergessen zu konfigurieren? Hier mein TCA an der Stelle von xxx_domain_model_labeltemplate (child):

'baseproducts' => array(
			'exclude' => 1,
			'label' => 'LLL:EXT:...',
			'config' => array(
                            'type' => 'group',
                            'internal_type' => 'db',
                            'allowed' => 'xxx_domain_model_baseproduct', // parent
                            'minitems' => '0',
                            'maxitems' => '50',
                        ),
		),

Und im Modell von Baseproduct (parent):

public function getLabeltemplates() {
            if (!self::$labeltemplatesRepository) {
                self::$labeltemplatesRepository = t3lib_div::makeInstance('xxxLabelgenerator_Domain_Repository_Labeltemplaterepository');
            }
            #return self::$labeltemplatesRepository->findAll(); // works
            return self::$labeltemplatesRepository->findByCategory($this); // doesn't work
        }

Danke für Tips!

mchenzi ·

hallo
weis jemand ob das immernoch so ist oder obe das der extension BUilder heutzutage die Klassen dafür erstellt?
dank.