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\Message\Response;
21: use Guzzle\Http\Url;
22: use OpenCloud\Common\Base;
23: use OpenCloud\Common\Exceptions\DocumentError;
24: use OpenCloud\Common\Exceptions\ServiceException;
25: use OpenCloud\Common\Exceptions\UrlError;
26: use OpenCloud\Common\Metadata;
27: use OpenCloud\Common\Service\ServiceInterface;
28: use OpenCloud\Common\Http\Message\Formatter;
29:
30: abstract class BaseResource extends Base
31: {
32: /** @var \OpenCloud\Common\Service\ServiceInterface */
33: protected $service;
34:
35: /** @var BaseResource */
36: protected $parent;
37:
38: /** @var \OpenCloud\Common\Metadata */
39: protected $metadata;
40:
41: /**
42: * @param ServiceInterface $service The service that this resource belongs to
43: * @param $data $data
44: */
45: public function __construct(ServiceInterface $service, $data = null)
46: {
47: $this->setService($service);
48: $this->metadata = new Metadata();
49: $this->populate($data);
50: }
51:
52: /**
53: * @param \OpenCloud\Common\Service\ServiceInterface $service
54: * @return \OpenCloud\Common\PersistentObject
55: */
56: public function setService(ServiceInterface $service)
57: {
58: $this->service = $service;
59:
60: return $this;
61: }
62:
63: /**
64: * @return \OpenCloud\Common\Service\ServiceInterface
65: * @throws \OpenCloud\Common\Exceptions\ServiceException
66: */
67: public function getService()
68: {
69: if (null === $this->service) {
70: throw new ServiceException('No service defined');
71: }
72:
73: return $this->service;
74: }
75:
76: /**
77: * @param BaseResource $parent
78: * @return self
79: */
80: public function setParent(BaseResource $parent)
81: {
82: $this->parent = $parent;
83:
84: return $this;
85: }
86:
87: /**
88: * @return mixed
89: */
90: public function getParent()
91: {
92: if (null === $this->parent) {
93: $this->parent = $this->getService();
94: }
95:
96: return $this->parent;
97: }
98:
99: /**
100: * Convenience method to return the service's client
101: *
102: * @return \Guzzle\Http\ClientInterface
103: */
104: public function getClient()
105: {
106: return $this->getService()->getClient();
107: }
108:
109: /**
110: * @param mixed $metadata
111: * @return $this
112: */
113: public function setMetadata($data)
114: {
115: if ($data instanceof Metadata) {
116: $metadata = $data;
117: } elseif (is_array($data) || is_object($data)) {
118: $metadata = new Metadata();
119: $metadata->setArray($data);
120: } else {
121: throw new \InvalidArgumentException(sprintf(
122: 'You must specify either an array/object of parameters, or an '
123: . 'instance of Metadata. You provided: %s',
124: print_r($data, true)
125: ));
126: }
127:
128: $this->metadata = $metadata;
129:
130: return $this;
131: }
132:
133: /**
134: * @return Metadata
135: */
136: public function getMetadata()
137: {
138: return $this->metadata;
139: }
140:
141: /**
142: * Get this resource's URL
143: *
144: * @param null $path URI path to add on
145: * @param array $query Query to add on
146: * @return mixed
147: */
148: public function getUrl($path = null, array $query = array())
149: {
150: if (!$url = $this->findLink('self')) {
151: // ...otherwise construct a URL from parent and this resource's
152: // "URL name". If no name is set, resourceName() throws an error.
153: $url = $this->getParent()->getUrl($this->resourceName());
154:
155: // Does it have a primary key?
156: if (null !== ($primaryKey = $this->getProperty($this->primaryKeyField()))) {
157: $url->addPath((string) $primaryKey);
158: }
159: }
160:
161: if (!$url instanceof Url) {
162: $url = Url::factory($url);
163: }
164:
165: return $url->addPath((string) $path)->setQuery($query);
166: }
167:
168: /**
169: * @deprecated
170: */
171: public function url($path = null, array $query = array())
172: {
173: return $this->getUrl($path, $query);
174: }
175:
176:
177: /**
178: * Find a resource link based on a type
179: *
180: * @param string $type
181: * @return bool
182: */
183: public function findLink($type = 'self')
184: {
185: if (empty($this->links)) {
186: return false;
187: }
188:
189: foreach ($this->links as $link) {
190: if ($link->rel == $type) {
191: return $link->href;
192: }
193: }
194:
195: return false;
196: }
197:
198: /**
199: * Returns the primary key field for the object
200: *
201: * @return string
202: */
203: protected function primaryKeyField()
204: {
205: return 'id';
206: }
207:
208: /**
209: * Returns the top-level key for the returned response JSON document
210: *
211: * @throws DocumentError
212: */
213: public static function jsonName()
214: {
215: if (isset(static::$json_name)) {
216: return static::$json_name;
217: }
218:
219: throw new DocumentError('A top-level JSON document key has not been defined for this resource');
220: }
221:
222: /**
223: * Returns the top-level key for collection responses
224: *
225: * @return string
226: */
227: public static function jsonCollectionName()
228: {
229: return isset(static::$json_collection_name) ? static::$json_collection_name : static::$json_name . 's';
230: }
231:
232: /**
233: * Returns the nested keys that could (rarely) prefix collection items. For example:
234: *
235: * {
236: * "keypairs": [
237: * {
238: * "keypair": {
239: * "fingerprint": "...",
240: * "name": "key1",
241: * "public_key": "..."
242: * }
243: * },
244: * {
245: * "keypair": {
246: * "fingerprint": "...",
247: * "name": "key2",
248: * "public_key": "..."
249: * }
250: * }
251: * ]
252: * }
253: *
254: * In the above example, "keypairs" would be the $json_collection_name and "keypair" would be the
255: * $json_collection_element
256: *
257: * @return string
258: */
259: public static function jsonCollectionElement()
260: {
261: if (isset(static::$json_collection_element)) {
262: return static::$json_collection_element;
263: }
264: }
265:
266: /**
267: * Returns the URI path for this resource
268: *
269: * @throws UrlError
270: */
271: public static function resourceName()
272: {
273: if (isset(static::$url_resource)) {
274: return static::$url_resource;
275: }
276:
277: throw new UrlError('No URL path defined for this resource');
278: }
279:
280: /**
281: * Parse a HTTP response for the required content
282: *
283: * @param Response $response
284: * @return mixed
285: */
286: public function parseResponse(Response $response)
287: {
288: $document = Formatter::decode($response);
289:
290: $topLevelKey = $this->jsonName();
291:
292: return ($topLevelKey && isset($document->$topLevelKey)) ? $document->$topLevelKey : $document;
293: }
294: }
295: