Skip to content

Commit

Permalink
Fix including (#1176)
Browse files Browse the repository at this point in the history
* Fixed duplicate entries in included section and including many to one through intermediate table

* Fixed bug where user endpoint used wrong include code
  • Loading branch information
jessevz authored Jan 27, 2025
1 parent 6297209 commit f41eba5
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 7 deletions.
2 changes: 1 addition & 1 deletion src/inc/apiv2/common/AbstractBaseAPI.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -872,7 +872,7 @@ protected function makeExpandables(Request $request, array $validExpandables): a
protected function getPrimaryKey(): string
{
$features = $this->getFeatures();
# Word-around required since getPrimaryKey is not static in dba/models/*.php
# Work-around required since getPrimaryKey is not static in dba/models/*.php
foreach($features as $key => $value) {
if ($value['pk'] == True) {
return $key;
Expand Down
100 changes: 95 additions & 5 deletions src/inc/apiv2/common/AbstractModelAPI.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,19 @@ protected static function fetchExpandObjects(array $objects, string $expand): mi
$toOneRelationships = static::getToOneRelationships();
if (array_key_exists($expand, $toOneRelationships)) {
$relationFactory = self::getModelFactory($toOneRelationships[$expand]['relationType']);

if (array_key_exists('junctionTableType', $toOneRelationships[$expand])) {
$junctionTableFactory = self::getModelFactory($toOneRelationships[$expand]['junctionTableType']);
return self::getManyToOneRelationViaIntermediate(
$objects,
$toOneRelationships[$expand]['junctionTableJoinField'],
$junctionTableFactory,
$relationFactory,
$toOneRelationships[$expand]['relationKey'],
$toOneRelationships[$expand]['parentKey']
);
};

return self::getForeignKeyRelation(
$objects,
$toOneRelationships[$expand]['key'],
Expand All @@ -73,7 +86,7 @@ protected static function fetchExpandObjects(array $objects, string $expand): mi
/* Associative entity */
if (array_key_exists('junctionTableType', $toManyRelationships[$expand])) {
$junctionTableFactory = self::getModelFactory($toManyRelationships[$expand]['junctionTableType']);
return self::getManyToOneRelationViaIntermediate(
return self::getManyToManyRelationViaIntermediate(
$objects,
$toManyRelationships[$expand]['key'],
$junctionTableFactory,
Expand Down Expand Up @@ -148,6 +161,66 @@ protected function getPrimaryKeyOther(string $dbaClass): string
}
}

/**
* Retrieve ManyToOne relalation for $objects ('parents') of type $targetFactory via 'intermidate'
* of $intermediateFactory joining on $joinField (between 'intermediate' and 'target'). Filtered by
* $filterField at $intermediateFactory.
*
* @param array $objects Objects Fetch relation for selected Objects
* @param string $objectField Field to use as base for $objects
* @param object $intermediateFactory Factory used as intermediate between parentObject and targetObject
* @param string $filterField Filter field of intermadiateObject to filter against $objects field
* @param object $targetFactory Object properties of objects returned
* @param string $joinField Field to connect 'intermediate' to 'target'
* @return array $many2One which is a map where the key is the id of the parent object and the value is an array of the included
* objects that are included for this parent object
*/
//A bit hacky solution to get a to one through an intermediate table, currently only used by tasks to include a hashlist through the taskwrapper
//another solution can be to overwrite fetchExpandObjects() in tasks.routes
final protected static function getManyToOneRelationViaIntermediate(
array $objects,
string $objectField,
object $intermediateFactory,
object $targetFactory,
string $joinField,
string $parentKey
): array {
assert($intermediateFactory instanceof AbstractModelFactory);
assert($targetFactory instanceof AbstractModelFactory);
$many2One = array();

/* Retrieve Parent -> Intermediate -> Target objects */
$objectIds = [];
foreach($objects as $object) {
$kv = $object->getKeyValueDict();
$objectIds[] = $kv[$objectField];
}
$baseFactory = self::getModelFactory(static::getDBAClass());
$qF = new ContainFilter($objectField, $objectIds, $intermediateFactory);
$jF = new JoinFilter($intermediateFactory, $joinField, $joinField);
$jF2 = new JoinFilter($baseFactory, $objectField, $objectField, $intermediateFactory);
$hO = $targetFactory->filter([Factory::FILTER => $qF, Factory::JOIN => [$jF, $jF2]]);

$intermediateObjectList = $hO[$intermediateFactory->getModelName()];
$targetObjectList = $hO[$targetFactory->getModelName()];
$baseObjectList = $hO[$baseFactory->getModelName()];

$intermediateObject = current($intermediateObjectList);
$targetObject = current($targetObjectList);
$baseObject = current($baseObjectList);

while ($intermediateObject && $targetObject && $baseObject) {
$kv = $baseObject->getKeyValueDict();
$many2One[$kv[$parentKey]] = $targetObject;

$intermediateObject = next($intermediateObjectList);
$targetObject = next($targetObjectList);
$baseObject = next($baseObjectList);
}
return $many2One;
}

/**
* Retrieve ForeignKey Relation
*
Expand Down Expand Up @@ -243,9 +316,10 @@ final protected static function getManyToOneRelation(
* @param object $targetFactory Object properties of objects returned
* @param string $joinField Field to connect 'intermediate' to 'target'
* @return array
* @return array $many2many which is a map where the key is the id of the parent object and the value is an array of the included
* objects that are included for this parent object
*/
final protected static function getManyToOneRelationViaIntermediate(
final protected static function getManyToManyRelationViaIntermediate(
array $objects,
string $objectField,
object $intermediateFactory,
Expand Down Expand Up @@ -372,6 +446,22 @@ final protected function ResourceRecordArrayToUpdateArray($data, $parentId)
return $updates;
}

protected static function addToRelatedResources(array $relatedResources, array $relatedResource) {
$alreadyExists = false;
$searchType = $relatedResource["type"];
$searchId = $relatedResource["id"];
foreach ($relatedResources as $resource) {
if ($resource["id"] == $searchId && $resource["type"] == $searchType) {
$alreadyExists = true;
break;
}
}
if (!$alreadyExists) {
$relatedResources[] = $relatedResource;
}
return $relatedResources;
}

/**
* API entry point for requesting multiple objects
*/
Expand Down Expand Up @@ -484,14 +574,14 @@ public static function getManyResources(object $apiClass, Request $request, Resp
$expandResultObject = $expandResult[$expand][$object->getId()];
if (is_array($expandResultObject)) {
foreach ($expandResultObject as $expandObject) {
$includedResources[] = $apiClass->obj2Resource($expandObject);
$includedResources = self::addToRelatedResources($includedResources, $apiClass->obj2Resource($expandObject));
}
} else {
if ($expandResultObject === null) {
// to-only relation which is nullable
continue;
}
$includedResources[] = $apiClass->obj2Resource($expandResultObject);
$includedResources = self::addToRelatedResources($includedResources, $apiClass->obj2Resource($expandResultObject));
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/inc/apiv2/model/tasks.routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ public static function getToOneRelationships(): array {
'intermediateType' => TaskWrapper::class,
'joinField' => Task::TASK_WRAPPER_ID,
'joinFieldRelation' => TaskWrapper::TASK_WRAPPER_ID,

'junctionTableType' => TaskWrapper::class,
'junctionTableFilterField' => TaskWrapper::HASHLIST_ID,
'junctionTableJoinField' => TaskWrapper::TASK_WRAPPER_ID,

'parentKey' => Task::TASK_ID
],
];
}
Expand Down
2 changes: 1 addition & 1 deletion src/inc/apiv2/model/users.routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ protected static function fetchExpandObjects(array $objects, string $expand): mi
/* Expand requested section */
switch($expand) {
case 'accessGroups':
return self::getManyToOneRelationViaIntermediate(
return self::getManyToManyRelationViaIntermediate(
$objects,
User::USER_ID,
Factory::getAccessGroupUserFactory(),
Expand Down

0 comments on commit f41eba5

Please sign in to comment.