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