Completed
Push — master ( d1be6d...8440b6 )
by Andrey
07:58
created

ServiceRestProxy::sendAsync()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 78

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 4
nop 8
dl 0
loc 78
rs 8.1687
c 0
b 0
f 0

How to fix   Long Method    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * LICENSE: The MIT License (the "License")
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 * https://github.com/azure/azure-storage-php/LICENSE
8
 *
9
 * Unless required by applicable law or agreed to in writing, software
10
 * distributed under the License is distributed on an "AS IS" BASIS,
11
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
 * See the License for the specific language governing permissions and
13
 * limitations under the License.
14
 *
15
 * PHP version 5
16
 *
17
 * @category  Microsoft
18
 * @package   MicrosoftAzure\Storage\Common\Internal
19
 * @author    Azure Storage PHP SDK <[email protected]>
20
 * @copyright 2016 Microsoft Corporation
21
 * @license   https://github.com/azure/azure-storage-php/LICENSE
22
 * @link      https://github.com/azure/azure-storage-php
23
 */
24
25
namespace MicrosoftAzure\Storage\Common\Internal;
26
27
use MicrosoftAzure\Storage\Common\Exceptions\ServiceException;
28
use MicrosoftAzure\Storage\Common\Internal\Resources;
29
use MicrosoftAzure\Storage\Common\Internal\Validate;
30
use MicrosoftAzure\Storage\Common\Internal\Utilities;
31
use MicrosoftAzure\Storage\Common\Internal\RetryMiddlewareFactory;
32
use MicrosoftAzure\Storage\Common\Models\ServiceOptions;
33
use MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext;
34
use MicrosoftAzure\Storage\Common\Internal\Middlewares\MiddlewareBase;
35
use MicrosoftAzure\Storage\Common\Middlewares\MiddlewareStack;
36
use MicrosoftAzure\Storage\Common\LocationMode;
37
use GuzzleHttp\Promise\PromiseInterface;
38
use GuzzleHttp\Promise\EachPromise;
39
use GuzzleHttp\Exception\RequestException;
40
use GuzzleHttp\Psr7\Response;
41
use GuzzleHttp\Psr7\Request;
42
use GuzzleHttp\Psr7\Uri;
43
use GuzzleHttp\Client;
44
use GuzzleHttp\Psr7;
45
use Psr\Http\Message\ResponseInterface;
46
47
/**
48
 * Base class for all services rest proxies.
49
 *
50
 * @ignore
51
 * @category  Microsoft
52
 * @package   MicrosoftAzure\Storage\Common\Internal
53
 * @author    Azure Storage PHP SDK <[email protected]>
54
 * @copyright 2016 Microsoft Corporation
55
 * @license   https://github.com/azure/azure-storage-php/LICENSE
56
 * @link      https://github.com/azure/azure-storage-php
57
 */
