Overview

Namespaces

  • None
  • OpenCloud
    • Autoscale
      • Resource
    • CloudMonitoring
      • Exception
      • Resource
    • Common
      • Exceptions
      • Log
      • Request
        • Response
    • Compute
    • Database
    • DNS
    • LoadBalancer
      • Resources
    • ObjectStore
      • Resource
    • Orchestration
    • Volume
  • PHP

Classes

  • Base
  • Collection
  • Lang
  • Metadata
  • Nova
  • PersistentObject
  • Service
  • ServiceCatalogItem
  • Overview
  • Namespace
  • Class
  • Tree
  • Download
  1: <?php
  2: 
  3: namespace OpenCloud\Common;
  4: 
  5: /**
  6:  * Provides an abstraction for working with ordered sets of objects
  7:  *
  8:  * Collection objects are used whenever there are multiples; for example,
  9:  * multiple objects in a container, or multiple servers in a service.
 10:  *
 11:  * @since 1.0
 12:  * @author Glen Campbell <glen.campbell@rackspace.com>
 13:  * @author Jamie Hannaford <jamie.hannaford@rackspace.com>
 14:  */
 15: class Collection extends Base 
 16: {
 17: 
 18:     private $service;
 19:     private $itemclass;
 20:     private $itemlist = array();
 21:     private $pointer = 0;
 22:     private $sortkey;
 23:     private $next_page_class;
 24:     private $next_page_callback;
 25:     private $next_page_url;
 26: 
 27:     /**
 28:      * A Collection is an array of objects
 29:      *
 30:      * Some assumptions:
 31:      * * The `Collection` class assumes that there exists on its service
 32:      *   a factory method with the same name of the class. For example, if
 33:      *   you create a Collection of class `Foobar`, it will attempt to call
 34:      *   the method `parent::Foobar()` to create instances of that class.
 35:      * * It assumes that the factory method can take an array of values, and
 36:      *   it passes that to the method.
 37:      *
 38:      * @param Service $service - the service associated with the collection
 39:      * @param string $itemclass - the Class of each item in the collection
 40:      *      (assumed to be the name of the factory method)
 41:      * @param array $arr - the input array
 42:      */
 43:     public function __construct($service, $itemclass, $array) 
 44:     {
 45:         $this->service = $service;
 46: 
 47:         $this->getLogger()->info(
 48:             'Collection:service={class}, class={itemClass}, array={array}', 
 49:             array(
 50:                 'class'     => get_class($service), 
 51:                 'itemClass' => $itemclass, 
 52:                 'array'     => print_r($array, true)
 53:             )
 54:         );
 55: 
 56:         $this->next_page_class = $itemclass;
 57: 
 58:         if (false !== ($classNamePos = strrpos($itemclass, '\\'))) {
 59:             $this->itemclass = substr($itemclass, $classNamePos + 1);
 60:         } else {
 61:             $this->itemclass = $itemclass;
 62:         }
 63: 
 64:         if (!is_array($array)) {
 65:             throw new Exceptions\CollectionError(
 66:                 Lang::translate('Cannot create a Collection without an array')
 67:             );
 68:         }
 69: 
 70:         // save the array of items
 71:         $this->setItemList($array);
 72:     }
 73:     
 74:     /**
 75:      * Set the entire data array.
 76:      * 
 77:      * @param array $array
 78:      */
 79:     public function setItemList(array $array)
 80:     {
 81:         $this->itemlist = $array;
 82:     }
 83:     
 84:     /**
 85:      * Retrieve the entire data array.
 86:      * 
 87:      * @return array
 88:      */
 89:     public function getItemList()
 90:     {
 91:         return $this->itemlist;
 92:     }
 93:     
 94:     /**
 95:      * Returns the number of items in the collection
 96:      *
 97:      * For most services, this is the total number of items. If the Collection
 98:      * is paginated, however, this only returns the count of items in the
 99:      * current page of data.
100:      * 
101:      * @return int
102:      */
103:     public function count()
104:     {
105:         return count($this->itemlist);
106:     }
107:     
108:     /**
109:      * Pseudonym for count()
110:      * 
111:      * @codeCoverageIgnore
112:      */
113:     public function size() 
114:     {
115:         return $this->count();
116:     }
117: 
118:     /**
119:      * Retrieves the service associated with the Collection
120:      *
121:      * @return Service
122:      */
123:     public function service() 
124:     {
125:         return $this->service;
126:     }
127: 
128:     /**
129:      * Resets the pointer to the beginning, but does NOT return the first item
130:      *
131:      * @api
132:      * @return void
133:      */
134:     public function reset() 
135:     {
136:         $this->pointer = 0;
137:     }
138: 
139:     /**
140:      * Resets the collection pointer back to the first item in the page
141:      * and returns it
142:      *
143:      * This is useful if you're only interested in the first item in the page.
144:      *
145:      * @api
146:      * @return Base the first item in the set
147:      */
148:     public function first() 
149:     {
150:         $this->reset();
151:         return $this->next();
152:     }
153: 
154:     /**
155:      * Returns the next item in the page
156:      *
157:      * @api
158:      * @return Base the next item or FALSE if at the end of the page
159:      */
160:     public function next() 
161:     {
162:         if ($this->pointer >= $this->count()) {
163:             return false;
164:         }
165:         
166:         $service = $this->service();
167:         
168:         if (method_exists($service, $this->itemclass)) {
169:             return $service->{$this->itemclass}($this->itemlist[$this->pointer++]);
170:         } elseif (method_exists($service, 'resource')) {
171:             return $service->resource($this->itemclass, $this->itemlist[$this->pointer++]);
172:         }
173:         // @codeCoverageIgnoreStart
174:         return false;
175:         // @codeCoverageIgnoreEnd
176:     }
177: 
178:     /**
179:      * sorts the collection on a specified key
180:      *
181:      * Note: only top-level keys can be used as the sort key. Note that this
182:      * only sorts the data in the current page of the Collection (for
183:      * multi-page data).
184:      *
185:      * @api
186:      * @param string $keyname the name of the field to use as the sort key
187:      * @return void
188:      */
189:     public function sort($keyname = 'id') 
190:     {
191:         $this->sortkey = $keyname;
192:         usort($this->itemlist, array($this, 'sortCompare'));
193:     }
194: 
195:     /**
196:      * selects only specified items from the Collection
197:      *
198:      * This provides a simple form of filtering on Collections. For each item
199:      * in the collection, it calls the callback function, passing it the item.
200:      * If the callback returns `TRUE`, then the item is retained; if it returns
201:      * `FALSE`, then the item is deleted from the collection.
202:      *
203:      * Note that this should not supersede server-side filtering; the
204:      * `Collection::Select()` method requires that *all* of the data for the
205:      * Collection be retrieved from the server before the filtering is
206:      * performed; this can be very inefficient, especially for large data
207:      * sets. This method is mostly useful on smaller-sized sets.
208:      *
209:      * Example:
210:      * <code>
211:      * $services = $connection->ServiceList();
212:      * $services->Select(function($item){ return $item->region=='ORD';});
213:      * // now the $services Collection only has items from the ORD region
214:      * </code>
215:      *
216:      * `Select()` is *destructive*; that is, it actually removes entries from
217:      * the collection. For example, if you use `Select()` to find items with
218:      * the ID > 10, then use it again to find items that are <= 10, it will
219:      * return an empty list.
220:      *
221:      * @api
222:      * @param callable $testfunc a callback function that is passed each item
223:      *      in turn. Note that `Select()` performs an explicit test for
224:      *      `FALSE`, so functions like `strpos()` need to be cast into a
225:      *      boolean value (and not just return the integer).
226:      * @returns void
227:      * @throws DomainError if callback doesn't return a boolean value
228:      */
229:     public function select($testfunc) 
230:     {
231:         foreach ($this->getItemList() as $index => $item) {
232:             $test = call_user_func($testfunc, $item);
233:             if (!is_bool($test)) {
234:                 throw new Exceptions\DomainError(
235:                     Lang::translate('Callback function for Collection::Select() did not return boolean')
236:                 );
237:             }
238:             if ($test === false) {
239:                 unset($this->itemlist[$index]);
240:             }
241:         }
242:     }
243: 
244:     /**
245:      * returns the Collection object for the next page of results, or
246:      * FALSE if there are no more pages
247:      *
248:      * Generally, the structure for a multi-page collection will look like
249:      * this:
250:      *
251:      *      $coll = $obj->Collection();
252:      *      do {
253:      *          while($item = $coll->Next()) {
254:      *              // do something with the item
255:      *          }
256:      *      } while ($coll = $coll->NextPage());
257:      *
258:      * @api
259:      * @return Collection if there are more pages of results, otherwise FALSE
260:      */
261:     public function nextPage() 
262:     {
263:         if (isset($this->next_page_url)) {
264:             return call_user_func(
265:                 $this->next_page_callback,
266:                 $this->next_page_class,
267:                 $this->next_page_url
268:             );
269:         }
270:         // @codeCoverageIgnoreStart
271:         return false;
272:         // @codeCoverageIgnoreEnd
273:     }
274: 
275:     /**
276:      * for paginated collection, sets the callback function and URL for
277:      * the next page
278:      *
279:      * The callback function should have the signature:
280:      *
281:      *      function Whatever($class, $url, $parent)
282:      *
283:      * and the `$url` should be the URL of the next page of results
284:      *
285:      * @param callable $callback the name of the function (or array of
286:      *      object, function name)
287:      * @param string $url the URL of the next page of results
288:      * @return void
289:      */
290:     public function setNextPageCallback($callback, $url) 
291:     {
292:         $this->next_page_callback = $callback;
293:         $this->next_page_url = $url;
294:     }
295: 
296:     /**
297:      * Compares two values of sort keys
298:      */
299:     private function sortCompare($a, $b) 
300:     {
301:         $key = $this->sortkey;
302: 
303:         // handle strings with strcmp()
304:         if (is_string($a->$key)) {
305:             return strcmp($a->$key, $b->$key);
306:         }
307: 
308:         // handle others with logical comparisons
309:         if ($a->$key == $b->$key) {
310:             return 0;
311:         }
312: 
313:         if ($a->$key < $b->$key) {
314:             return -1;
315:         } else {
316:             return 1;
317:         }
318:     }
319: 
320: }
321: 
PHP OpenCloud API API documentation generated by ApiGen 2.8.0