1<?php 2/** 3 * Copyright 2010 Google 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 18namespace Google\Service; 19 20use Google\Model; 21use Google\Http\MediaFileUpload; 22use Google\Exception as GoogleException; 23use Google\Utils\UriTemplate; 24use GuzzleHttp\Psr7\Request; 25 26/** 27 * Implements the actual methods/resources of the discovered Google API using magic function 28 * calling overloading (__call()), which on call will see if the method name (plus.activities.list) 29 * is available in this service, and if so construct an apiHttpRequest representing it. 30 * 31 */ 32class Resource 33{ 34 // Valid query parameters that work, but don't appear in discovery. 35 private $stackParameters = array( 36 'alt' => array('type' => 'string', 'location' => 'query'), 37 'fields' => array('type' => 'string', 'location' => 'query'), 38 'trace' => array('type' => 'string', 'location' => 'query'), 39 'userIp' => array('type' => 'string', 'location' => 'query'), 40 'quotaUser' => array('type' => 'string', 'location' => 'query'), 41 'data' => array('type' => 'string', 'location' => 'body'), 42 'mimeType' => array('type' => 'string', 'location' => 'header'), 43 'uploadType' => array('type' => 'string', 'location' => 'query'), 44 'mediaUpload' => array('type' => 'complex', 'location' => 'query'), 45 'prettyPrint' => array('type' => 'string', 'location' => 'query'), 46 ); 47 48 /** @var string $rootUrl */ 49 private $rootUrl; 50 51 /** @var \Google\Client $client */ 52 private $client; 53 54 /** @var string $serviceName */ 55 private $serviceName; 56 57 /** @var string $servicePath */ 58 private $servicePath; 59 60 /** @var string $resourceName */ 61 private $resourceName; 62 63 /** @var array $methods */ 64 private $methods; 65 66 public function __construct($service, $serviceName, $resourceName, $resource) 67 { 68 $this->rootUrl = $service->rootUrl; 69 $this->client = $service->getClient(); 70 $this->servicePath = $service->servicePath; 71 $this->serviceName = $serviceName; 72 $this->resourceName = $resourceName; 73 $this->methods = is_array($resource) && isset($resource['methods']) ? 74 $resource['methods'] : 75 array($resourceName => $resource); 76 } 77 78 /** 79 * TODO: This function needs simplifying. 80 * @param $name 81 * @param $arguments 82 * @param $expectedClass - optional, the expected class name 83 * @return mixed|$expectedClass|ResponseInterface|RequestInterface 84 * @throws \Google\Exception 85 */ 86 public function call($name, $arguments, $expectedClass = null) 87 { 88 if (! isset($this->methods[$name])) { 89 $this->client->getLogger()->error( 90 'Service method unknown', 91 array( 92 'service' => $this->serviceName, 93 'resource' => $this->resourceName, 94 'method' => $name 95 ) 96 ); 97 98 throw new GoogleException( 99 "Unknown function: " . 100 "{$this->serviceName}->{$this->resourceName}->{$name}()" 101 ); 102 } 103 $method = $this->methods[$name]; 104 $parameters = $arguments[0]; 105 106 // postBody is a special case since it's not defined in the discovery 107 // document as parameter, but we abuse the param entry for storing it. 108 $postBody = null; 109 if (isset($parameters['postBody'])) { 110 if ($parameters['postBody'] instanceof Model) { 111 // In the cases the post body is an existing object, we want 112 // to use the smart method to create a simple object for 113 // for JSONification. 114 $parameters['postBody'] = $parameters['postBody']->toSimpleObject(); 115 } else if (is_object($parameters['postBody'])) { 116 // If the post body is another kind of object, we will try and 117 // wrangle it into a sensible format. 118 $parameters['postBody'] = 119 $this->convertToArrayAndStripNulls($parameters['postBody']); 120 } 121 $postBody = (array) $parameters['postBody']; 122 unset($parameters['postBody']); 123 } 124 125 // TODO: optParams here probably should have been 126 // handled already - this may well be redundant code. 127 if (isset($parameters['optParams'])) { 128 $optParams = $parameters['optParams']; 129 unset($parameters['optParams']); 130 $parameters = array_merge($parameters, $optParams); 131 } 132 133 if (!isset($method['parameters'])) { 134 $method['parameters'] = array(); 135 } 136 137 $method['parameters'] = array_merge( 138 $this->stackParameters, 139 $method['parameters'] 140 ); 141 142 foreach ($parameters as $key => $val) { 143 if ($key != 'postBody' && ! isset($method['parameters'][$key])) { 144 $this->client->getLogger()->error( 145 'Service parameter unknown', 146 array( 147 'service' => $this->serviceName, 148 'resource' => $this->resourceName, 149 'method' => $name, 150 'parameter' => $key 151 ) 152 ); 153 throw new GoogleException("($name) unknown parameter: '$key'"); 154 } 155 } 156 157 foreach ($method['parameters'] as $paramName => $paramSpec) { 158 if (isset($paramSpec['required']) && 159 $paramSpec['required'] && 160 ! isset($parameters[$paramName]) 161 ) { 162 $this->client->getLogger()->error( 163 'Service parameter missing', 164 array( 165 'service' => $this->serviceName, 166 'resource' => $this->resourceName, 167 'method' => $name, 168 'parameter' => $paramName 169 ) 170 ); 171 throw new GoogleException("($name) missing required param: '$paramName'"); 172 } 173 if (isset($parameters[$paramName])) { 174 $value = $parameters[$paramName]; 175 $parameters[$paramName] = $paramSpec; 176 $parameters[$paramName]['value'] = $value; 177 unset($parameters[$paramName]['required']); 178 } else { 179 // Ensure we don't pass nulls. 180 unset($parameters[$paramName]); 181 } 182 } 183 184 $this->client->getLogger()->info( 185 'Service Call', 186 array( 187 'service' => $this->serviceName, 188 'resource' => $this->resourceName, 189 'method' => $name, 190 'arguments' => $parameters, 191 ) 192 ); 193 194 // build the service uri 195 $url = $this->createRequestUri( 196 $method['path'], 197 $parameters 198 ); 199 200 // NOTE: because we're creating the request by hand, 201 // and because the service has a rootUrl property 202 // the "base_uri" of the Http Client is not accounted for 203 $request = new Request( 204 $method['httpMethod'], 205 $url, 206 ['content-type' => 'application/json'], 207 $postBody ? json_encode($postBody) : '' 208 ); 209 210 // support uploads 211 if (isset($parameters['data'])) { 212 $mimeType = isset($parameters['mimeType']) 213 ? $parameters['mimeType']['value'] 214 : 'application/octet-stream'; 215 $data = $parameters['data']['value']; 216 $upload = new MediaFileUpload($this->client, $request, $mimeType, $data); 217 218 // pull down the modified request 219 $request = $upload->getRequest(); 220 } 221 222 // if this is a media type, we will return the raw response 223 // rather than using an expected class 224 if (isset($parameters['alt']) && $parameters['alt']['value'] == 'media') { 225 $expectedClass = null; 226 } 227 228 // if the client is marked for deferring, rather than 229 // execute the request, return the response 230 if ($this->client->shouldDefer()) { 231 // @TODO find a better way to do this 232 $request = $request 233 ->withHeader('X-Php-Expected-Class', $expectedClass); 234 235 return $request; 236 } 237 238 return $this->client->execute($request, $expectedClass); 239 } 240 241 protected function convertToArrayAndStripNulls($o) 242 { 243 $o = (array) $o; 244 foreach ($o as $k => $v) { 245 if ($v === null) { 246 unset($o[$k]); 247 } elseif (is_object($v) || is_array($v)) { 248 $o[$k] = $this->convertToArrayAndStripNulls($o[$k]); 249 } 250 } 251 return $o; 252 } 253 254 /** 255 * Parse/expand request parameters and create a fully qualified 256 * request uri. 257 * @static 258 * @param string $restPath 259 * @param array $params 260 * @return string $requestUrl 261 */ 262 public function createRequestUri($restPath, $params) 263 { 264 // Override the default servicePath address if the $restPath use a / 265 if ('/' == substr($restPath, 0, 1)) { 266 $requestUrl = substr($restPath, 1); 267 } else { 268 $requestUrl = $this->servicePath . $restPath; 269 } 270 271 // code for leading slash 272 if ($this->rootUrl) { 273 if ('/' !== substr($this->rootUrl, -1) && '/' !== substr($requestUrl, 0, 1)) { 274 $requestUrl = '/' . $requestUrl; 275 } 276 $requestUrl = $this->rootUrl . $requestUrl; 277 } 278 $uriTemplateVars = array(); 279 $queryVars = array(); 280 foreach ($params as $paramName => $paramSpec) { 281 if ($paramSpec['type'] == 'boolean') { 282 $paramSpec['value'] = $paramSpec['value'] ? 'true' : 'false'; 283 } 284 if ($paramSpec['location'] == 'path') { 285 $uriTemplateVars[$paramName] = $paramSpec['value']; 286 } else if ($paramSpec['location'] == 'query') { 287 if (is_array($paramSpec['value'])) { 288 foreach ($paramSpec['value'] as $value) { 289 $queryVars[] = $paramName . '=' . rawurlencode(rawurldecode($value)); 290 } 291 } else { 292 $queryVars[] = $paramName . '=' . rawurlencode(rawurldecode($paramSpec['value'])); 293 } 294 } 295 } 296 297 if (count($uriTemplateVars)) { 298 $uriTemplateParser = new UriTemplate(); 299 $requestUrl = $uriTemplateParser->parse($requestUrl, $uriTemplateVars); 300 } 301 302 if (count($queryVars)) { 303 $requestUrl .= '?' . implode('&', $queryVars); 304 } 305 306 return $requestUrl; 307 } 308} 309