58
class ServiceRestProxy extends RestProxy
59
{
60
    private $accountName;
61
    private $psrPrimaryUri;
62
    private $psrSecondaryUri;
63
    private $options;
64
    private $client;
65
66
    /**
67
     * Initializes new ServiceRestProxy object.
68
     *
69
     * @param string                    $primaryUri     The storage account
70
     *                                                  primary uri.
71
     * @param string                    $secondaryUri   The storage account
72
     *                                                  secondary uri.
73
     * @param string                    $accountName    The name of the account.
74
     * @param Serialization\ISerializer $dataSerializer The data serializer.
75
     * @param array                     $options        Array of options for
76
     *                                                  the service
77
     */
78
    public function __construct(
79
        $primaryUri,
80
        $secondaryUri,
81
        $accountName,
82
        Serialization\ISerializer $dataSerializer,
83
        array $options = []
84
    ) {
85
        $primaryUri   = Utilities::appendDelimiter($primaryUri, '/');
86
        $secondaryUri = Utilities::appendDelimiter($secondaryUri, '/');
87
88
        parent::__construct($dataSerializer);
89
90
        $this->accountName     = $accountName;
91
        $this->psrPrimaryUri   = new Uri($primaryUri);
92
        $this->psrSecondaryUri = new Uri($secondaryUri);
93
        $this->options         = array_merge(array('http' => array()), $options);
94
        $this->client          = self::createClient($this->options['http']);
95
    }
96
97
    /**
98
     * Create a Guzzle client for future usage.
99
     *
100
     * @param  array $options Optional parameters for the client.
101
     *
102
     * @return Client
103
     */
104
    private static function createClient(array $options)
105
    {
106
        $verify = true;
107
        //Disable SSL if proxy has been set, and set the proxy in the client.
108
        $proxy = getenv('HTTP_PROXY');
109
        // For testing with Fiddler
110
        // $proxy = 'localhost:8888';
111
        // $verify = false;
112
        if (!empty($proxy)) {
113
            $options['proxy'] = $proxy;
114
        }
115
116
        return (new \GuzzleHttp\Client(
117
            array_merge(
118
                $options,
119
                array(
120
                    "defaults" => array(
121
                        "allow_redirects" => true,
122
                        "exceptions" => true,
123
                        "decode_content" => true,
124
                    ),
125
                    'cookies' => true,
126
                    'verify' => $verify,
127
                )
128
            )
129
        ));
130
    }
131
132
    /**
133
     * Gets the account name.
134
     *
135
     * @return string
136
     */
137
    public function getAccountName()
138
    {
139
        return $this->accountName;
140
    }
141
142
    /**
143
     * Create a middleware stack with given middleware.
144
     *
145
     * @param  ServiceOptions  $serviceOptions The options user passed in.
146
     *
147
     * @return MiddlewareStack
148
     */
149
    protected function createMiddlewareStack(ServiceOptions $serviceOptions)
150
    {
151
        //If handler stack is not defined by the user, create a default
152
        //middleware stack.
153
        $stack = null;
0 ignored issues
show
Unused Code introduced by
$stack is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
154
        if (array_key_exists('stack', $this->options['http'])) {
155
            $stack = $this->options['http']['stack'];
156
        } elseif ($serviceOptions->getMiddlewareStack() != null) {
157
            $stack = $serviceOptions->getMiddlewareStack();
158
        } else {
159
            $stack = new MiddlewareStack();
160
        }
161
162
        //Push all the middlewares specified in the $serviceOptions to the
163
        //handlerstack.
164
        if ($serviceOptions->getMiddlewares() != array()) {
165
            foreach ($serviceOptions->getMiddlewares() as $middleware) {
166
                $stack->push($middleware);
167
            }
168
        }
169
170
        //Push all the middlewares specified in the $options to the
171
        //handlerstack.
172
        if (array_key_exists('middlewares', $this->options)) {
173
            foreach ($this->options['middlewares'] as $middleware) {
174
                $stack->push($middleware);
175
            }
176
        }
177
178
        //Push all the middlewares specified in $this->middlewares to the
179
        //handlerstack.
180
        foreach ($this->getMiddlewares() as $middleware) {
181
            $stack->push($middleware);
182
        }
183
184
        return $stack;
185
    }
186
187
    /**
188
     * Send the requests concurrently. Number of concurrency can be modified
189
     * by inserting a new key/value pair with the key 'number_of_concurrency'
190
     * into the $requestOptions of $serviceOptions. Return only the promise.
191
     *
192
     * @param  callable $generator      the generator function to generate
193
     *                                  request upon fullfilment
194
     * @param  int      $statusCode     The expected status code for each of the
195
     *                                  request generated by generator.
196
     * @param  array    $requestOptions The service options for the concurrent
0 ignored issues
show
Documentation introduced by
There is no parameter named $requestOptions. Did you maybe mean $options?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
197
     *                                  requests.
198
     *
199
     * @return array
200
     */
201
    protected function sendConcurrentAsync(
202
        callable $generator,
203
        $statusCode,
204
        ServiceOptions $options
205
    ) {
206
        $client = $this->client;
207
        $middlewareStack = $this->createMiddlewareStack($options);
208
209
        $sendAsync = function ($request, $options) use ($client) {
210
            if ($request->getMethod() == 'HEAD') {
211
                $options['decode_content'] = false;
212
            }
213
            return $client->sendAsync($request, $options);
214
        };
215
216
        $handler = $middlewareStack->apply($sendAsync);
217
218
        $requestOptions = $this->generateRequestOptions($options, $handler);
219
220
        $promises = \call_user_func(
221
            function () use (
222
                $generator,
223
                $handler,
224
                $requestOptions
225
            ) {
226
                while (is_callable($generator) && ($request = $generator())) {
227
                    yield \call_user_func($handler, $request, $requestOptions);
228
                }
229
            }
230
        );
231
232
        $eachPromise = new EachPromise($promises, [
233
            'concurrency' => $options->getNumberOfConcurrency(),
234
            'fulfilled' => function ($response, $index) use ($statusCode) {
0 ignored issues
show
Unused Code introduced by
The parameter $index is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
235
                //the promise is fulfilled, evaluate the response
236
                self::throwIfError(
237
                    $response,
238
                    $statusCode
239
                );
240
            },
241
            'rejected' => function ($reason, $index) {
0 ignored issues
show
Unused Code introduced by
The parameter $index is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
242
                //Still rejected even if the retry logic has been applied.
243
                //Throwing exception.
244
                throw $reason;
245
            }
246
        ]);
247
        
248
        return $eachPromise->promise();
249
    }
250
251
252
    /**
253
     * Create the request to be sent.
254
     *
255
     * @param  string $method         The method of the HTTP request
256
     * @param  array  $headers        The header field of the request
257
     * @param  array  $queryParams    The query parameter of the request
258
     * @param  array  $postParameters The HTTP POST parameters
259
     * @param  string $path           URL path
260
     * @param  string $body           Request body
261
     *
262
     * @return \GuzzleHttp\Psr7\Request
263
     */
264
    protected function createRequest(
265
        $method,
266
        array $headers,
267
        array $queryParams,
268
        array $postParameters,
269
        $path,
270
        $locationMode,
271
        $body = Resources::EMPTY_STRING
272
    ) {
273
        if ($locationMode == LocationMode::SECONDARY_ONLY ||
274
            $locationMode == LocationMode::SECONDARY_THEN_PRIMARY) {
275
            $uri = $this->psrSecondaryUri;
276
        } else {
277
            $uri = $this->psrPrimaryUri;
278
        }
279
        
280
        //Append the path, not replacing it.
281
        if ($path != null) {
282
            $exPath = $uri->getPath();
283
            if ($exPath != '') {
284
                //Remove the duplicated slash in the path.
285
                if ($path != '' && $path[0] == '/') {
286
                    $path = $exPath . substr($path, 1);
287
                } else {
288
                    $path = $exPath . $path;
289
                }
290
            }
291
            $uri = $uri->withPath($path);
292
        }
293
294
        // add query parameters into headers
295
        if ($queryParams != null) {
296
            $queryString = Psr7\build_query($queryParams);
297
            $uri = $uri->withQuery($queryString);
298
        }
299
300
        // add post parameters into bodys
301
        $actualBody = null;
302
        if (empty($body)) {
303
            if (empty($headers['content-type'])) {
304
                $headers['content-type'] = 'application/x-www-form-urlencoded';
305
                $actualBody = Psr7\build_query($postParameters);
306
            }
307
        } else {
308
            $actualBody = $body;
309
        }
310
311
        $request = new Request(
312
            $method,
313
            $uri,
314
            $headers,
315
            $actualBody
316
        );
317
318
        //add content-length to header
319
        $bodySize = $request->getBody()->getSize();
320
        if ($bodySize > 0) {
321
            $request = $request->withHeader('content-length', $bodySize);
322
        }
323
        return $request;
324
    }
325
326
    /**
327
     * Create promise of sending HTTP request with the specified parameters.
328
     *
329
     * @param  string         $method         HTTP method used in the request
330
     * @param  array          $headers        HTTP headers.
331
     * @param  array          $queryParams    URL query parameters.
332
     * @param  array          $postParameters The HTTP POST parameters.
333
     * @param  string         $path           URL path
334
     * @param  array|int      $expected       Expected Status Codes.
335
     * @param  string         $body           Request body
336
     * @param  ServiceOptions $options        Service options
0 ignored issues
show
Documentation introduced by
There is no parameter named $options. Did you maybe mean $serviceOptions?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
337
     *
338
     * @return \GuzzleHttp\Promise\PromiseInterface
339
     */
340
    protected function sendAsync(
341
        $method,
342
        array $headers,
343
        array $queryParams,
344
        array $postParameters,
345
        $path,
346
        $expected = Resources::STATUS_OK,
347
        $body = Resources::EMPTY_STRING,
348
        ServiceOptions $serviceOptions = null
349
    ) {
350
        if ($serviceOptions == null) {
351
            $serviceOptions = new ServiceOptions();
352
        }
353
        $this->addOptionalQueryParam(
354
            $queryParams,
355
            Resources::QP_TIMEOUT,
356
            $serviceOptions->getTimeout()
357
        );
358
359
        $request = $this->createRequest(
360
            $method,
361
            $headers,
362
            $queryParams,
363
            $postParameters,
364
            $path,
365
            $serviceOptions->getLocationMode(),
366
            $body
367
        );
368
369
        $client = $this->client;
370
371
        $middlewareStack = $this->createMiddlewareStack($serviceOptions);
372
373
        $sendAsync = function ($request, $options) use ($client) {
374
            return $client->sendAsync($request, $options);
375
        };
376
377
        $handler = $middlewareStack->apply($sendAsync);
378
379
        $requestOptions =
380
            $this->generateRequestOptions($serviceOptions, $handler);
381
382
        if ($request->getMethod() == 'HEAD') {
383
            $requestOptions[Resources::ROS_DECODE_CONTENT] = false;
384
        }
385
386
        $promise = \call_user_func($handler, $request, $requestOptions);
387
388
        return $promise->then(
389
            function ($response) use ($expected, $requestOptions) {
390
                self::throwIfError(
391
                    $response,
392
                    $expected
393
                );
394
395
                return self::addLocationHeaderToResponse(
396
                    $response,
397
                    $requestOptions[Resources::ROS_LOCATION_MODE]
398
                );
399
            },
400
            function ($reason) use ($expected) {
401
                if (!($reason instanceof RequestException)) {
402
                    throw $reason;
403
                }
404
                $response = $reason->getResponse();
405
                if ($response != null) {
406
                    self::throwIfError(
407
                        $response,
408
                        $expected
409
                    );
410
                } else {
411
                    //if could not get response but promise rejected, throw reason.
412
                    throw $reason;
413
                }
414
                return $response;
415
            }
416
        );
417
    }
418
419
    /**
420
     * Generate the request options using the given service options and stored
421
     * information.
422
     *
423
     * @param  ServiceOptions $serviceOptions The service options used to
424
     *                                        generate request options.
425
     * @param  callable       $handler        The handler used to send the
426
     *                                        request.
427
     * @return array
428
     */
429
    protected function generateRequestOptions(
430
        ServiceOptions $serviceOptions,
431
        callable $handler
432
    ) {
433
        $result = array();
434
        $result[Resources::ROS_LOCATION_MODE]  = $serviceOptions->getLocationMode();
435
        $result[Resources::ROS_STREAM]         = $serviceOptions->getIsStreaming();
436
        $result[Resources::ROS_DECODE_CONTENT] = $serviceOptions->getDecodeContent();
437
        $result[Resources::ROS_HANDLER]        = $handler;
438
        $result[Resources::ROS_SECONDARY_URI]  = $this->getPsrSecondaryUri();
439
        $result[Resources::ROS_PRIMARY_URI]    = $this->getPsrPrimaryUri();
440
441
        return $result;
442
    }
443
444
    /**
445
     * Sends the context.
446
     *
447
     * @param  HttpCallContext $context The context of the request.
448
     * @return \GuzzleHttp\Psr7\Response
449
     */
450
    protected function sendContext(HttpCallContext $context)
451
    {
452
        return $this->sendContextAsync($context)->wait();
453
    }
454
455
    /**
456
     * Creates the promise to send the context.
457
     *
458
     * @param  HttpCallContext $context The context of the request.
459
     *
460
     * @return \GuzzleHttp\Promise\PromiseInterface
461
     */
462
    protected function sendContextAsync(HttpCallContext $context)
463
    {
464
        return $this->sendAsync(
465
            $context->getMethod(),
466
            $context->getHeaders(),
467
            $context->getQueryParameters(),
468
            $context->getPostParameters(),
469
            $context->getPath(),
470
            $context->getStatusCodes(),
471
            $context->getBody(),
472
            $context->getServiceOptions()
473
        );
474
    }
475
476
    /**
477
     * Throws ServiceException if the recieved status code is not expected.
478
     *
479
     * @param ResponseInterface $response The response received
480
     * @param array|int         $expected The expected status codes.
481
     *
482
     * @return void
483
     *
484
     * @throws ServiceException
485
     */
486
    public static function throwIfError(ResponseInterface $response, $expected)
487
    {
488
        $expectedStatusCodes = is_array($expected) ? $expected : array($expected);
489
490
        if (!in_array($response->getStatusCode(), $expectedStatusCodes)) {
491
            throw new ServiceException($response);
492
        }
493
    }
494
    
495
    /**
496
     * Adds HTTP POST parameter to the specified
497
     *
498
     * @param array  $postParameters An array of HTTP POST parameters.
499
     * @param string $key            The key of a HTTP POST parameter.
500
     * @param string $value          the value of a HTTP POST parameter.
501
     *
502
     * @return array
503
     */
504
    public function addPostParameter(
505
        array $postParameters,
506
        $key,
507
        $value
508
    ) {
509
        Validate::isArray($postParameters, 'postParameters');
510
        $postParameters[$key] = $value;
511
        return $postParameters;
512
    }
513
514
    /**
515
     * Groups set of values into one value separated with Resources::SEPARATOR
516
     *
517
     * @param array $values array of values to be grouped.
518
     *
519
     * @return string
520
     */
521
    public static function groupQueryValues(array $values)
522
    {
523
        Validate::isArray($values, 'values');
524
        $joined = Resources::EMPTY_STRING;
525
526
        sort($values);
527
528
        foreach ($values as $value) {
529
            if (!is_null($value) && !empty($value)) {
530
                $joined .= $value . Resources::SEPARATOR;
531
            }
532
        }
533
534
        return trim($joined, Resources::SEPARATOR);
535
    }
536
537
    /**
538
     * Adds metadata elements to headers array
539
     *
540
     * @param array $headers  HTTP request headers
541
     * @param array $metadata user specified metadata
542
     *
543
     * @return array
544
     */
545
    protected function addMetadataHeaders(array $headers, array $metadata = null)
546
    {
547
        Utilities::validateMetadata($metadata);
548
549
        $metadata = $this->generateMetadataHeaders($metadata);
550
        $headers  = array_merge($headers, $metadata);
551
552
        return $headers;
553
    }
554
555
    /**
556
     * Generates metadata headers by prefixing each element with 'x-ms-meta'.
557
     *
558
     * @param array $metadata user defined metadata.
559
     *
560
     * @return array
561
     */
562
    public function generateMetadataHeaders(array $metadata = null)
563
    {
564
        $metadataHeaders = array();
565
566
        if (is_array($metadata) && !is_null($metadata)) {
567
            foreach ($metadata as $key => $value) {
568
                $headerName = Resources::X_MS_META_HEADER_PREFIX;
569
                if (strpos($value, "\r") !== false
570
                    || strpos($value, "\n") !== false
571
                ) {
572
                    throw new \InvalidArgumentException(Resources::INVALID_META_MSG);
573
                }
574
575
                // Metadata name is case-presrved and case insensitive
576
                $headerName                     .= $key;
577
                $metadataHeaders[$headerName] = $value;
578
            }
579
        }
580
581
        return $metadataHeaders;
582
    }
583
584
    /**
585
     * Get the primary URI in PSR form.
586
     *
587
     * @return Uri
588
     */
589
    public function getPsrPrimaryUri()
590
    {
591
        return $this->psrPrimaryUri;
592
    }
593
594
    /**
595
     * Get the secondary URI in PSR form.
596
     *
597
     * @return Uri
598
     */
599
    public function getPsrSecondaryUri()
600
    {
601
        return $this->psrSecondaryUri;
602
    }
603
604
    /**
605
     * Adds the header that indicates the location mode to the response header.
606
     *
607
     * @return  ResponseInterface
608
     */
609
    private static function addLocationHeaderToResponse(
610
        ResponseInterface $response,
611
        $locationMode
612
    ) {
613
        //If the response already has this header, return itself.
614
        if ($response->hasHeader(Resources::X_MS_CONTINUATION_LOCATION_MODE)) {
615
            return $response;
616
        }
617
        //Otherwise, add the header that indicates the endpoint to be used if
618
        //continuation token is used for subsequent request. Notice that if the
619
        //response does not have location header set at the moment, it means
620
        //that the user have not set a retry middleware.
621
        if ($locationMode == LocationMode::PRIMARY_THEN_SECONDARY) {
622
            $response = $response->withHeader(
623
                Resources::X_MS_CONTINUATION_LOCATION_MODE,
624
                LocationMode::PRIMARY_ONLY
625
            );
626
        } elseif ($locationMode == LocationMode::SECONDARY_THEN_PRIMARY) {
627
            $response = $response->withHeader(
628
                Resources::X_MS_CONTINUATION_LOCATION_MODE,
629
                LocationMode::SECONDARY_ONLY
630
            );
631
        } elseif ($locationMode == LocationMode::SECONDARY_ONLY  ||
632
                  $locationMode == LocationMode::PRIMARY_ONLY) {
633
            $response = $response->withHeader(
634
                Resources::X_MS_CONTINUATION_LOCATION_MODE,
635
                $locationMode
636
            );
637
        }
638
        return $response;
639
    }
640
}
641