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