Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"microsoft/kiota-serialization-text": "^0.2.0",
"microsoft/kiota-abstractions": "^0.2.0",
"php-http/httplug": "^2.2",
"php-http/guzzle7-adapter": "^1.0"
"php-http/guzzle7-adapter": "^1.0",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^9.0",
Expand Down
39 changes: 39 additions & 0 deletions src/Models/PageResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace Microsoft\Graph\Core\Models;

class PageResult
{
/** @var string|null $odataNextLink */
private ?string $odataNextLink;
/** @var array<mixed>|null $value */
private ?array $value;

/**
* @return string|null
*/
public function getOdataNextLink(): ?string {
return $this->odataNextLink;
}

/**
* @return array<mixed>|null
*/
public function getValue(): ?array {
return $this->value;
}

/**
* @param string|null $nextLink
*/
public function setOdataNextLink(?string $nextLink): void{
$this->odataNextLink = $nextLink;
}

/**
* @param array|null $value
*/
public function setValue(?array $value): void {
$this->value = $value;
}
}
187 changes: 187 additions & 0 deletions src/Tasks/PageIterator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
<?php

namespace Microsoft\Graph\Core\Tasks;

use Exception;
use Http\Promise\FulfilledPromise;
use Http\Promise\Promise;
use Http\Promise\RejectedPromise;
use InvalidArgumentException;
use JsonException;
use Microsoft\Graph\Core\Models\PageResult;
use Microsoft\Kiota\Abstractions\HttpMethod;
use Microsoft\Kiota\Abstractions\NativeResponseHandler;
use Microsoft\Kiota\Abstractions\RequestAdapter;
use Microsoft\Kiota\Abstractions\RequestInformation;
use Microsoft\Kiota\Abstractions\RequestOption;
use Microsoft\Kiota\Abstractions\Serialization\Parsable;

class PageIterator
{
private PageResult $currentPage;
private RequestAdapter $requestAdapter;
private bool $hasNext = false;
private int $pauseIndex;
/** @var array{string, string} $constructorFunc */
private array $constructorCallable;
private array $headers = [];
/** @var array<RequestOption>|null */
private ?array $requestOptions = [];

/**
* @param Parsable|array|object $response paged collection response
* @param RequestAdapter $requestAdapter
* @param array{string,string} $constructorCallable The method to construct a paged response object.
* @throws JsonException
*/
public function __construct($response, RequestAdapter $requestAdapter, array $constructorCallable) {
$this->requestAdapter = $requestAdapter;
$this->constructorCallable = $constructorCallable;
$this->pauseIndex = 0;
$page = self::convertToPage($response);

if ($page !== null) {
$this->currentPage = $page;
$this->hasNext = true;
}
$this->headers = [];
}

/**
* @param array $headers
*/
public function setHeaders(array $headers): void
{
$this->headers = $headers;
}

/**
* @param array $requestOptions
*/
public function setRequestOptions(array $requestOptions): void
{
$this->requestOptions = $requestOptions;
}

/**
* @param int $pauseIndex
*/
public function setPauseIndex(int $pauseIndex): void
{
$this->pauseIndex = $pauseIndex;
}

/**
* @param callable(Parsable|array|object): bool $callback The callback function to apply on every entity. Pauses iteration if false is returned
* @throws Exception
*/
public function iterate(callable $callback): void {
while(true) {
$keepIterating = $this->enumerate($callback);

if (!$keepIterating) {
return;
}
$nextPage = $this->next();

if (empty($nextPage)) {
$this->hasNext = false;
return;
}
$this->currentPage = $nextPage;
$this->pauseIndex = 0;
}
}

/**
* @throws Exception
*/
public function next(): ?PageResult {
if (empty($this->currentPage->getOdataNextLink())) {
return null;
}

$response = $this->fetchNextPage();
$result = $response->wait();
return self::convertToPage($result);
}

/**
* @param $response
* @return PageResult|null
* @throws JsonException
*/
public static function convertToPage($response): ?PageResult {
$page = new PageResult();
if ($response === null) {
throw new InvalidArgumentException('$response cannot be null');
}

if (is_array($response)) {
$value = $response['value'];
} else if(is_a($response, Parsable::class) && method_exists($response, 'getValue')) {
$value = $response->getValue();
} else {
$value = $response->value;
}

if ($value === null) {
throw new InvalidArgumentException('The response does not contain a value.');
}

$parsablePage = is_a($response, Parsable::class) ? $response : json_decode(json_encode($response,JSON_THROW_ON_ERROR), true);
if (is_array($parsablePage)) {
$page->setOdataNextLink($parsablePage['@odata.nextLink'] ?? '');
} else {
$page->setOdataNextLink($parsablePage->getOdataNextLink());
}
$page->setValue($value);
return $page;
}
private function fetchNextPage(): ?Promise {
/** @var Parsable $graphResponse */
$graphResponse = null;

$nextLink = $this->currentPage->getOdataNextLink();

if ($nextLink === null) {
return new RejectedPromise(new InvalidArgumentException('The response does not have a nextLink'));
}

if (!filter_var($nextLink, FILTER_VALIDATE_URL)) {
throw new InvalidArgumentException('Could not parse the nextLink url.');
}

$requestInfo = new RequestInformation();
$requestInfo->httpMethod = HttpMethod::GET;
$requestInfo->setUri($nextLink);
$requestInfo->headers = $this->headers;
if ($this->requestOptions !== null) {
$requestInfo->addRequestOptions(...$this->requestOptions);
}

return $this->requestAdapter->sendAsync($requestInfo, $this->constructorCallable);
}

public function enumerate(?callable $callback): ?bool {
$keepIterating = true;

$pageItems = $this->currentPage->getValue();
if (empty($pageItems)) {
return false;
}
for ($i = $this->pauseIndex; $i < count($pageItems); $i++){
$keepIterating = $callback($pageItems[$i]);

if (!$keepIterating) {
$this->pauseIndex = $i + 1;
break;
}
}
return $keepIterating;
}

public function hasNext(): bool {
return $this->hasNext;
}
}
Loading