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:
152: // ...otherwise construct a URL from parent and this resource's
153: // "URL name". If no name is set, resourceName() throws an error.
154: $url = $this->getParent()->getUrl($this->resourceName());
155:
156: // Does it have a primary key?
157: if (null !== ($primaryKey = $this->getProperty($this->primaryKeyField()))) {
158: $url->addPath((string) $primaryKey);
159: }
160: }
161:
162: if (!$url instanceof Url) {
163: $url = Url::factory($url);
164: }
165:
166: return $url->addPath((string) $path)->setQuery($query);
167: }
168:
169: /**
170: * @deprecated
171: */
172: public function url($path = null, array $query = array())
173: {
174: return $this->getUrl($path, $query);
175: }
176:
177:
178: /**
179: * Find a resource link based on a type
180: *
181: * @param string $type
182: * @return bool
183: */
184: public function findLink($type = 'self')
185: {
186: if (empty($this->links)) {
187: return false;
188: }
189:
190: foreach ($this->links as $link) {
191: if ($link->rel == $type) {
192: return $link->href;
193: }
194: }
195:
196: return false;
197: }
198:
199: /**
200: * Returns the primary key field for the object
201: *
202: * @return string
203: */
204: protected function primaryKeyField()
205: {
206: return 'id';
207: }
208:
209: /**
210: * Returns the top-level key for the returned response JSON document
211: *
212: * @throws DocumentError
213: */
214: public static function jsonName()
215: {
216: if (isset(static::$json_name)) {
217: return static::$json_name;
218: }
219:
220: throw new DocumentError('A top-level JSON document key has not been defined for this resource');
221: }
222:
223: /**
224: * Returns the top-level key for collection responses
225: *
226: * @return string
227: */
228: public static function jsonCollectionName()
229: {
230: return isset(static::$json_collection_name) ? static::$json_collection_name : static::$json_name . 's';
231: }
232:
233: /**
234: * Returns the nested keys that could (rarely) prefix collection items. For example:
235: *
236: * {
237: * "keypairs": [
238: * {
239: * "keypair": {
240: * "fingerprint": "...",
241: * "name": "key1",
242: * "public_key": "..."
243: * }
244: * },
245: * {
246: * "keypair": {
247: * "fingerprint": "...",
248: * "name": "key2",
249: * "public_key": "..."
250: * }
251: * }
252: * ]
253: * }
254: *
255: * In the above example, "keypairs" would be the $json_collection_name and "keypair" would be the
256: * $json_collection_element
257: *
258: * @return string
259: */
260: public static function jsonCollectionElement()
261: {
262: if (isset(static::$json_collection_element)) {
263: return static::$json_collection_element;
264: }
265: }
266:
267: /**
268: * Returns the URI path for this resource
269: *
270: * @throws UrlError
271: */
272: public static function resourceName()
273: {
274: if (isset(static::$url_resource)) {
275: return static::$url_resource;
276: }
277:
278: throw new UrlError('No URL path defined for this resource');
279: }
280:
281: /**
282: * Parse a HTTP response for the required content
283: *
284: * @param Response $response
285: * @return mixed
286: */
287: public function parseResponse(Response $response)
288: {
289: $document = Formatter::decode($response);
290:
291: $topLevelKey = $this->jsonName();
292:
293: return ($topLevelKey && isset($document->$topLevelKey)) ? $document->$topLevelKey : $document;
294: }
295: }
296: