Skip to content

Commit

Permalink
Merge pull request #71 from stopfstedt/batch_requests_by_ids_m44
Browse files Browse the repository at this point in the history
implements request batching
  • Loading branch information
jrjohnson authored Jan 6, 2025
2 parents 40f913a + 7358660 commit 1976184
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 81 deletions.
143 changes: 86 additions & 57 deletions classes/ilios.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ class ilios {
*/
const API_BASE_PATH = '/api/v3/';

/**
* @var int The maximum number of filtering request parameters in a given API request.
*/
const MAX_FILTER_PARAMS = 250;

/**
* @var string The API access token.
*/
Expand Down Expand Up @@ -79,8 +84,7 @@ public function __construct(
* @throws moodle_exception
*/
public function get_schools(array $filterby = [], array $sortby = []): array {
$response = $this->get('schools', $filterby, $sortby);
return $response->schools;
return $this->get('schools', 'schools', $filterby, $sortby);
}

/**
Expand All @@ -93,8 +97,7 @@ public function get_schools(array $filterby = [], array $sortby = []): array {
* @throws moodle_exception
*/
public function get_cohorts(array $filterby = [], array $sortby = []): array {
$response = $this->get('cohorts', $filterby, $sortby);
return $response->cohorts;
return $this->get('cohorts', 'cohorts', $filterby, $sortby);
}

/**
Expand All @@ -107,8 +110,7 @@ public function get_cohorts(array $filterby = [], array $sortby = []): array {
* @throws moodle_exception
*/
public function get_programs(array $filterby = [], array $sortby = []): array {
$response = $this->get('programs', $filterby, $sortby);
return $response->programs;
return $this->get('programs', 'programs', $filterby, $sortby);
}

/**
Expand All @@ -121,8 +123,7 @@ public function get_programs(array $filterby = [], array $sortby = []): array {
* @throws moodle_exception
*/
public function get_program_years(array $filterby = [], array $sortby = []): array {
$response = $this->get('programyears', $filterby, $sortby);
return $response->programYears;
return $this->get('programyears', 'programYears', $filterby, $sortby);
}

/**
Expand All @@ -135,8 +136,7 @@ public function get_program_years(array $filterby = [], array $sortby = []): arr
* @throws moodle_exception
*/
public function get_learner_groups(array $filterby = [], array $sortby = []): array {
$response = $this->get('learnergroups', $filterby, $sortby);
return $response->learnerGroups;
return $this->get('learnergroups', 'learnerGroups', $filterby, $sortby);
}

/**
Expand All @@ -149,8 +149,7 @@ public function get_learner_groups(array $filterby = [], array $sortby = []): ar
* @throws moodle_exception
*/
public function get_instructor_groups(array $filterby = [], array $sortby = []): array {
$response = $this->get('instructorgroups', $filterby, $sortby);
return $response->instructorGroups;
return $this->get('instructorgroups', 'instructorGroups', $filterby, $sortby);
}

/**
Expand All @@ -163,8 +162,7 @@ public function get_instructor_groups(array $filterby = [], array $sortby = []):
* @throws moodle_exception
*/
public function get_offerings(array $filterby = [], array $sortby = []): array {
$response = $this->get('offerings', $filterby, $sortby);
return $response->offerings;
return $this->get('offerings', 'offerings', $filterby, $sortby);
}

/**
Expand All @@ -177,8 +175,7 @@ public function get_offerings(array $filterby = [], array $sortby = []): array {
* @throws moodle_exception
*/
public function get_ilms(array $filterby = [], array $sortby = []): array {
$response = $this->get('ilmsessions', $filterby, $sortby);
return $response->ilmSessions;
return $this->get('ilmsessions', 'ilmSessions', $filterby, $sortby);
}

/**
Expand All @@ -191,8 +188,7 @@ public function get_ilms(array $filterby = [], array $sortby = []): array {
* @throws moodle_exception
*/
public function get_users(array $filterby = [], array $sortby = []): array {
$response = $this->get('users', $filterby, $sortby);
return $response->users;
return $this->get('users', 'users', $filterby, $sortby);
}

/**
Expand All @@ -204,11 +200,7 @@ public function get_users(array $filterby = [], array $sortby = []): array {
* @throws moodle_exception
*/
public function get_school(int $id): ?object {
$response = $this->get_by_id('schools', $id);
if ($response) {
return $response->schools[0];
}
return null;
return $this->get_by_id('schools', 'schools', $id);
}

/**
Expand All @@ -220,11 +212,7 @@ public function get_school(int $id): ?object {
* @throws moodle_exception
*/
public function get_cohort(int $id): ?object {
$response = $this->get_by_id('cohorts', $id);
if ($response) {
return $response->cohorts[0];
}
return null;
return $this->get_by_id('cohorts', 'cohorts', $id);
}

/**
Expand All @@ -236,11 +224,7 @@ public function get_cohort(int $id): ?object {
* @throws moodle_exception
*/
public function get_program(int $id): ?object {
$response = $this->get_by_id('programs', $id);
if ($response) {
return $response->programs[0];
}
return null;
return $this->get_by_id('programs', 'programs', $id);
}

/**
Expand All @@ -252,11 +236,7 @@ public function get_program(int $id): ?object {
* @throws moodle_exception
*/
public function get_learner_group(int $id): ?object {
$response = $this->get_by_id('learnergroups', $id);
if ($response) {
return $response->learnerGroups[0];
}
return null;
return $this->get_by_id('learnergroups', 'learnerGroups', $id);
}

/**
Expand Down Expand Up @@ -342,54 +322,50 @@ public function get_instructor_ids_from_learner_group(int $groupid): array {
return array_values($instructorids);
}


/**
* Sends a GET request to a given API endpoint with given options.
*
* @param string $path The target path fragment of the API request URL. May include query parameters.
* @param string $path The target path fragment of the API request URL.
* @param string $key The name of the property that holds the requested data points in the payload.
* @param array $filterby An associative array of filter options.
* @param array $sortby An associative array of sort options.
* @return object The decoded response body.
* @return array The data points from the decoded payload.
* @throws GuzzleException
* @throws moodle_exception
*/
public function get(
string $path,
string $key,
array $filterby = [],
array $sortby = [],
): object {
): array {
$this->validate_access_token($this->accesstoken);
$options = ['headers' => ['X-JWT-Authorization' => 'Token ' . $this->accesstoken]];

$url = $this->apibaseurl . $path;

// Construct query params from given filters and sort orders.
// Unfortunately, <code>http_build_query()</code> doesn't cut it here, so we have to hand-roll this.
$queryparams = [];
$filterparams = [];
if (!empty($filterby)) {
foreach ($filterby as $param => $value) {
if (is_array($value)) {
foreach ($value as $val) {
$queryparams[] = "filters[$param][]=$val";
$filterparams[] = "filters[$param][]=$val";
}
} else {
$queryparams[] = "filters[$param]=$value";
$filterparams[] = "filters[$param]=$value";
}
}
}

$sortparams = [];
if (!empty($sortby)) {
foreach ($sortby as $param => $value) {
$queryparams[] = "order_by[$param]=$value";
$sortparams[] = "order_by[$param]=$value";
}
}

$url = $this->apibaseurl . $path;

if (!empty($queryparams)) {
$url .= '?' . implode('&', $queryparams);
}

$response = $this->httpclient->get($url, $options);
return $this->parse_result($response->getBody());
return $this->send_get_request($url, $key, $options, $filterparams, $sortparams);
}

/**
Expand All @@ -415,6 +391,7 @@ public static function get_access_token_payload(string $accesstoken): array {
* Retrieves a given resource from Ilios by its given ID.
*
* @param string $path The URL path fragment that names the resource.
* @param string $key The name of the property that holds the requested data points in the payload.
* @param int $id The ID.
* @param bool $returnnullonnotfound If TRUE then NULL is returned if the resource cannot be found.
* On FALSE, an exception is raised on 404/Not-Found.
Expand All @@ -423,9 +400,10 @@ public static function get_access_token_payload(string $accesstoken): array {
* @throws GuzzleException
* @throws moodle_exception
*/
public function get_by_id(string $path, int $id, bool $returnnullonnotfound = true): ?object {
public function get_by_id(string $path, string $key, int $id, bool $returnnullonnotfound = true): ?object {
try {
return $this->get($path . '/' . $id);
$response = $this->get($path . '/' . $id, $key);
return $response[0];
} catch (ClientException $e) {
if ($returnnullonnotfound && (404 === $e->getResponse()->getStatusCode())) {
return null;
Expand All @@ -435,6 +413,57 @@ public function get_by_id(string $path, int $id, bool $returnnullonnotfound = tr
}
}

/**
* Internal helper method for sending a GET request to the Ilios API.
*
* @param string $url The API request URL. May include query parameters.
* @param string $key The name of the property that holds the requested data points in the payload.
* @param array $options API Client options, such as header values.
* @param array $filterparams An associative array of filtering request parameters.
* @param array $sortparams An associative array of sorting request parameters.
* @return array The data points from the decoded payload.
* @throws GuzzleException
* @throws moodle_exception
*/
protected function send_get_request(
string $url,
string $key,
array $options = [],
array $filterparams = [],
array $sortparams = []
): array {
// Batch processing comes first.
// If the number of request parameters exceeds the given limit,
// we must chunk them up into smaller lists and run then individually, followed by results aggregation.
// This is done by recursively calling this method with chunked filtering options.
if (count($filterparams) > self::MAX_FILTER_PARAMS) {
$batches = array_chunk($filterparams, self::MAX_FILTER_PARAMS);
$results = [];
foreach ($batches as $batch) {
$result = $this->send_get_request($url, $key, $options, $batch, $sortparams);
$results = array_merge($results, $result);
}
return $results;
}

$queryparams = array_merge($filterparams, $sortparams);

if (!empty($queryparams)) {
$url .= '?' . implode('&', $queryparams);
}

$response = $this->httpclient->get($url, $options);
$rhett = $this->parse_result($response->getBody());
if (!property_exists($rhett, $key)) {
throw new moodle_exception(
'errorresponseentitynotfound',
'enrol_ilios',
a: $key,
);
}
return $rhett->$key;
}

/**
* Decodes and returns the given JSON-encoded input.
*
Expand Down
Loading

0 comments on commit 1976184

Please sign in to comment.