1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:
17:
18: namespace OpenCloud\Common\Collection;
19:
20: use Guzzle\Http\Exception\ClientErrorResponseException;
21: use Guzzle\Http\Url;
22: use Iterator;
23: use OpenCloud\Common\Http\Message\Formatter;
24:
25: 26: 27: 28: 29: 30: 31: 32:
33: class PaginatedIterator extends ResourceIterator implements Iterator
34: {
35: const MARKER = 'marker';
36: const LIMIT = 'limit';
37:
38: 39: 40:
41: protected $currentMarker;
42:
43: 44: 45:
46: protected $nextUrl;
47:
48: protected $defaults = array(
49:
50: 'limit.total' => 10000,
51: 'limit.page' => 100,
52:
53:
54: 'key.links' => 'links',
55:
56:
57: 'key.collection' => null,
58: 'key.collectionElement' => null,
59:
60:
61: 'key.marker' => 'name',
62:
63:
64: 'request.method' => 'GET',
65: 'request.headers' => array(),
66: 'request.body' => null,
67: 'request.curlOptions' => array()
68: );
69:
70: protected $required = array('resourceClass', 'baseUrl');
71:
72: 73: 74: 75: 76: 77: 78: 79:
80: public static function factory($parent, array $options = array(), array $data = null)
81: {
82: $list = new static();
83:
84: $list->setOptions($list->parseOptions($options))
85: ->setResourceParent($parent)
86: ->rewind();
87:
88: if ($data) {
89: $list->setElements($data);
90: } else {
91: $list->appendNewCollection();
92: }
93:
94: return $list;
95: }
96:
97:
98: 99: 100: 101:
102: public function setBaseUrl(Url $url)
103: {
104: $this->baseUrl = $url;
105:
106: return $this;
107: }
108:
109: public function current()
110: {
111: return parent::current();
112: }
113:
114: public function key()
115: {
116: return parent::key();
117: }
118:
119: 120: 121: 122:
123: public function next()
124: {
125: if (!$this->valid()) {
126: return false;
127: }
128:
129: $current = $this->current();
130:
131: $this->position++;
132: $this->updateMarkerToCurrent();
133:
134: return $current;
135: }
136:
137: 138: 139: 140:
141: public function updateMarkerToCurrent()
142: {
143: if (!isset($this->elements[$this->position])) {
144: return;
145: }
146:
147: $element = $this->elements[$this->position];
148: $this->setMarkerFromElement($element);
149: }
150:
151: protected function setMarkerFromElement($element)
152: {
153: $key = $this->getOption('key.marker');
154:
155: if (isset($element->$key)) {
156: $this->currentMarker = $element->$key;
157: }
158: }
159:
160: 161: 162: 163:
164: public function rewind()
165: {
166: parent::rewind();
167: $this->currentMarker = null;
168: }
169:
170: public function valid()
171: {
172: $totalLimit = $this->getOption('limit.total');
173: if ($totalLimit !== false && $this->position >= $totalLimit) {
174: return false;
175: } elseif (isset($this->elements[$this->position])) {
176: return true;
177: } elseif ($this->shouldAppend() === true) {
178: $before = $this->count();
179: $this->appendNewCollection();
180: return ($this->count() > $before) ? true : false;
181: }
182:
183: return false;
184: }
185:
186: protected function shouldAppend()
187: {
188: return $this->currentMarker && (
189: $this->nextUrl ||
190: $this->position % $this->getOption('limit.page') == 0
191: );
192: }
193:
194: 195: 196: 197: 198: 199:
200: public function appendElements(array $elements)
201: {
202: $this->elements = array_merge($this->elements, $elements);
203:
204: return $this;
205: }
206:
207: 208: 209: 210: 211: 212:
213: public function appendNewCollection()
214: {
215: $request = $this->resourceParent
216: ->getClient()
217: ->createRequest(
218: $this->getOption('request.method'),
219: $this->constructNextUrl(),
220: $this->getOption('request.headers'),
221: $this->getOption('request.body'),
222: $this->getOption('request.curlOptions')
223: );
224:
225: try {
226: $response = $request->send();
227: } catch (ClientErrorResponseException $e) {
228: return false;
229: }
230:
231: if (!($body = Formatter::decode($response)) || $response->getStatusCode() == 204) {
232: return false;
233: }
234:
235: $this->nextUrl = $this->extractNextLink($body);
236:
237: return $this->appendElements($this->parseResponseBody($body));
238: }
239:
240: 241: 242: 243: 244: 245:
246: public function ($body)
247: {
248: $key = $this->getOption('key.links');
249:
250: $value = null;
251:
252: if (isset($body->$key)) {
253: foreach ($body->$key as $link) {
254: if (isset($link->rel) && $link->rel == 'next') {
255: $value = $link->href;
256: break;
257: }
258: }
259: }
260:
261: return $value;
262: }
263:
264: 265: 266: 267: 268:
269: public function constructNextUrl()
270: {
271: if (!$url = $this->nextUrl) {
272: $url = clone $this->getOption('baseUrl');
273: $query = $url->getQuery();
274:
275: if (isset($this->currentMarker)) {
276: $query[static::MARKER] = $this->currentMarker;
277: }
278:
279: if (($limit = $this->getOption('limit.page')) && !$query->hasKey(static::LIMIT)) {
280: $query[static::LIMIT] = $limit;
281: }
282:
283: $url->setQuery($query);
284: }
285:
286: return $url;
287: }
288:
289: 290: 291: 292: 293: 294:
295: public function parseResponseBody($body)
296: {
297: $collectionKey = $this->getOption('key.collection');
298:
299: $data = array();
300:
301: if (is_array($body)) {
302: $data = $body;
303: } elseif (isset($body->$collectionKey)) {
304: if (null !== ($elementKey = $this->getOption('key.collectionElement'))) {
305:
306: foreach ($body->$collectionKey as $item) {
307: $subValues = $item->$elementKey;
308: unset($item->$elementKey);
309: $data[] = array_merge((array) $item, (array) $subValues);
310: }
311: } else {
312:
313: $data = $body->$collectionKey;
314: }
315: }
316:
317: return $data;
318: }
319:
320: 321: 322:
323: public function populateAll()
324: {
325: while ($this->valid()) {
326: $this->next();
327: }
328: }
329: }
330: