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:
149: $key = $this->getOption('key.marker');
150:
151: if (isset($element->$key)) {
152: $this->currentMarker = $element->$key;
153: }
154: }
155:
156: 157: 158: 159:
160: public function rewind()
161: {
162: parent::rewind();
163: $this->currentMarker = null;
164: }
165:
166: public function valid()
167: {
168: if ($this->getOption('limit.total') !== false && $this->position >= $this->getOption('limit.total')) {
169: return false;
170: } elseif (isset($this->elements[$this->position])) {
171: return true;
172: } elseif ($this->shouldAppend() === true) {
173: $before = $this->count();
174: $this->appendNewCollection();
175:
176: return ($this->count() > $before) ? true : false;
177: }
178:
179: return false;
180: }
181:
182: protected function shouldAppend()
183: {
184: return $this->currentMarker && $this->position % $this->getOption('limit.page') == 0;
185: }
186:
187: 188: 189: 190: 191: 192:
193: public function appendElements(array $elements)
194: {
195: $this->elements = array_merge($this->elements, $elements);
196:
197: return $this;
198: }
199:
200: 201: 202: 203: 204: 205:
206: public function appendNewCollection()
207: {
208: $request = $this->resourceParent
209: ->getClient()
210: ->createRequest(
211: $this->getOption('request.method'),
212: $this->constructNextUrl(),
213: $this->getOption('request.headers'),
214: $this->getOption('request.body'),
215: $this->getOption('request.curlOptions')
216: );
217:
218: try {
219: $response = $request->send();
220: } catch (ClientErrorResponseException $e) {
221: return false;
222: }
223:
224: if (!($body = Formatter::decode($response)) || $response->getStatusCode() == 204) {
225: return false;
226: }
227:
228: $this->nextUrl = $this->extractNextLink($body);
229:
230: return $this->appendElements($this->parseResponseBody($body));
231: }
232:
233: 234: 235: 236: 237: 238:
239: public function ($body)
240: {
241: $key = $this->getOption('key.links');
242:
243: $value = null;
244:
245: if (isset($body->$key)) {
246: foreach ($body->$key as $link) {
247: if (isset($link->rel) && $link->rel == 'next') {
248: $value = $link->href;
249: break;
250: }
251: }
252: }
253:
254: return $value;
255: }
256:
257: 258: 259: 260: 261:
262: public function constructNextUrl()
263: {
264: if (!$url = $this->nextUrl) {
265:
266: $url = clone $this->getOption('baseUrl');
267: $query = $url->getQuery();
268:
269: if (isset($this->currentMarker)) {
270: $query[static::MARKER] = $this->currentMarker;
271: }
272:
273: if (($limit = $this->getOption('limit.page')) && !$query->hasKey(static::LIMIT)) {
274: $query[static::LIMIT] = $limit;
275: }
276:
277: $url->setQuery($query);
278: }
279:
280: return $url;
281: }
282:
283: 284: 285: 286: 287: 288:
289: public function parseResponseBody($body)
290: {
291: $collectionKey = $this->getOption('key.collection');
292:
293: $data = array();
294:
295: if (is_array($body)) {
296: $data = $body;
297: } elseif (isset($body->$collectionKey)) {
298: if (null !== ($elementKey = $this->getOption('key.collectionElement'))) {
299:
300: foreach ($body->$collectionKey as $item) {
301: $subValues = $item->$elementKey;
302: unset($item->$elementKey);
303: $data[] = array_merge((array) $item, (array) $subValues);
304: }
305: } else {
306:
307: $data = $body->$collectionKey;
308: }
309: }
310:
311: return $data;
312: }
313:
314: 315: 316:
317: public function populateAll()
318: {
319: while ($this->valid()) {
320: $this->next();
321: }
322: }
323: }
324: