| @@ -33,8 +33,9 @@ | ||
| 33 | 33 | $this->client = $client; |
| 34 | 34 | $this->operation = $operation; |
| 35 | 35 | $this->args = $args; |
| 36 | 36 | $this->config = $config; |
| 37 | + MetricsBuilder::appendMetricsCaptureMiddleware($this->client->getHandlerList(), MetricsBuilder::PAGINATOR); | |
| 37 | 38 | } |
| 38 | 39 | /** |
| 39 | 40 | * Runs a paginator asynchronously and uses a callback to handle results. |
| 40 | 41 | * |
| @@ -91,18 +92,27 @@ | ||
| 91 | 92 | public function current() |
| 92 | 93 | { |
| 93 | 94 | return $this->valid() ? $this->result : \false; |
| 94 | 95 | } |
| 96 | + /** | |
| 97 | + * @return mixed | |
| 98 | + */ | |
| 95 | 99 | #[\ReturnTypeWillChange] |
| 96 | 100 | public function key() |
| 97 | 101 | { |
| 98 | 102 | return $this->valid() ? $this->requestCount - 1 : null; |
| 99 | 103 | } |
| 104 | + /** | |
| 105 | + * @return void | |
| 106 | + */ | |
| 100 | 107 | #[\ReturnTypeWillChange] |
| 101 | 108 | public function next() |
| 102 | 109 | { |
| 103 | 110 | $this->result = null; |
| 104 | 111 | } |
| 112 | + /** | |
| 113 | + * @return bool | |
| 114 | + */ | |
| 105 | 115 | #[\ReturnTypeWillChange] |
| 106 | 116 | public function valid() |
| 107 | 117 | { |
| 108 | 118 | if ($this->result) { |
| @@ -108,15 +118,28 @@ | ||
| 108 | 118 | if ($this->result) { |
| 109 | 119 | return \true; |
| 110 | 120 | } |
| 111 | 121 | if ($this->nextToken || !$this->requestCount) { |
| 122 | + //Forward/backward paging can result in a case where the last page's nextforwardtoken | |
| 123 | + //is the same as the one that came before it. This can cause an infinite loop. | |
| 124 | + $hasBidirectionalPaging = $this->config['output_token'] === 'nextForwardToken'; | |
| 125 | + if ($hasBidirectionalPaging && $this->nextToken) { | |
| 126 | + $tokenKey = $this->config['input_token']; | |
| 127 | + $previousToken = $this->nextToken[$tokenKey]; | |
| 128 | + } | |
| 112 | 129 | $this->result = $this->client->execute($this->createNextCommand($this->args, $this->nextToken)); |
| 113 | 130 | $this->nextToken = $this->determineNextToken($this->result); |
| 131 | + if (isset($previousToken) && $previousToken === $this->nextToken[$tokenKey]) { | |
| 132 | + return \false; | |
| 133 | + } | |
| 114 | 134 | $this->requestCount++; |
| 115 | 135 | return \true; |
| 116 | 136 | } |
| 117 | 137 | return \false; |
| 118 | 138 | } |
| 139 | + /** | |
| 140 | + * @return void | |
| 141 | + */ | |
| 119 | 142 | #[\ReturnTypeWillChange] |
| 120 | 143 | public function rewind() |
| 121 | 144 | { |
| 122 | 145 | $this->requestCount = 0; |
| @@ -122,9 +145,9 @@ | ||
| 122 | 145 | $this->requestCount = 0; |
| 123 | 146 | $this->nextToken = null; |
| 124 | 147 | $this->result = null; |
| 125 | 148 | } |
| 126 | - private function createNextCommand(array $args, array $nextToken = null) | |
| 149 | + private function createNextCommand(array $args, ?array $nextToken = null) | |
| 127 | 150 | { |
| 128 | 151 | return $this->client->getCommand($this->operation, \array_merge($args, $nextToken ?: [])); |
| 129 | 152 | } |
| 130 | 153 | private function determineNextToken(Result $result) |