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:  * An abstraction that defines persistent objects associated with a service
  4:  *
  5:  * @copyright 2012-2013 Rackspace Hosting, Inc.
  6:  * See COPYING for licensing information
  7:  *
  8:  * @package phpOpenCloud
  9:  * @version 1.0
 10:  * @author Glen Campbell <glen.campbell@rackspace.com>
 11:  * @author Jamie Hannaford <jamie.hannaford@rackspace.com>
 12:  */
 13: 
 14: namespace OpenCloud\Common;
 15: 
 16: /**
 17:  * Represents an object that can be retrieved, created, updated and deleted.
 18:  *
 19:  * This class abstracts much of the common functionality between: 
 20:  *  
 21:  *  * Nova servers;
 22:  *  * Swift containers and objects;
 23:  *  * DBAAS instances;
 24:  *  * Cinder volumes;
 25:  *  * and various other objects that:
 26:  *    * have a URL;
 27:  *    * can be created, updated, deleted, or retrieved;
 28:  *    * use a standard JSON format with a top-level element followed by 
 29:  *      a child object with attributes.
 30:  *
 31:  * In general, you can create a persistent object class by subclassing this
 32:  * class and defining some protected, static variables:
 33:  * 
 34:  *  * $url_resource - the sub-resource value in the URL of the parent. For
 35:  *    example, if the parent URL is `http://something/parent`, then setting this
 36:  *    value to "another" would result in a URL for the persistent object of 
 37:  *    `http://something/parent/another`.
 38:  *
 39:  *  * $json_name - the top-level JSON object name. For example, if the
 40:  *    persistent object is represented by `{"foo": {"attr":value, ...}}`, then
 41:  *    set $json_name to "foo".
 42:  *
 43:  *  * $json_collection_name - optional; this value is the name of a collection
 44:  *    of the persistent objects. If not provided, it defaults to `json_name`
 45:  *    with an appended "s" (e.g., if `json_name` is "foo", then
 46:  *    `json_collection_name` would be "foos"). Set this value if the collection 
 47:  *    name doesn't follow this pattern.
 48:  *
 49:  *  * $json_collection_element - the common pattern for a collection is:
 50:  *    `{"collection": [{"attr":"value",...}, {"attr":"value",...}, ...]}`
 51:  *    That is, each element of the array is a \stdClass object containing the
 52:  *    object's attributes. In rare instances, the objects in the array
 53:  *    are named, and `json_collection_element` contains the name of the
 54:  *    collection objects. For example, in this JSON response:
 55:  *    `{"allowedDomain":[{"allowedDomain":{"name":"foo"}}]}`,
 56:  *    `json_collection_element` would be set to "allowedDomain".
 57:  *
 58:  * The PersistentObject class supports the standard CRUD methods; if these are 
 59:  * not needed (i.e. not supported by  the service), the subclass should redefine 
 60:  * these to call the `noCreate`, `noUpdate`, or `noDelete` methods, which will 
 61:  * trigger an appropriate exception. For example, if an object cannot be created:
 62:  *
 63:  *    function create($params = array()) 
 64:  *    { 
 65:  *       $this->noCreate(); 
 66:  *    }
 67:  */
 68: abstract class PersistentObject extends Base
 69: {
 70:       
 71:     private $service;
 72:     
 73:     private $parent;
 74:     
 75:     protected $id; 
 76: 
 77:     /**
 78:      * Retrieves the instance from persistent storage
 79:      *
 80:      * @param mixed $service The service object for this resource
 81:      * @param mixed $info    The ID or array/object of data
 82:      */
 83:     public function __construct($service = null, $info = null)
 84:     {
 85:         if ($service instanceof Service) {
 86:             $this->setService($service);
 87:         }
 88:         
 89:         if (property_exists($this, 'metadata')) {
 90:             $this->metadata = new Metadata;
 91:         }
 92:         
 93:         $this->populate($info);
 94:     }
 95:     
 96:     /**
 97:      * Validates properties that have a namespace: prefix
 98:      *
 99:      * If the property prefix: appears in the list of supported extension
100:      * namespaces, then the property is applied to the object. Otherwise,
101:      * an exception is thrown.
102:      *
103:      * @param string $name the name of the property
104:      * @param mixed $value the property's value
105:      * @return void
106:      * @throws AttributeError
107:      */
108:     public function __set($name, $value)
109:     {
110:         $this->setProperty($name, $value, $this->getService()->namespaces());
111:     }
112:     
113:     /**
114:      * Sets the service associated with this resource object.
115:      * 
116:      * @param \OpenCloud\Common\Service $service
117:      */
118:     public function setService(Service $service)
119:     {
120:         $this->service = $service;
121:         return $this;
122:     }
123:     
124:     /**
125:      * Returns the service object for this resource; required for making
126:      * requests, etc. because it has direct access to the Connection.
127:      * 
128:      * @return \OpenCloud\Common\Service
129:      */
130:     public function getService()
131:     {
132:         if (null === $this->service) {
133:             throw new Exceptions\ServiceValueError(
134:                 'No service defined'
135:             );
136:         }
137:         return $this->service;
138:     }
139:     
140:     /**
141:      * Legacy shortcut to getService
142:      * 
143:      * @return \OpenCloud\Common\Service
144:      */
145:     public function service()
146:     {
147:         return $this->getService();
148:     }
149:     
150:     /**
151:      * Set the parent object for this resource.
152:      * 
153:      * @param \OpenCloud\Common\PersistentObject $parent
154:      */
155:     public function setParent(PersistentObject $parent)
156:     {
157:         $this->parent = $parent;
158:         return $this;
159:     }
160:     
161:     /**
162:      * Returns the parent.
163:      * 
164:      * @return \OpenCloud\Common\PersistentObject
165:      */
166:     public function getParent()
167:     {
168:         if (null === $this->parent) {
169:             $this->parent = $this->getService();
170:         }
171:         return $this->parent;
172:     }
173:     
174:     /**
175:      * Legacy shortcut to getParent
176:      * 
177:      * @return \OpenCloud\Common\PersistentObject
178:      */
179:     public function parent()
180:     {
181:         return $this->getParent();
182:     }
183:     
184:     
185: 
186:     
187:     /**
188:      * API OPERATIONS (CRUD & CUSTOM)
189:      */
190:     
191:     /**
192:      * Creates a new object
193:      *
194:      * @api
195:      * @param array $params array of values to set when creating the object
196:      * @return HttpResponse
197:      * @throws VolumeCreateError if HTTP status is not Success
198:      */
199:     public function create($params = array())
200:     {
201:         // set parameters
202:         if (!empty($params)) {
203:             $this->populate($params, false);
204:         }
205: 
206:         // debug
207:         $this->getLogger()->info('{class}::Create({name})', array(
208:             'class' => get_class($this), 
209:             'name'  => $this->Name()
210:         ));
211: 
212:         // construct the JSON
213:         $object = $this->createJson();
214:         $json = json_encode($object);
215:         $this->checkJsonError();
216: 
217:         $this->getLogger()->info('{class}::Create JSON [{json}]', array(
218:             'class' => get_class($this), 
219:             'json'  => $json
220:         ));
221:  
222:         // send the request
223:         $response = $this->getService()->request(
224:             $this->createUrl(),
225:             'POST',
226:             array('Content-Type' => 'application/json'),
227:             $json
228:         );
229:         
230:         // check the return code
231:         // @codeCoverageIgnoreStart
232:         if ($response->httpStatus() > 204) {
233:             throw new Exceptions\CreateError(sprintf(
234:                 Lang::translate('Error creating [%s] [%s], status [%d] response [%s]'),
235:                 get_class($this),
236:                 $this->Name(),
237:                 $response->HttpStatus(),
238:                 $response->HttpBody()
239:             ));
240:         }
241: 
242:         if ($response->HttpStatus() == "201" && ($location = $response->Header('Location'))) {
243:             // follow Location header
244:             $this->refresh(null, $location);
245:         } else {
246:             // set values from response
247:             $object = json_decode($response->httpBody());
248:             
249:             if (!$this->checkJsonError()) {
250:                 $top = $this->jsonName();
251:                 if (isset($object->$top)) {
252:                     $this->populate($object->$top);
253:                 }
254:             }
255:         }
256:         // @codeCoverageIgnoreEnd
257: 
258:         return $response;
259:     }
260: 
261:     /**
262:      * Updates an existing object
263:      *
264:      * @api
265:      * @param array $params array of values to set when updating the object
266:      * @return HttpResponse
267:      * @throws VolumeCreateError if HTTP status is not Success
268:      */
269:     public function update($params = array())
270:     {
271:         // set parameters
272:         if (!empty($params)) {
273:             $this->populate($params);
274:         }
275: 
276:         // debug
277:         $this->getLogger()->info('{class}::Update({name})', array(
278:             'class' => get_class($this),
279:             'name'  => $this->Name()   
280:         ));
281: 
282:         // construct the JSON
283:         $obj = $this->updateJson($params);
284:         $json = json_encode($obj);
285: 
286:         $this->checkJsonError();
287: 
288:         $this->getLogger()->info('{class}::Update JSON [{json}]', array(
289:             'class' => get_class($this), 
290:             'json'  => $json
291:         ));
292: 
293:         // send the request
294:         $response = $this->getService()->Request(
295:             $this->url(),
296:             'PUT',
297:             array(),
298:             $json
299:         );
300: 
301:         // check the return code
302:         // @codeCoverageIgnoreStart
303:         if ($response->HttpStatus() > 204) {
304:             throw new Exceptions\UpdateError(sprintf(
305:                 Lang::translate('Error updating [%s] with [%s], status [%d] response [%s]'),
306:                 get_class($this),
307:                 $json,
308:                 $response->HttpStatus(),
309:                 $response->HttpBody()
310:             ));
311:         }
312:         // @codeCoverageIgnoreEnd
313: 
314:         return $response;
315:     }
316: 
317:     /**
318:      * Deletes an object
319:      *
320:      * @api
321:      * @return HttpResponse
322:      * @throws DeleteError if HTTP status is not Success
323:      */
324:     public function delete()
325:     {
326:         $this->getLogger()->info('{class}::Delete()', array('class' => get_class($this)));
327: 
328:         // send the request
329:         $response = $this->getService()->request($this->url(), 'DELETE');
330: 
331:         // check the return code
332:         // @codeCoverageIgnoreStart
333:         if ($response->HttpStatus() > 204) {
334:             throw new Exceptions\DeleteError(sprintf(
335:                 Lang::translate('Error deleting [%s] [%s], status [%d] response [%s]'),
336:                 get_class(),
337:                 $this->Name(),
338:                 $response->HttpStatus(),
339:                 $response->HttpBody()
340:             ));
341:         }
342:         // @codeCoverageIgnoreEnd
343: 
344:         return $response;
345:     }
346: 
347:      /**
348:      * Returns an object for the Create() method JSON
349:      * Must be overridden in a child class.
350:      *
351:      * @throws CreateError if not overridden
352:      */
353:     protected function createJson()
354:     {
355:         throw new Exceptions\CreateError(sprintf(
356:             Lang::translate('[%s] CreateJson() must be overridden'),
357:             get_class($this)
358:         ));
359:     }
360: 
361:     /**
362:      * Returns an object for the Update() method JSON
363:      * Must be overridden in a child class.
364:      *
365:      * @throws UpdateError if not overridden
366:      */
367:     protected function updateJson($params = array())
368:     {
369:         throw new Exceptions\UpdateError(sprintf(
370:             Lang::translate('[%s] UpdateJson() must be overridden'),
371:             get_class($this)
372:         ));
373:     }
374: 
375:     /**
376:      * throws a CreateError for subclasses that don't support Create
377:      *
378:      * @throws CreateError
379:      */
380:     protected function noCreate()
381:     {
382:         throw new Exceptions\CreateError(sprintf(
383:             Lang::translate('[%s] does not support Create()'),
384:             get_class()
385:         ));
386:     }
387: 
388:     /**
389:      * throws a DeleteError for subclasses that don't support Delete
390:      *
391:      * @throws DeleteError
392:      */
393:     protected function noDelete()
394:     {
395:         throw new Exceptions\DeleteError(sprintf(
396:             Lang::translate('[%s] does not support Delete()'),
397:             get_class()
398:         ));
399:     }
400: 
401:     /**
402:      * throws a UpdateError for subclasses that don't support Update
403:      *
404:      * @throws UpdateError
405:      */
406:     protected function noUpdate()
407:     {
408:         throw new Exceptions\UpdateError(sprintf(
409:             Lang::translate('[%s] does not support Update()'),
410:             get_class()
411:         ));
412:     }
413:     
414:     /**
415:      * Returns the default URL of the object
416:      *
417:      * This may have to be overridden in subclasses.
418:      *
419:      * @param string $subresource optional sub-resource string
420:      * @param array $qstr optional k/v pairs for query strings
421:      * @return string
422:      * @throws UrlError if URL is not defined
423:      */
424:     public function url($subresource = null, $queryString = array())
425:     {
426:         // find the primary key attribute name
427:         $primaryKey = $this->primaryKeyField();
428: 
429:         // first, see if we have a [self] link
430:         $url = $this->findLink('self');
431: 
432:         /**
433:          * Next, check to see if we have an ID
434:          * Note that we use Parent() instead of Service(), since the parent
435:          * object might not be a service.
436:          */
437:         if (!$url && $this->$primaryKey) {
438:             $url = Lang::noslash($this->getParent()->url($this->resourceName())) . '/' . $this->$primaryKey;
439:         }
440: 
441:         // add the subresource
442:         if ($url) {
443:             $url .= $subresource ? "/$subresource" : '';
444:             if (count($queryString)) {
445:                 $url .= '?' . $this->makeQueryString($queryString);
446:             }
447:             return $url;
448:         }
449: 
450:         // otherwise, we don't have a URL yet
451:         throw new Exceptions\UrlError(sprintf(
452:             Lang::translate('%s does not have a URL yet'), 
453:             get_class($this)
454:         ));
455:     }
456: 
457:     /**
458:      * Waits for the server/instance status to change
459:      *
460:      * This function repeatedly polls the system for a change in server
461:      * status. Once the status reaches the `$terminal` value (or 'ERROR'),
462:      * then the function returns.
463:      *
464:      * The polling interval is set by the constant RAXSDK_POLL_INTERVAL.
465:      *
466:      * The function will automatically terminate after RAXSDK_SERVER_MAXTIMEOUT
467:      * seconds elapse.
468:      *
469:      * @api
470:      * @param string $terminal the terminal state to wait for
471:      * @param integer $timeout the max time (in seconds) to wait
472:      * @param callable $callback a callback function that is invoked with
473:      *      each repetition of the polling sequence. This can be used, for
474:      *      example, to update a status display or to permit other operations
475:      *      to continue
476:      * @return void
477:      */
478:     public function waitFor(
479:         $terminal = 'ACTIVE',
480:         $timeout = RAXSDK_SERVER_MAXTIMEOUT,
481:         $callback = NULL,
482:         $sleep = RAXSDK_POLL_INTERVAL
483:     ) {
484:         // find the primary key field
485:         $primaryKey = $this->PrimaryKeyField();
486: 
487:         // save stats
488:         $startTime = time();
489:         
490:         $states = array('ERROR', $terminal);
491:         
492:         while (true) {
493:             
494:             $this->refresh($this->$primaryKey);
495:             
496:             if ($callback) {
497:                 call_user_func($callback, $this);
498:             }
499:             
500:             if (in_array($this->status(), $states) || (time() - $startTime) > $timeout) {
501:                 return;
502:             }
503:             // @codeCoverageIgnoreStart
504:             sleep($sleep);
505:         }
506:     }
507:     // @codeCoverageIgnoreEnd
508: 
509:     /**
510:      * Refreshes the object from the origin (useful when the server is
511:      * changing states)
512:      *
513:      * @return void
514:      * @throws IdRequiredError
515:      */
516:     public function refresh($id = null, $url = null)
517:     {
518:         $primaryKey = $this->PrimaryKeyField();
519: 
520:         if (!$url) {
521:             if ($id === null) {
522:                 $id = $this->$primaryKey;
523:             }
524: 
525:             if (!$id) {
526:                 throw new Exceptions\IdRequiredError(sprintf(
527:                     Lang::translate('%s has no ID; cannot be refreshed'),
528:                     get_class())
529:                 );
530:             }
531: 
532:             // retrieve it
533:             $this->getLogger()->info(Lang::translate('{class} id [{id}]'), array(
534:                 'class' => get_class($this), 
535:                 'id'    => $id
536:             ));
537:             
538:             $this->$primaryKey = $id;
539:             $url = $this->url();
540:         }
541:         
542:         // reset status, if available
543:         if (property_exists($this, 'status')) {
544:             $this->status = null;
545:         }
546: 
547:         // perform a GET on the URL
548:         $response = $this->getService()->Request($url);
549:         
550:         // check status codes
551:         // @codeCoverageIgnoreStart
552:         if ($response->HttpStatus() == 404) {
553:             throw new Exceptions\InstanceNotFound(
554:                 sprintf(Lang::translate('%s [%s] not found [%s]'),
555:                 get_class($this),
556:                 $this->$primaryKey,
557:                 $url
558:             ));
559:         }
560: 
561:         if ($response->HttpStatus() >= 300) {
562:             throw new Exceptions\UnknownError(
563:                 sprintf(Lang::translate('Unexpected %s error [%d] [%s]'),
564:                 get_class($this),
565:                 $response->HttpStatus(),
566:                 $response->HttpBody()
567:             ));
568:         }
569: 
570:         // check for empty response
571:         if (!$response->HttpBody()) {
572:             throw new Exceptions\EmptyResponseError(
573:                 sprintf(Lang::translate('%s::Refresh() unexpected empty response, URL [%s]'),
574:                 get_class($this),
575:                 $url
576:             ));
577:         }
578: 
579:         // we're ok, reload the response
580:         if ($json = $response->HttpBody()) {
581:  
582:             $this->getLogger()->info('refresh() JSON [{json}]', array('json' => $json));
583:             
584:             $response = json_decode($json);
585: 
586:             if ($this->CheckJsonError()) {
587:                 throw new Exceptions\ServerJsonError(sprintf(
588:                     Lang::translate('JSON parse error on %s refresh'), 
589:                     get_class($this)
590:                 ));
591:             }
592: 
593:             $top = $this->JsonName();
594:             
595:             if ($top && isset($response->$top)) {
596:                 $content = $response->$top;
597:             } else {
598:                 $content = $response;
599:             }
600:             
601:             $this->populate($content);
602: 
603:         }
604:         // @codeCoverageIgnoreEnd
605:     }
606: 
607:     
608:     /**
609:      * OBJECT INFORMATION
610:      */
611:     
612:     /**
613:      * Returns the displayable name of the object
614:      *
615:      * Can be overridden by child objects; *must* be overridden by child
616:      * objects if the object does not have a `name` attribute defined.
617:      *
618:      * @api
619:      * @return string
620:      * @throws NameError if attribute 'name' is not defined
621:      */
622:     public function name()
623:     {
624:         if (property_exists($this, 'name')) {
625:             return $this->name;
626:         } else {
627:             throw new Exceptions\NameError(sprintf(
628:                 Lang::translate('Name attribute does not exist for [%s]'),
629:                 get_class($this)
630:             ));
631:         }
632:     }
633: 
634:     /**
635:      * Sends the json string to the /action resource
636:      *
637:      * This is used for many purposes, such as rebooting the server,
638:      * setting the root password, creating images, etc.
639:      * Since it can only be used on a live server, it checks for a valid ID.
640:      *
641:      * @param $object - this will be encoded as json, and we handle all the JSON
642:      *     error-checking in one place
643:      * @throws ServerIdError if server ID is not defined
644:      * @throws ServerActionError on other errors
645:      * @returns boolean; TRUE if successful, FALSE otherwise
646:      */
647:     protected function action($object)
648:     {
649:         $primaryKey = $this->primaryKeyField();
650: 
651:         if (!$this->$primaryKey) {
652:             throw new Exceptions\IdRequiredError(sprintf(
653:                 Lang::translate('%s is not defined'),
654:                 get_class($this)
655:             ));
656:         }
657: 
658:         if (!is_object($object)) {
659:             throw new Exceptions\ServerActionError(sprintf(
660:                 Lang::translate('%s::Action() requires an object as its parameter'),
661:                 get_class($this)
662:             ));
663:         }
664: 
665:         // convert the object to json
666:         $json = json_encode($object);
667:         $this->getLogger()->info('JSON [{string}]', array('json' => $json));
668: 
669:         $this->checkJsonError();
670: 
671:         // debug - save the request
672:         $this->getLogger()->info(Lang::translate('{class}::action [{json}]'), array(
673:             'class' => get_class($this), 
674:             'json'  => $json
675:         ));
676: 
677:         // get the URL for the POST message
678:         $url = $this->url('action');
679: 
680:         // POST the message
681:         $response = $this->getService()->request($url, 'POST', array(), $json);
682: 
683:         // @codeCoverageIgnoreStart
684:         if (!is_object($response)) {
685:             throw new Exceptions\HttpError(sprintf(
686:                 Lang::translate('Invalid response for %s::Action() request'),
687:                 get_class($this)
688:             ));
689:         }
690:         
691:         // check for errors
692:         if ($response->HttpStatus() >= 300) {
693:             throw new Exceptions\ServerActionError(sprintf(
694:                 Lang::translate('%s::Action() [%s] failed; response [%s]'),
695:                 get_class($this),
696:                 $url,
697:                 $response->HttpBody()
698:             ));
699:         }
700:         // @codeCoverageIgnoreStart
701: 
702:         return $response;
703:     }
704:     
705:     /**
706:      * Execute a custom resource request.
707:      * 
708:      * @param string $path
709:      * @param string $method
710:      * @param string|array|object $body
711:      * @return boolean
712:      * @throws Exceptions\InvalidArgumentError
713:      * @throws Exceptions\HttpError
714:      * @throws Exceptions\ServerActionError
715:      */
716:     public function customAction($url, $method = 'GET', $body = null)
717:     {
718:         if (is_string($body) && (json_decode($body) === null)) {
719:             throw new Exceptions\InvalidArgumentError(
720:                 'Please provide either a well-formed JSON string, or an object '
721:                 . 'for JSON serialization'
722:             );
723:         } else {
724:             $body = json_encode($body);
725:         }
726: 
727:         // POST the message
728:         $response = $this->service()->request($url, $method, array(), $body);
729: 
730:         if (!is_object($response)) {
731:             throw new Exceptions\HttpError(sprintf(
732:                 Lang::translate('Invalid response for %s::customAction() request'),
733:                 get_class($this)
734:             ));
735:         }
736: 
737:         // check for errors
738:         // @codeCoverageIgnoreStart
739:         if ($response->HttpStatus() >= 300) {
740:             throw new Exceptions\ServerActionError(sprintf(
741:                 Lang::translate('%s::customAction() [%s] failed; response [%s]'),
742:                 get_class($this),
743:                 $url,
744:                 $response->HttpBody()
745:             ));
746:         }
747:         // @codeCoverageIgnoreEnd
748: 
749:         $object = json_decode($response->httpBody());
750:         
751:         $this->checkJsonError();
752:         
753:         return $object;
754:     }
755: 
756:     /**
757:      * returns the object's status or `N/A` if not available
758:      *
759:      * @api
760:      * @return string
761:      */
762:     public function status()
763:     {
764:         return (isset($this->status)) ? $this->status : 'N/A';
765:     }
766: 
767:     /**
768:      * returns the object's identifier
769:      *
770:      * Can be overridden by a child class if the identifier is not in the
771:      * `$id` property. Use of this function permits the `$id` attribute to
772:      * be protected or private to prevent unauthorized overwriting for
773:      * security.
774:      *
775:      * @api
776:      * @return string
777:      */
778:     public function id()
779:     {
780:         return $this->id;
781:     }
782: 
783:     /**
784:      * checks for `$alias` in extensions and throws an error if not present
785:      *
786:      * @throws UnsupportedExtensionError
787:      */
788:     public function checkExtension($alias)
789:     {
790:         if (!in_array($alias, $this->getService()->namespaces())) {
791:             throw new Exceptions\UnsupportedExtensionError(sprintf(
792:                 Lang::translate('Extension [%s] is not installed'),
793:                 $alias
794:             ));
795:         }
796:         
797:         return true;
798:     }
799: 
800:     /**
801:      * returns the region associated with the object
802:      *
803:      * navigates to the parent service to determine the region.
804:      *
805:      * @api
806:      */
807:     public function region()
808:     {
809:         return $this->getService()->Region();
810:     }
811:     
812:     /**
813:      * Since each server can have multiple links, this returns the desired one
814:      *
815:      * @param string $type - 'self' is most common; use 'bookmark' for
816:      *      the version-independent one
817:      * @return string the URL from the links block
818:      */
819:     public function findLink($type = 'self')
820:     {
821:         if (empty($this->links)) {
822:             return false;
823:         }
824: 
825:         foreach ($this->links as $link) {
826:             if ($link->rel == $type) {
827:                 return $link->href;
828:             }
829:         }
830: 
831:         return false;
832:     }
833: 
834:     /**
835:      * returns the URL used for Create
836:      *
837:      * @return string
838:      */
839:     protected function createUrl()
840:     {
841:         return $this->getParent()->Url($this->ResourceName());
842:     }
843: 
844:     /**
845:      * Returns the primary key field for the object
846:      *
847:      * The primary key is usually 'id', but this function is provided so that
848:      * (in rare cases where it is not 'id'), it can be overridden.
849:      *
850:      * @return string
851:      */
852:     protected function primaryKeyField()
853:     {
854:         return 'id';
855:     }
856: 
857:     /**
858:      * Returns the top-level document identifier for the returned response
859:      * JSON document; must be overridden in child classes
860:      *
861:      * For example, a server document is (JSON) `{"server": ...}` and an
862:      * Instance document is `{"instance": ...}` - this function must return
863:      * the top level document name (either "server" or "instance", in
864:      * these examples).
865:      *
866:      * @throws DocumentError if not overridden
867:      */
868:     public static function jsonName()
869:     {
870:         if (isset(static::$json_name)) {
871:             return static::$json_name;
872:         }
873: 
874:         throw new Exceptions\DocumentError(sprintf(
875:             Lang::translate('No JSON object defined for class [%s] in JsonName()'),
876:             get_class()
877:         ));
878:     }
879: 
880:     /**
881:      * returns the collection JSON element name
882:      *
883:      * When an object is returned in a collection, it usually has a top-level
884:      * object that is an array holding child objects of the object types.
885:      * This static function returns the name of the top-level element. Usually,
886:      * that top-level element is simply the JSON name of the resource.'s';
887:      * however, it can be overridden by specifying the $json_collection_name
888:      * attribute.
889:      *
890:      * @return string
891:      */
892:     public static function jsonCollectionName()
893:     {
894:         if (isset(static::$json_collection_name)) {
895:             return static::$json_collection_name;
896:         } else {
897:             return static::$json_name . 's';
898:         }
899:     }
900: 
901:     /**
902:      * returns the JSON name for each element in a collection
903:      *
904:      * Usually, elements in a collection are anonymous; this function, however,
905:      * provides for an element level name:
906:      *
907:      *  `{ "collection" : [ { "element" : ... } ] }`
908:      *
909:      * @return string
910:      */
911:     public static function jsonCollectionElement()
912:     {
913:         if (isset(static::$json_collection_element)) {
914:             return static::$json_collection_element;
915:         }
916:     }
917: 
918:     /**
919:      * Returns the resource name for the URL of the object; must be overridden
920:      * in child classes
921:      *
922:      * For example, a server is `/servers/`, a database instance is
923:      * `/instances/`. Must be overridden in child classes.
924:      *
925:      * @throws UrlError
926:      */
927:     public static function resourceName()
928:     {
929:         if (isset(static::$url_resource)) {
930:             return static::$url_resource;
931:         }
932: 
933:         throw new Exceptions\UrlError(sprintf(
934:             Lang::translate('No URL resource defined for class [%s] in ResourceName()'),
935:             get_class()
936:         ));
937:     }
938: 
939: }
PHP OpenCloud API API documentation generated by ApiGen 2.8.0