ENH Refactor eagerloading fetch into separate methods

It is hard to work with the current structure - having each relation
type in its own method makes it way easier to see where my concerns
start and end with a given relation.

This also reduces the chance of variable bleed-over, where the value in
a variable in the first loop (in the same or other relation type) could
bleed into the next iteration of the loop.
This commit is contained in:
Guy Sartorelli 2023-07-05 12:48:58 +12:00
parent 60ca35c02d
commit 85e503d012
No known key found for this signature in database
GPG Key ID: F313E3B9504D496A

View File

@ -1099,117 +1099,187 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li
}
// has_one
if ($hasOneIDField) {
$itemArray = [];
$relationItemIDs = [];
if ($dataClass === $dataClasses[0]) {
while ($row = $query->record()) {
$itemArray[] = [
'ID' => $row['ID'],
$hasOneIDField => $row[$hasOneIDField]
];
$relationItemIDs[] = $row[$hasOneIDField];
}
} else {
foreach ($prevRelationArray as $itemData) {
$itemArray[] = [
'ID' => $itemData->ID,
$hasOneIDField => $itemData->$hasOneIDField
];
$relationItemIDs[] = $itemData->$hasOneIDField;
}
}
$relationArray = DataObject::get($relationDataClass)->filter(['ID' => $relationItemIDs])->toArray();
foreach ($itemArray as $itemData) {
foreach ($relationArray as $relationItem) {
$eagerLoadID = $itemData['ID'];
if ($relationItem->ID === $itemData[$hasOneIDField]) {
$this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation] = $relationItem;
}
}
}
$prevRelationArray = $relationArray;
$ids = $relationItemIDs;
list($prevRelationArray, $ids) = $this->fetchEagerLoadHasOne(
$query,
$prevRelationArray,
$hasOneIDField,
$relationDataClass,
$eagerLoadRelation,
$relation,
$dataClass,
$dataClasses
);
// belongs_to
} elseif ($belongsToIDField) {
$relationArray = DataObject::get($relationDataClass)->filter([$belongsToIDField => $ids])->toArray();
$relationItemIDs = [];
foreach ($relationArray as $relationItem) {
$relationItemIDs[] = $relationItem->ID;
$eagerLoadID = $relationItem->$belongsToIDField;
$this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation] = $relationItem;
}
$prevRelationArray = $relationArray;
$ids = $relationItemIDs;
list($prevRelationArray, $ids) = $this->fetchEagerLoadBelongsTo(
$ids,
$belongsToIDField,
$relationDataClass,
$eagerLoadRelation,
$relation
);
// has_many
} elseif ($hasManyIDField) {
$relationArray = DataObject::get($relationDataClass)->filter([$hasManyIDField => $ids])->toArray();
$relationItemIDs = [];
foreach ($relationArray as $relationItem) {
$relationItemIDs[] = $relationItem->ID;
$eagerLoadID = $relationItem->$hasManyIDField;
if (!isset($this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation])) {
$arrayList = ArrayList::create();
$arrayList->setDataClass($relationItem->dataClass);
$this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation] = $arrayList;
}
$this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation]->push($relationItem);
}
$prevRelationArray = $relationArray;
$ids = $relationItemIDs;
list($prevRelationArray, $ids) = $this->fetchEagerLoadHasMany(
$ids,
$hasManyIDField,
$relationDataClass,
$eagerLoadRelation,
$relation
);
// many_many + belongs_many_many & many_many_through
} elseif ($manyManyLastComponent) {
$parentField = $manyManyLastComponent['parentField'];
$childField = $manyManyLastComponent['childField'];
// $join will either be:
// - the join table name for many-many
// - the join data class for many-many-through
$join = $manyManyLastComponent['join'];
// many_many_through
if (is_a($manyManyLastComponent['relationClass'], ManyManyThroughList::class, true)) {
$joinThroughObjs = $join::get()->filter([$parentField => $ids]);
$relationItemIDs = [];
$rows = [];
foreach ($joinThroughObjs as $joinThroughObj) {
$rows[] = [
$parentField => $joinThroughObj->$parentField,
$childField => $joinThroughObj->$childField
];
$relationItemIDs[] = $joinThroughObj->$childField;
}
// many_many + belongs_many_many
} else {
$joinTableQuery = DB::query('SELECT * FROM "' . $join . '" WHERE "' . $parentField . '" IN (' . implode(',', $ids) . ')');
$relationItemIDs = [];
$rows = [];
while ($row = $joinTableQuery->record()) {
$rows[] = [
$parentField => $row[$parentField],
$childField => $row[$childField]
];
$relationItemIDs[] = $row[$childField];
}
}
$relationArray = DataObject::get($relationDataClass)->filter(['ID' => $relationItemIDs])->toArray();
foreach ($rows as $row) {
$eagerLoadID = $row[$parentField];
if (!isset($this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation])) {
$arrayList = ArrayList::create();
$arrayList->setDataClass($manyManyLastComponent['childClass']);
$this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation] = $arrayList;
}
$relationItem = array_values(array_filter($relationArray, function ($relationItem) use ($row, $childField) {
return $relationItem->ID === $row[$childField];
}))[0];
$this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation]->push($relationItem);
}
$prevRelationArray = $relationArray;
$ids = $relationItemIDs;
list($prevRelationArray, $ids) = $this->fetchEagerLoadManyMany(
$manyManyLastComponent,
$ids,
$relationDataClass,
$eagerLoadRelation,
$relation
);
} else {
throw new LogicException('Something went wrong with the eager loading');
}
}
}
private function fetchEagerLoadHasOne(
Query $query,
array $prevRelationArray,
string $hasOneIDField,
string $relationDataClass,
string $eagerLoadRelation,
string $relation,
string $dataClass,
array $dataClasses
): array
{
$itemArray = [];
$relationItemIDs = [];
if ($dataClass === $dataClasses[0]) {
while ($row = $query->record()) {
$itemArray[] = [
'ID' => $row['ID'],
$hasOneIDField => $row[$hasOneIDField]
];
$relationItemIDs[] = $row[$hasOneIDField];
}
} else {
foreach ($prevRelationArray as $itemData) {
$itemArray[] = [
'ID' => $itemData->ID,
$hasOneIDField => $itemData->$hasOneIDField
];
$relationItemIDs[] = $itemData->$hasOneIDField;
}
}
$relationArray = DataObject::get($relationDataClass)->filter(['ID' => $relationItemIDs])->toArray();
foreach ($itemArray as $itemData) {
foreach ($relationArray as $relationItem) {
$eagerLoadID = $itemData['ID'];
if ($relationItem->ID === $itemData[$hasOneIDField]) {
$this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation] = $relationItem;
}
}
}
return [$relationArray, $relationItemIDs];
}
private function fetchEagerLoadBelongsTo(
array $ids,
string $belongsToIDField,
string $relationDataClass,
string $eagerLoadRelation,
string $relation
): array
{
$relationArray = DataObject::get($relationDataClass)->filter([$belongsToIDField => $ids])->toArray();
$relationItemIDs = [];
foreach ($relationArray as $relationItem) {
$relationItemIDs[] = $relationItem->ID;
$eagerLoadID = $relationItem->$belongsToIDField;
$this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation] = $relationItem;
}
return [$relationArray, $relationItemIDs];
}
private function fetchEagerLoadHasMany(
array $ids,
string $hasManyIDField,
string $relationDataClass,
string $eagerLoadRelation,
string $relation
): array
{
$relationArray = DataObject::get($relationDataClass)->filter([$hasManyIDField => $ids])->toArray();
$relationItemIDs = [];
foreach ($relationArray as $relationItem) {
$relationItemIDs[] = $relationItem->ID;
$eagerLoadID = $relationItem->$hasManyIDField;
if (!isset($this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation])) {
$arrayList = ArrayList::create();
$arrayList->setDataClass($relationItem->dataClass);
$this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation] = $arrayList;
}
$this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation]->push($relationItem);
}
return [$relationArray, $relationItemIDs];
}
private function fetchEagerLoadManyMany(
array $manyManyLastComponent,
array $ids,
string $relationDataClass,
string $eagerLoadRelation,
string $relation
): array
{
$parentField = $manyManyLastComponent['parentField'];
$childField = $manyManyLastComponent['childField'];
// $join will either be:
// - the join table name for many-many
// - the join data class for many-many-through
$join = $manyManyLastComponent['join'];
// many_many_through
if (is_a($manyManyLastComponent['relationClass'], ManyManyThroughList::class, true)) {
$joinThroughObjs = $join::get()->filter([$parentField => $ids]);
$relationItemIDs = [];
$rows = [];
foreach ($joinThroughObjs as $joinThroughObj) {
$rows[] = [
$parentField => $joinThroughObj->$parentField,
$childField => $joinThroughObj->$childField
];
$relationItemIDs[] = $joinThroughObj->$childField;
}
// many_many + belongs_many_many
} else {
$joinTableQuery = DB::query('SELECT * FROM "' . $join . '" WHERE "' . $parentField . '" IN (' . implode(',', $ids) . ')');
$relationItemIDs = [];
$rows = [];
while ($row = $joinTableQuery->record()) {
$rows[] = [
$parentField => $row[$parentField],
$childField => $row[$childField]
];
$relationItemIDs[] = $row[$childField];
}
}
$relationArray = DataObject::get($relationDataClass)->filter(['ID' => $relationItemIDs])->toArray();
foreach ($rows as $row) {
$eagerLoadID = $row[$parentField];
if (!isset($this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation])) {
$arrayList = ArrayList::create();
$arrayList->setDataClass($manyManyLastComponent['childClass']);
$this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation] = $arrayList;
}
$relationItem = array_values(array_filter($relationArray, function ($relationItem) use ($row, $childField) {
return $relationItem->ID === $row[$childField];
}))[0];
$this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation]->push($relationItem);
}
return [$relationArray, $relationItemIDs];
}
/**
* Eager load relations for DataObjects in this DataList including nested relations
*