      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: