1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 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: 34: 35: 36: 37:
38: public function create($params = array())
39: {
40:
41: if (!empty($params)) {
42: $this->populate($params, false);
43: }
44:
45:
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:
54:
55:
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: 67: 68: 69: 70:
71: public function update($params = array())
72: {
73:
74: if (!empty($params)) {
75: $this->populate($params);
76: }
77:
78:
79: $json = json_encode($this->updateJson($params));
80: $this->checkJsonError();
81:
82:
83: return $this->getClient()->put($this->getUrl(), self::getJsonHeader(), $json)->send();
84: }
85:
86: 87: 88: 89: 90:
91: public function delete()
92: {
93: return $this->getClient()->delete($this->getUrl())->send();
94: }
95:
96: 97: 98: 99: 100: 101: 102: 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:
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: 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: 154: 155: 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: 170: 171: 172: 173: 174: 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:
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: 204: 205: 206: 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: 234: 235: 236: 237: 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: 250: 251: 252: 253: 254: 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: 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: 303:
304: protected function noCreate()
305: {
306: throw new CreateError('This resource does not support the create operation');
307: }
308:
309: 310: 311:
312: protected function noDelete()
313: {
314: throw new DeleteError('This resource does not support the delete operation');
315: }
316:
317: 318: 319:
320: protected function noUpdate()
321: {
322: throw new UpdateError('This resource does not support the update operation');
323: }
324:
325: 326: 327: 328: 329: 330: 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: 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: 360: 361: 362: 363:
364: protected function generateJsonPatch($updatedProperties)
365: {
366:
367: $currentProperties = json_decode(json_encode($this->getUpdateablePropertiesAsArray()), true);
368: $updatedProperties = json_decode(json_encode($updatedProperties), true);
369:
370:
371:
372: foreach ($currentProperties as $key => $value) {
373: if (!array_key_exists($key, $updatedProperties)) {
374: $updatedProperties[$key] = $value;
375: }
376: }
377:
378:
379: $currentProperties = $this->recursivelyAliasPropertyValue($currentProperties);
380: $updatedProperties = $this->recursivelyAliasPropertyValue($updatedProperties);
381:
382:
383: $json = json_encode(JsonPatch::diff($currentProperties, $updatedProperties));
384: $this->checkJsonError();
385:
386: return $json;
387: }
388:
389:
390:
391: 392: 393: 394: 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: 407: 408:
409: public function id()
410: {
411: return $this->id;
412: }
413:
414: 415: 416: 417:
418: public function status()
419: {
420: return (isset($this->status)) ? $this->status : 'N/A';
421: }
422:
423: 424: 425: 426:
427: public function region()
428: {
429: return $this->getService()->region();
430: }
431:
432: 433: 434: 435:
436: public function createUrl()
437: {
438: return $this->getParent()->getUrl($this->resourceName());
439: }
440: }
441: