Skip to content

Commit 5d599d3

Browse files
authored
Merge pull request #440 from Art4/436-redundant-request
Fix redundant requests `AbstractApi::retrieveData()`
2 parents c94d01b + 07b5153 commit 5d599d3

File tree

3 files changed

+78
-14
lines changed

3 files changed

+78
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515

1616
### Changed
1717

18+
- Improvement in `Redmine\Client\AbstractApi::retrieveData()` by using `total_count` from redmine response to avoid unnecessary http requests.
1819
- Behaviour-driven tests are run against Redmine 6.0.2, 5.1.4, 5.0.10.
1920

2021
### Deprecated

src/Redmine/Api/AbstractApi.php

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -320,18 +320,21 @@ protected function retrieveData(string $endpoint, array $params = []): array
320320

321321
$returnData = [];
322322

323-
$limit = $params['limit'];
323+
// Redmine max limit is 100,
324+
// @see https://www.redmine.org/projects/redmine/wiki/Rest_api#Collection-resources-and-pagination
325+
$redmineLimit = 100;
326+
$requestedLimit = $remaininglimit = $params['limit'];
324327
$offset = $params['offset'];
325328

326-
while ($limit > 0) {
327-
if ($limit > 100) {
328-
$_limit = 100;
329-
$limit -= 100;
329+
while ($remaininglimit > 0) {
330+
if ($remaininglimit > $redmineLimit) {
331+
$realLimit = $redmineLimit;
332+
$remaininglimit -= $redmineLimit;
330333
} else {
331-
$_limit = $limit;
332-
$limit = 0;
334+
$realLimit = $remaininglimit;
335+
$remaininglimit = 0;
333336
}
334-
$params['limit'] = $_limit;
337+
$params['limit'] = $realLimit;
335338
$params['offset'] = $offset;
336339

337340
$this->lastResponse = $this->getHttpClient()->request(HttpFactory::makeRequest(
@@ -344,16 +347,34 @@ protected function retrieveData(string $endpoint, array $params = []): array
344347

345348
$returnData = array_merge_recursive($returnData, $newDataSet);
346349

347-
$offset += $_limit;
350+
// After the first request we know the total_count for this endpoint
351+
// so lets use the total_count to correct $requestedLimit to save us
352+
// from making unnecessary requests
353+
// e.g. total_count = 5 and $requestedLimit = 500 will make only 1 request instead of 2
354+
if (isset($newDataSet['total_count']) && $newDataSet['total_count'] < $requestedLimit) {
355+
$requestedLimit = $remaininglimit = (int) $newDataSet['total_count'];
356+
357+
if ($remaininglimit > $redmineLimit) {
358+
$realLimit = $redmineLimit;
359+
$remaininglimit -= $redmineLimit;
360+
} else {
361+
$realLimit = $remaininglimit;
362+
$remaininglimit = 0;
363+
}
364+
}
365+
366+
$offset += $realLimit;
348367

349368
if (
350-
empty($newDataSet) || !isset($newDataSet['limit']) || (
351-
isset($newDataSet['offset']) &&
352-
isset($newDataSet['total_count']) &&
353-
$newDataSet['offset'] >= $newDataSet['total_count']
369+
empty($newDataSet)
370+
|| !isset($newDataSet['limit'])
371+
|| (
372+
isset($newDataSet['offset'])
373+
&& isset($newDataSet['total_count'])
374+
&& $newDataSet['offset'] >= $newDataSet['total_count']
354375
)
355376
) {
356-
$limit = 0;
377+
$remaininglimit = 0;
357378
}
358379
}
359380

tests/Unit/Api/AbstractApiTest.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,48 @@ public static function retrieveDataData(): array
398398
];
399399
}
400400

401+
public function testRetrieveDataWith100ResultsMakes1Request(): void
402+
{
403+
$response1 = $this->createStub(Response::class);
404+
$response1->method('getContentType')->willReturn('application/json');
405+
$response1->method('getContent')->willReturn('{"total_count":100,"offset":0,"limit":100,"data":[]}');
406+
407+
$client = $this->createMock(HttpClient::class);
408+
$client->expects($this->once())->method('request')->willReturn($response1);
409+
410+
$api = new class ($client) extends AbstractApi {};
411+
412+
$method = new ReflectionMethod($api, 'retrieveData');
413+
$method->setAccessible(true);
414+
415+
$method->invoke($api, '/data.json', ['limit' => 101]);
416+
}
417+
418+
public function testRetrieveDataWith250ResultsMakes3Requests(): void
419+
{
420+
$response1 = $this->createStub(Response::class);
421+
$response1->method('getContentType')->willReturn('application/json');
422+
$response1->method('getContent')->willReturn('{"total_count":250,"offset":0,"limit":100,"data":[]}');
423+
424+
$response2 = $this->createStub(Response::class);
425+
$response2->method('getContentType')->willReturn('application/json');
426+
$response2->method('getContent')->willReturn('{"total_count":250,"offset":100,"limit":100,"data":[]}');
427+
428+
$response3 = $this->createStub(Response::class);
429+
$response3->method('getContentType')->willReturn('application/json');
430+
$response3->method('getContent')->willReturn('{"total_count":250,"offset":200,"limit":100,"data":[]}');
431+
432+
$client = $this->createMock(HttpClient::class);
433+
$client->expects($this->exactly(3))->method('request')->willReturnOnConsecutiveCalls($response1, $response2, $response3);
434+
435+
$api = new class ($client) extends AbstractApi {};
436+
437+
$method = new ReflectionMethod($api, 'retrieveData');
438+
$method->setAccessible(true);
439+
440+
$method->invoke($api, '/data.json', ['limit' => 301]);
441+
}
442+
401443
/**
402444
* @dataProvider getRetrieveDataToExceptionData
403445
*/

0 commit comments

Comments
 (0)