1: <?php
2:
3: namespace Guzzle\Service\Resource;
4:
5: use Guzzle\Common\AbstractHasDispatcher;
6: use Guzzle\Service\Command\CommandInterface;
7:
8: /**
9: * {@inheritdoc}
10: */
11: abstract class ResourceIterator extends AbstractHasDispatcher implements ResourceIteratorInterface
12: {
13: /**
14: * @var CommandInterface Command used to send requests
15: */
16: protected $command;
17:
18: /**
19: * @var CommandInterface First sent command
20: */
21: protected $originalCommand;
22:
23: /**
24: * @var array Currently loaded resources
25: */
26: protected $resources;
27:
28: /**
29: * @var int Total number of resources that have been retrieved
30: */
31: protected $retrievedCount = 0;
32:
33: /**
34: * @var int Total number of resources that have been iterated
35: */
36: protected $iteratedCount = 0;
37:
38: /**
39: * @var string NextToken/Marker for a subsequent request
40: */
41: protected $nextToken = false;
42:
43: /**
44: * @var int Maximum number of resources to fetch per request
45: */
46: protected $pageSize;
47:
48: /**
49: * @var int Maximum number of resources to retrieve in total
50: */
51: protected $limit;
52:
53: /**
54: * @var int Number of requests sent
55: */
56: protected $requestCount = 0;
57:
58: /**
59: * @var array Initial data passed to the constructor
60: */
61: protected $data = array();
62:
63: /**
64: * @var bool Whether or not the current value is known to be invalid (e.g. when sendRequest() returns 0 resources)
65: */
66: protected $invalid;
67:
68: /**
69: * {@inheritdoc}
70: */
71: public static function getAllEvents()
72: {
73: return array(
74: // About to issue another command to get more results
75: 'resource_iterator.before_send',
76: // Issued another command to get more results
77: 'resource_iterator.after_send'
78: );
79: }
80:
81: /**
82: * This should only be invoked by a {@see ClientInterface} object.
83: *
84: * @param CommandInterface $command Initial command used for iteration
85: *
86: * @param array $data Associative array of additional parameters. You may specify any number of custom options for
87: * an iterator. Among these options, you may also specify the following values:
88: * - limit: Attempt to limit the maximum number of resources to this amount
89: * - page_size: Attempt to retrieve this number of resources per request
90: */
91: public function __construct(CommandInterface $command, array $data = array())
92: {
93: // Clone the command to keep track of the originating command for rewind
94: $this->originalCommand = $command;
95:
96: // Parse options from the array of options
97: $this->data = $data;
98: $this->limit = array_key_exists('limit', $data) ? $data['limit'] : 0;
99: $this->pageSize = array_key_exists('page_size', $data) ? $data['page_size'] : false;
100: }
101:
102: /**
103: * Get all of the resources as an array (Warning: this could issue a large number of requests)
104: *
105: * @return array
106: */
107: public function toArray()
108: {
109: return iterator_to_array($this, false);
110: }
111:
112: /**
113: * {@inheritdoc}
114: */
115: public function setLimit($limit)
116: {
117: $this->limit = $limit;
118: $this->resetState();
119:
120: return $this;
121: }
122:
123: /**
124: * {@inheritdoc}
125: */
126: public function setPageSize($pageSize)
127: {
128: $this->pageSize = $pageSize;
129: $this->resetState();
130:
131: return $this;
132: }
133:
134: /**
135: * Get an option from the iterator
136: *
137: * @param string $key Key of the option to retrieve
138: *
139: * @return mixed|null Returns NULL if not set or the value if set
140: */
141: public function get($key)
142: {
143: return array_key_exists($key, $this->data) ? $this->data[$key] : null;
144: }
145:
146: /**
147: * Set an option on the iterator
148: *
149: * @param string $key Key of the option to set
150: * @param mixed $value Value to set for the option
151: *
152: * @return ResourceIterator
153: */
154: public function set($key, $value)
155: {
156: $this->data[$key] = $value;
157:
158: return $this;
159: }
160:
161: /**
162: * Return the current element.
163: *
164: * @return mixed Returns the current element.
165: */
166: public function current()
167: {
168: return $this->resources ? current($this->resources) : false;
169: }
170:
171: /**
172: * Return the key of the current element.
173: *
174: * @return mixed
175: */
176: public function key()
177: {
178: return max(0, $this->iteratedCount - 1);
179: }
180:
181: /**
182: * Return the total number of items that have been retrieved thus far.
183: *
184: * @return int
185: */
186: public function count()
187: {
188: return $this->retrievedCount;
189: }
190:
191: /**
192: * Get the total number of requests sent
193: *
194: * @return int
195: */
196: public function getRequestCount()
197: {
198: return $this->requestCount;
199: }
200:
201: /**
202: * Rewind the Iterator to the first element and send the original command
203: */
204: public function rewind()
205: {
206: // Use the original command
207: $this->command = clone $this->originalCommand;
208: $this->resetState();
209: $this->next();
210: }
211:
212: /**
213: * Check if there is a current element after calls to rewind() or next().
214: *
215: * @return bool Returns TRUE if the current element is valid or FALSE
216: */
217: public function valid()
218: {
219: return !$this->invalid && (!$this->resources || $this->current() || $this->nextToken)
220: && (!$this->limit || $this->iteratedCount < $this->limit + 1);
221: }
222:
223: /**
224: * Move forward to next element and may trigger subsequent requests
225: */
226: public function next()
227: {
228: $this->iteratedCount++;
229:
230: // Check if a new set of resources needs to be retrieved
231: $sendRequest = false;
232: if (!$this->resources) {
233: $sendRequest = true;
234: } else {
235: // iterate over the internal array
236: $current = next($this->resources);
237: $sendRequest = $current === false && $this->nextToken && (!$this->limit || $this->iteratedCount < $this->limit + 1);
238: }
239:
240: if ($sendRequest) {
241:
242: $this->dispatch('resource_iterator.before_send', array(
243: 'iterator' => $this,
244: 'resources' => $this->resources
245: ));
246:
247: // Get a new command object from the original command
248: $this->command = clone $this->originalCommand;
249: // Send a request and retrieve the newly loaded resources
250: $this->resources = $this->sendRequest();
251: $this->requestCount++;
252:
253: // If no resources were found, then the last request was not needed
254: // and iteration must stop
255: if (empty($this->resources)) {
256: $this->invalid = true;
257: } else {
258: // Add to the number of retrieved resources
259: $this->retrievedCount += count($this->resources);
260: // Ensure that we rewind to the beginning of the array
261: reset($this->resources);
262: }
263:
264: $this->dispatch('resource_iterator.after_send', array(
265: 'iterator' => $this,
266: 'resources' => $this->resources
267: ));
268: }
269: }
270:
271: /**
272: * Retrieve the NextToken that can be used in other iterators.
273: *
274: * @return string Returns a NextToken
275: */
276: public function getNextToken()
277: {
278: return $this->nextToken;
279: }
280:
281: /**
282: * Returns the value that should be specified for the page size for a request that will maintain any hard limits,
283: * but still honor the specified pageSize if the number of items retrieved + pageSize < hard limit
284: *
285: * @return int Returns the page size of the next request.
286: */
287: protected function calculatePageSize()
288: {
289: if ($this->limit && $this->iteratedCount + $this->pageSize > $this->limit) {
290: return 1 + ($this->limit - $this->iteratedCount);
291: }
292:
293: return (int) $this->pageSize;
294: }
295:
296: /**
297: * Reset the internal state of the iterator without triggering a rewind()
298: */
299: protected function resetState()
300: {
301: $this->iteratedCount = 0;
302: $this->retrievedCount = 0;
303: $this->nextToken = false;
304: $this->resources = null;
305: $this->invalid = false;
306: }
307:
308: /**
309: * Send a request to retrieve the next page of results. Hook for subclasses to implement.
310: *
311: * @return array Returns the newly loaded resources
312: */
313: abstract protected function sendRequest();
314: }
315: