Overview

Namespaces

  • OpenCloud
    • Autoscale
      • Resource
    • CDN
      • Resource
    • CloudMonitoring
      • Collection
      • Exception
      • Resource
    • Common
      • Collection
      • Constants
      • Exceptions
      • Http
        • Message
      • Log
      • Resource
      • Service
    • Compute
      • Constants
      • Exception
      • Resource
    • Database
      • Resource
    • DNS
      • Collection
      • Resource
    • Identity
      • Constants
      • Resource
    • Image
      • Enum
      • Resource
        • JsonPatch
        • Schema
    • LoadBalancer
      • Collection
      • Enum
      • Resource
    • Networking
      • Resource
    • ObjectStore
      • Constants
      • Enum
      • Exception
      • Resource
      • Upload
    • Orchestration
      • Resource
    • Queues
      • Collection
      • Exception
      • Resource
    • Volume
      • Resource
  • PHP

Classes

  • BaseResource
  • NovaResource
  • PersistentResource
  • ReadOnlyResource
  • Overview
  • Namespace
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * Copyright 2012-2014 Rackspace US, Inc.
  4:  *
  5:  * Licensed under the Apache License, Version 2.0 (the "License");
  6:  * you may not use this file except in compliance with the License.
  7:  * You may obtain a copy of the License at
  8:  *
  9:  * http://www.apache.org/licenses/LICENSE-2.0
 10:  *
 11:  * Unless required by applicable law or agreed to in writing, software
 12:  * distributed under the License is distributed on an "AS IS" BASIS,
 13:  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14:  * See the License for the specific language governing permissions and
 15:  * limitations under the License.
 16:  */
 17: 
 18: namespace OpenCloud\Common\Resource;
 19: 
 20: use Guzzle\Http\Url;
 21: use OpenCloud\Common\Constants\State;
 22: use OpenCloud\Common\Exceptions\CreateError;
 23: use OpenCloud\Common\Exceptions\DeleteError;
 24: use OpenCloud\Common\Exceptions\IdRequiredError;
 25: use OpenCloud\Common\Exceptions\NameError;
 26: use OpenCloud\Common\Exceptions\UnsupportedExtensionError;
 27: use OpenCloud\Common\Exceptions\UpdateError;
 28: use mikemccabe\JsonPatch\JsonPatch;
 29: 
 30: abstract class PersistentResource extends BaseResource
 31: {
 32:     /**
 33:      * Create a new resource
 34:      *
 35:      * @param array $params
 36:      * @return \Guzzle\Http\Message\Response
 37:      */
 38:     public function create($params = array())
 39:     {
 40:         // set parameters
 41:         if (!empty($params)) {
 42:             $this->populate($params, false);
 43:         }
 44: 
 45:         // construct the JSON
 46:         $json = json_encode($this->createJson());
 47:         $this->checkJsonError();
 48: 
 49:         $createUrl = $this->createUrl();
 50: 
 51:         $response = $this->getClient()->post($createUrl, self::getJsonHeader(), $json)->send();
 52: 
 53:         // We have to try to parse the response body first because it should have precedence over a Location refresh.
 54:         // I'd like to reverse the order, but Nova instances return ephemeral properties on creation which are not
 55:         // available when you follow the Location link...
 56:         if (null !== ($decoded = $this->parseResponse($response))) {
 57:             $this->populate($decoded);
 58:         } elseif ($location = $response->getHeader('Location')) {
 59:             $this->refreshFromLocationUrl($location);
 60:         }
 61: 
 62:         return $response;
 63:     }
 64: 
 65:     /**
 66:      * Update a resource
 67:      *
 68:      * @param array $params
 69:      * @return \Guzzle\Http\Message\Response
 70:      */
 71:     public function update($params = array())
 72:     {
 73:         // set parameters
 74:         if (!empty($params)) {
 75:             $this->populate($params);
 76:         }
 77: 
 78:         // construct the JSON
 79:         $json = json_encode($this->updateJson($params));
 80:         $this->checkJsonError();
 81: 
 82:         // send the request
 83:         return $this->getClient()->put($this->getUrl(), self::getJsonHeader(), $json)->send();
 84:     }
 85: 
 86:     /**
 87:      * Delete this resource
 88:      *
 89:      * @return \Guzzle\Http\Message\Response
 90:      */
 91:     public function delete()
 92:     {
 93:         return $this->getClient()->delete($this->getUrl())->send();
 94:     }
 95: 
 96:     /**
 97:      * Refresh the state of a resource
 98:      *
 99:      * @param null $id
100:      * @param null $url
101:      * @return \Guzzle\Http\Message\Response
102:      * @throws IdRequiredError
103:      */
104:     public function refresh($id = null, $url = null)
105:     {
106:         $primaryKey = $this->primaryKeyField();
107:         $primaryKeyVal = $this->getProperty($primaryKey);
108: 
109:         if (!$url) {
110:             if (!$id = $id ?: $primaryKeyVal) {
111:                 $message = sprintf("This resource cannot be refreshed because it has no %s", $primaryKey);
112:                 throw new IdRequiredError($message);
113:             }
114: 
115:             if ($primaryKeyVal != $id) {
116:                 $this->setProperty($primaryKey, $id);
117:             }
118: 
119:             $url = $this->getUrl();
120:         }
121: 
122:         // reset status, if available
123:         if ($this->getProperty('status')) {
124:             $this->setProperty('status', null);
125:         }
126: 
127:         $response = $this->getClient()->get($url)->send();
128: 
129:         if (null !== ($decoded = $this->parseResponse($response))) {
130:             $this->populate($decoded);
131:         }
132: 
133:         return $response;
134:     }
135: 
136: 
137:     /**
138:      * Causes resource to refresh based on parent's URL
139:      */
140:     protected function refreshFromParent()
141:     {
142:         $url = clone $this->getParent()->getUrl();
143:         $url->addPath($this->resourceName());
144: 
145:         $response = $this->getClient()->get($url)->send();
146: 
147:         if (null !== ($decoded = $this->parseResponse($response))) {
148:             $this->populate($decoded);
149:         }
150:     }
151: 
152:     /**
153:      * Given a `location` URL, refresh this resource
154:      *
155:      * @param $url
156:      */
157:     public function refreshFromLocationUrl($url)
158:     {
159:         $fullUrl = Url::factory($url);
160: 
161:         $response = $this->getClient()->get($fullUrl)->send();
162: 
163:         if (null !== ($decoded = $this->parseResponse($response))) {
164:             $this->populate($decoded);
165:         }
166:     }
167: 
168:     /**
169:      * A method to repeatedly poll the API resource, waiting for an eventual state change
170:      *
171:      * @param null $state    The expected state of the resource
172:      * @param null $timeout  The maximum timeout to wait
173:      * @param null $callback The callback to use to check the state
174:      * @param null $interval How long between each refresh request
175:      */
176:     public function waitFor($state = null, $timeout = null, $callback = null, $interval = null)
177:     {
178:         $state    = $state ?: State::ACTIVE;
179:         $timeout  = $timeout ?: State::DEFAULT_TIMEOUT;
180:         $interval = $interval ?: State::DEFAULT_INTERVAL;
181: 
182:         // save stats
183:         $startTime = time();
184: 
185:         $states = array('ERROR', $state);
186: 
187:         while (true) {
188:             $this->refresh($this->getProperty($this->primaryKeyField()));
189: 
190:             if ($callback) {
191:                 call_user_func($callback, $this);
192:             }
193: 
194:             if (in_array($this->status(), $states) || (time() - $startTime) > $timeout) {
195:                 return;
196:             }
197: 
198:             sleep($interval);
199:         }
200:     }
201: 
202:     /**
203:      * Provides JSON for create request body
204:      *
205:      * @return object
206:      * @throws \RuntimeException
207:      */
208:     protected function createJson()
209:     {
210:         if (!isset($this->createKeys)) {
211:             throw new \RuntimeException(sprintf(
212:                 'This resource object [%s] must have a visible createKeys array',
213:                 get_class($this)
214:             ));
215:         }
216: 
217:         $element = (object) array();
218: 
219:         foreach ($this->createKeys as $key) {
220:             if (null !== ($property = $this->getProperty($key))) {
221:                 $element->{$this->getAlias($key)} = $this->recursivelyAliasPropertyValue($property);
222:             }
223:         }
224: 
225:         if (isset($this->metadata) && count($this->metadata)) {
226:             $element->metadata = (object) $this->metadata->toArray();
227:         }
228: 
229:         return (object) array($this->jsonName() => (object) $element);
230:     }
231: 
232:     /**
233:      * Returns the alias configured for the given key. If no alias exists
234:      * it returns the original key.
235:      *
236:      * @param  string $key
237:      * @return string
238:      */
239:     protected function getAlias($key)
240:     {
241:         if (false !== ($alias = array_search($key, $this->aliases))) {
242:             return $alias;
243:         }
244: 
245:         return $key;
246:     }
247: 
248:     /**
249:      * Returns the given property value's alias, if configured; Else, the
250:      * unchanged property value is returned. If the given property value
251:      * is an array or an instance of \stdClass, it is aliases recursively.
252:      *
253:      * @param  mixed $propertyValue Array or \stdClass instance to alias
254:      * @return mixed Property value, aliased recursively
255:      */
256:     protected function recursivelyAliasPropertyValue($propertyValue)
257:     {
258:         if (is_array($propertyValue)) {
259:             foreach ($propertyValue as $key => $subValue) {
260:                 $aliasedSubValue = $this->recursivelyAliasPropertyValue($subValue);
261:                 if (is_numeric($key)) {
262:                     $propertyValue[$key] = $aliasedSubValue;
263:                 } else {
264:                     unset($propertyValue[$key]);
265:                     $propertyValue[$this->getAlias($key)] = $aliasedSubValue;
266:                 }
267:             }
268:         } elseif (is_object($propertyValue) && ($propertyValue instanceof \stdClass)) {
269:             foreach (get_object_vars($propertyValue) as $key => $subValue) {
270:                 unset($propertyValue->$key);
271:                 $propertyValue->{$this->getAlias($key)} = $this->recursivelyAliasPropertyValue($subValue);
272:             }
273:         }
274: 
275:         return $propertyValue;
276:     }
277: 
278:     /**
279:      * Provides JSON for update request body
280:      */
281:     protected function updateJson($params = array())
282:     {
283:         if (!isset($this->updateKeys)) {
284:             throw new \RuntimeException(sprintf(
285:                 'This resource object [%s] must have a visible updateKeys array',
286:                 get_class($this)
287:             ));
288:         }
289: 
290:         $element = (object) array();
291: 
292:         foreach ($this->updateKeys as $key) {
293:             if (null !== ($property = $this->getProperty($key))) {
294:                 $element->{$this->getAlias($key)} = $this->recursivelyAliasPropertyValue($property);
295:             }
296:         }
297: 
298:         return (object) array($this->jsonName() => (object) $element);
299:     }
300: 
301:     /**
302:      * @throws CreateError
303:      */
304:     protected function noCreate()
305:     {
306:         throw new CreateError('This resource does not support the create operation');
307:     }
308: 
309:     /**
310:      * @throws DeleteError
311:      */
312:     protected function noDelete()
313:     {
314:         throw new DeleteError('This resource does not support the delete operation');
315:     }
316: 
317:     /**
318:      * @throws UpdateError
319:      */
320:     protected function noUpdate()
321:     {
322:         throw new UpdateError('This resource does not support the update operation');
323:     }
324: 
325:     /**
326:      * Check whether an extension is valid
327:      *
328:      * @param mixed $alias The extension name
329:      * @return bool
330:      * @throws UnsupportedExtensionError
331:      */
332:     public function checkExtension($alias)
333:     {
334:         if (!in_array($alias, $this->getService()->namespaces())) {
335:             throw new UnsupportedExtensionError(sprintf("%s extension is not installed", $alias));
336:         }
337: 
338:         return true;
339:     }
340: 
341:     /**
342:      * Returns the object's properties as an array
343:      */
344:     protected function getUpdateablePropertiesAsArray()
345:     {
346:         $properties = get_object_vars($this);
347: 
348:         $propertiesToKeep = array();
349:         foreach ($this->updateKeys as $key) {
350:             if (isset($properties[$key])) {
351:                 $propertiesToKeep[$key] = $properties[$key];
352:             }
353:         }
354: 
355:         return $propertiesToKeep;
356:     }
357: 
358:     /**
359:      * Generates a JSON Patch representation and return its
360:      *
361:      * @param mixed $updatedProperties Properties of the resource to update
362:      * @return String JSON Patch representation for updates
363:      */
364:     protected function generateJsonPatch($updatedProperties)
365:     {
366:         // Normalize current and updated properties into nested arrays
367:         $currentProperties = json_decode(json_encode($this->getUpdateablePropertiesAsArray()), true);
368:         $updatedProperties = json_decode(json_encode($updatedProperties), true);
369: 
370:         // Add any properties that haven't changed to generate the correct patch
371:         // (otherwise unchanging properties are marked as removed in the patch)
372:         foreach ($currentProperties as $key => $value) {
373:             if (!array_key_exists($key, $updatedProperties)) {
374:                 $updatedProperties[$key] = $value;
375:             }
376:         }
377: 
378:         // Recursively alias current and updated properties
379:         $currentProperties = $this->recursivelyAliasPropertyValue($currentProperties);
380:         $updatedProperties = $this->recursivelyAliasPropertyValue($updatedProperties);
381: 
382:         // Generate JSON Patch representation
383:         $json = json_encode(JsonPatch::diff($currentProperties, $updatedProperties));
384:         $this->checkJsonError();
385: 
386:         return $json;
387:     }
388: 
389:     /********  DEPRECATED METHODS ********/
390: 
391:     /**
392:      * @deprecated
393:      * @return string
394:      * @throws NameError
395:      */
396:     public function name()
397:     {
398:         if (null !== ($name = $this->getProperty('name'))) {
399:             return $name;
400:         } else {
401:             throw new NameError('Name attribute does not exist for this resource');
402:         }
403:     }
404: 
405:     /**
406:      * @deprecated
407:      * @return mixed
408:      */
409:     public function id()
410:     {
411:         return $this->id;
412:     }
413: 
414:     /**
415:      * @deprecated
416:      * @return string
417:      */
418:     public function status()
419:     {
420:         return (isset($this->status)) ? $this->status : 'N/A';
421:     }
422: 
423:     /**
424:      * @deprecated
425:      * @return mixed
426:      */
427:     public function region()
428:     {
429:         return $this->getService()->region();
430:     }
431: 
432:     /**
433:      * @deprecated
434:      * @return \Guzzle\Http\Url
435:      */
436:     public function createUrl()
437:     {
438:         return $this->getParent()->getUrl($this->resourceName());
439:     }
440: }
441: 
API documentation generated by ApiGen 2.8.0