Completed
Push — master ( 0e12ee...cb89b4 )
by Nate
02:30
created

MethodBodyBuilder::setResponseType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
/*
3
 * Copyright (c) 2015 Nate Brunette.
4
 * Distributed under the MIT License (http://opensource.org/licenses/MIT)
5
 */
6
7
namespace Tebru\Retrofit\Generation\Builder;
8
9
use Tebru;
10
use Tebru\Dynamo\Model\Body;
11
12
/**
13
 * Class MethodBodyBuilder
14
 *
15
 * @author Nate Brunette <[email protected]>
16
 */
17
class MethodBodyBuilder
18
{
19
    /**
20
     * @var Body
21
     */
22
    private $methodBody;
23
    
24
    /**
25
     * @var string
26
     */
27
    private $baseUrl;
28
29
    /**
30
     * Request method
31
     *
32
     * @var string
33
     */
34
    private $requestMethod;
35
36
    /**
37
     * Request uri
38
     *
39
     * @var string
40
     */
41
    private $uri;
42
43
    /**
44
     * Array of query parameters
45
     *
46
     * @var array
47
     */
48
    private $queries = [];
49
50
    /**
51
     * Variable name of query map argument
52
     *
53
     * @var string
54
     */
55
    private $queryMap;
56
57
    /**
58
     * Request headers
59
     *
60
     * @var array
61
     */
62
    private $headers = [];
63
64
    /**
65
     * A string of php code to describe how to create the body
66
     *
67
     * @var string
68
     */
69
    private $body;
70
71
    /**
72
     * An array of body parts
73
     *
74
     * @var array
75
     */
76
    private $bodyParts = [];
77
78
    /**
79
     * True if the body is an object
80
     *
81
     * @var bool
82
     */
83
    private $bodyIsObject = false;
84
85
    /**
86
     * True if the body is an optional paramter
87
     *
88
     * @var bool
89
     */
90
    private $bodyIsOptional = false;
91
92
    /**
93
     * True if the body implements \JsonSerializable
94
     *
95
     * @var boolean
96
     */
97
    private $bodyIsJsonSerializable;
98
99
    /**
100
     * True if the body is an array
101
     *
102
     * @var bool
103
     */
104
    private $bodyIsArray = false;
105
106
    /**
107
     * If we should json encode the body
108
     *
109
     * @var bool
110
     */
111
    private $jsonEncode = false;
112
113
    /**
114
     * If the body should be sent form urlencoded
115
     *
116
     * @var bool
117
     */
118
    private $formUrlEncoded = false;
119
120
    /**
121
     * If the body should be sent as multipart
122
     *
123
     * @var bool
124
     */
125
    private $multipartEncoded = false;
126
127
    /**
128
     * Method return type
129
     *
130
     * @var string
131
     */
132
    private $returnType = 'array';
133
134
    /**
135
     * JMS Serializer serialization context
136
     *
137
     * @var array
138
     */
139
    private $serializationContext = [];
140
141
    /**
142
     * JMS Serializer deserialization attributes
143
     *
144
     * @var array
145
     */
146
    private $deserializationContext = [];
147
148
    /**
149
     * Request callback variable name
150
     *
151
     * @var string
152
     */
153
    private $callback;
154
155
    /**
156
     * Async callback is optional
157
     *
158
     * @var bool
159
     */
160
    private $callbackOptional = false;
161
162
    /**
163
     * Boundary Id for multipart requests
164
     *
165
     * @var string
166
     */
167
    private $boundaryId;
168
169
    /**
170
     * @var string
171
     */
172
    private $responseType;
173
174
    /**
175
     * Constructor
176
     */
177
    public function __construct()
178
    {
179
        $this->methodBody = new Body();
180
    }
181
182
    /**
183
     * @param string $baseUrl
184
     */
185
    public function setBaseUrl($baseUrl)
186
    {
187
        $this->baseUrl = $baseUrl;
188
    }
189
190
    /**
191
     * @param string $requestMethod
192
     */
193
    public function setRequestMethod($requestMethod)
194
    {
195
        $this->requestMethod = $requestMethod;
196
    }
197
198
    /**
199
     * @param string $uri
200
     */
201
    public function setUri($uri)
202
    {
203
        $this->uri = $uri;
204
    }
205
206
    /**
207
     * @param array $queries
208
     */
209
    public function setQueries(array $queries)
210
    {
211
        $this->queries = $queries;
212
    }
213
214
    /**
215
     * @param string $queryMap
216
     */
217
    public function setQueryMap($queryMap)
218
    {
219
        $this->queryMap = $queryMap;
220
    }
221
222
    /**
223
     * @param array $headers
224
     */
225
    public function setHeaders(array $headers)
226
    {
227
        $this->headers = $headers;
228
    }
229
230
    /**
231
     * @param string $body
232
     */
233
    public function setBody($body)
234
    {
235
        $this->body = $body;
236
    }
237
238
    /**
239
     * @param array $bodyParts
240
     */
241
    public function setBodyParts(array $bodyParts)
242
    {
243
        $this->bodyParts = $bodyParts;
244
    }
245
246
    /**
247
     * @param boolean $bodyIsObject
248
     */
249
    public function setBodyIsObject($bodyIsObject)
250
    {
251
        $this->bodyIsObject = $bodyIsObject;
252
    }
253
254
    /**
255
     * @param boolean $bodyIsOptional
256
     */
257
    public function setBodyIsOptional($bodyIsOptional)
258
    {
259
        $this->bodyIsOptional = $bodyIsOptional;
260
    }
261
262
    /**
263
     * @param boolean $bodyIsJsonSerializable
264
     */
265
    public function setBodyIsJsonSerializable($bodyIsJsonSerializable)
266
    {
267
        $this->bodyIsJsonSerializable = $bodyIsJsonSerializable;
268
    }
269
270
    /**
271
     * @param boolean $bodyIsArray
272
     */
273
    public function setBodyIsArray($bodyIsArray)
274
    {
275
        $this->bodyIsArray = $bodyIsArray;
276
    }
277
278
    /**
279
     * @param boolean $jsonEncode
280
     */
281
    public function setJsonEncode($jsonEncode)
282
    {
283
        $this->jsonEncode = $jsonEncode;
284
    }
285
286
    /**
287
     * @param boolean $formUrlEncoded
288
     */
289
    public function setFormUrlEncoded($formUrlEncoded)
290
    {
291
        $this->formUrlEncoded = $formUrlEncoded;
292
    }
293
294
    /**
295
     * @param boolean $multipartEncoded
296
     */
297
    public function setMultipartEncoded($multipartEncoded)
298
    {
299
        $this->multipartEncoded = $multipartEncoded;
300
    }
301
302
    /**
303
     * @param string $boundaryId
304
     */
305
    public function setBoundaryId($boundaryId)
306
    {
307
        $this->boundaryId = $boundaryId;
308
    }
309
310
    /**
311
     * @param string $returnType
312
     */
313
    public function setReturnType($returnType)
314
    {
315
        $this->returnType = $returnType;
316
    }
317
318
    /**
319
     * @param array $serializationContext
320
     */
321
    public function setSerializationContext(array $serializationContext)
322
    {
323
        $this->serializationContext = $serializationContext;
324
    }
325
326
    /**
327
     * @param array $deserializationContext
328
     */
329
    public function setDeserializationContext(array $deserializationContext)
330
    {
331
        $this->deserializationContext = $deserializationContext;
332
    }
333
334
    /**
335
     * @param string $callback
336
     */
337
    public function setCallback($callback)
338
    {
339
        $this->callback = $callback;
340
    }
341
342
    /**
343
     * @param boolean $callbackOptional
344
     */
345
    public function setCallbackOptional($callbackOptional)
346
    {
347
        $this->callbackOptional = $callbackOptional;
348
    }
349
350
    /**
351
     * @param string $responseType
352
     */
353
    public function setResponseType($responseType)
354
    {
355
        $this->responseType = $responseType;
356
    }
357
358
    /**
359
     * Build the method body
360
     *
361
     * @return string
362
     */
363
    public function build()
364
    {
365
        Tebru\assertThat(null === $this->body || empty($this->bodyParts), 'Cannot have both @Body and @Part annotations');
366
367
        $this->createRequestUrl();
368
        $this->createHeaders();
369
        $this->createBody();
370
        $this->createResponse();
371
        $this->createReturns();
372
373
        return (string) $this->methodBody;
374
    }
375
376
    /**
377
     * Build the request url
378
     */
379
    private function createRequestUrl()
380
    {
381
        Tebru\assertNotNull($this->uri, 'Request annotation not found (e.g. @GET, @POST)');
382
383
        $baseUrl = (null !== $this->baseUrl) ? $this->baseUrl : '$this->baseUrl';
384
385
        // request request params using http_build_query if we have a query map
386
        if (null !== $this->queryMap) {
387
            // if we have regular queries, add them to the query builder
388
            if (!empty($this->queries)) {
389
                $queryArray = $this->arrayToString($this->queries);
390
                $this->methodBody->add('$queryArray = \Tebru\Retrofit\Generation\Manipulator\QueryManipulator::boolToString(%s + %s);', $queryArray, $this->queryMap);
391
                $this->methodBody->add('$queryString = http_build_query($queryArray);');
392
            } else {
393
                $this->methodBody->add('$queryArray = \Tebru\Retrofit\Generation\Manipulator\QueryManipulator::boolToString(%s);', $this->queryMap);
394
                $this->methodBody->add('$queryString = http_build_query($queryArray);');
395
            }
396
397
            $this->methodBody->add('$requestUrl = %s . "%s?" . $queryString;', $baseUrl, $this->uri);
398
399
            // if we have queries, add them to the request url
400
        } elseif (!empty($this->queries)) {
401
            $queryArray = $this->arrayToString($this->queries);
402
            $this->methodBody->add('$queryArray = \Tebru\Retrofit\Generation\Manipulator\QueryManipulator::boolToString(%s);', $queryArray);
403
            $this->methodBody->add('$queryString = http_build_query($queryArray);');
404
            $this->methodBody->add('$requestUrl = %s . "%s" . "?" . $queryString;', $baseUrl, $this->uri);
405
        } else {
406
            $this->methodBody->add('$requestUrl = %s . "%s";', $baseUrl, $this->uri);
407
        }
408
    }
409
410
    /**
411
     * Build the headers
412
     */
413
    private function createHeaders()
414
    {
415
        if (empty($this->headers)) {
416
            $this->methodBody->add('$headers = [];');
417
418
            return;
419
        }
420
421
        $this->methodBody->add('$headers = %s;', $this->arrayToString($this->headers));
422
    }
423
424
    /**
425
     * Build the request body
426
     */
427
    private function createBody()
428
    {
429
        // body should be null if there isn't a body or parts
430
        if (null === $this->body && empty($this->bodyParts)) {
431
            $this->methodBody->add('$body = null;');
432
433
            return;
434
        }
435
436
        // if body is optional and 'empty' it should be null
437
        if ($this->bodyIsOptional) {
438
            $this->methodBody->add('if (empty(%s)) { $body = null; } else {', $this->body);
439
        }
440
441
        $this->doCreateBody();
442
443
        if ($this->bodyIsOptional) {
444
            $this->methodBody->add('}');
445
        }
446
    }
447
448
    /**
449
     * Logic to actually create body
450
     */
451
    private function doCreateBody()
452
    {
453
        // check if body is a string, set variable if it doesn't already equal '$body'
454
        if (!$this->bodyIsArray && !$this->bodyIsObject && empty($this->bodyParts)) {
455
            if ('$body' !== $this->body) {
456
                $this->methodBody->add('$body = %s;', $this->body);
457
            }
458
459
            return;
460
        }
461
462
        // if we're json encoding, we don't need the body as an array first
463
        if ($this->jsonEncode) {
464
            $this->createBodyJson();
465
466
            return;
467
        }
468
469
        // otherwise, we do need to convert the body to an array
470
        $this->createBodyArray();
471
472
        if ($this->formUrlEncoded) {
473
            $this->methodBody->add('$bodyArray = \Tebru\Retrofit\Generation\Manipulator\QueryManipulator::boolToString($bodyArray);');
474
            $this->methodBody->add('$body = http_build_query($bodyArray);');
475
476
            return;
477
        }
478
479
        // body is multipart
480
        if ($this->multipartEncoded) {
481
            $this->methodBody->add('$bodyParts = [];');
482
            $this->methodBody->add('foreach ($bodyArray as $key => $value) {');
483
            $this->methodBody->add('$file = null;');
484
            $this->methodBody->add('if (is_resource($value)) { $file = $value; }');
485
            $this->methodBody->add('if (is_string($value)) { $file = fopen($value, "r"); }');
486
            $this->methodBody->add('if (!is_resource($file)) { throw new \LogicException("Expected resource or file path"); }');
487
            $this->methodBody->add('$bodyParts[] = ["name" => $key, "contents" => $file];');
488
            $this->methodBody->add('}');
489
490
            $this->methodBody->add('$body = new \GuzzleHttp\Psr7\MultipartStream($bodyParts, "%s");', $this->boundaryId);
491
        }
492
    }
493
494
    /**
495
     * Create json body
496
     */
497 View Code Duplication
    private function createBodyJson()
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
498
    {
499
        // json encode arrays
500
        if ($this->bodyIsArray) {
501
            $this->methodBody->add('$body = json_encode(%s);', $this->body);
502
503
            return;
504
        }
505
506
        // if parts exist, json encode the parts
507
        if (!empty($this->bodyParts)) {
508
            $this->methodBody->add('$body = json_encode(%s);', $this->arrayToString($this->bodyParts));
509
510
            return;
511
        }
512
513
        // if it's an object, serialize it unless it implements \JsonSerializable
514
        if ($this->bodyIsObject) {
515
            if ($this->bodyIsJsonSerializable) {
516
                $this->methodBody->add('$body = json_encode(%s);', $this->body);
517
518
                return;
519
            }
520
521
            $this->serializeObject('$bodySerializationContext', '$body', $this->body);
522
        }
523
    }
524
525
    /**
526
     * Normalize body as array
527
     */
528 View Code Duplication
    private function createBodyArray()
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
529
    {
530
        // if it's already an array, set to variable
531
        if ($this->bodyIsArray) {
532
            $this->methodBody->add('$bodyArray = %s;', $this->body);
533
534
            return;
535
        }
536
537
        // if parts exist, set to variable
538
        if (!empty($this->bodyParts)) {
539
            $this->methodBody->add('$bodyArray = %s;', $this->arrayToString($this->bodyParts));
540
541
            return;
542
        }
543
544
        // if it implements \JsonSerializable, call jsonSerialize() to get array
545
        if ($this->bodyIsJsonSerializable) {
546
            $this->methodBody->add('$bodyArray = %s->jsonSerialize();', $this->body);
547
548
            return;
549
        }
550
551
        // otherwise, serialize and json_decode()
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
552
        $this->serializeObject('$bodySerializationContext', '$serializedBody', $this->body);
553
        $this->methodBody->add('$bodyArray = json_decode($serializedBody, true);');
554
    }
555
556
    /**
557
     * Helper method to serialize an object
558
     *
559
     * @param string $contextVar
560
     * @param string $bodyVar
561
     * @param string $object
562
     */
563
    private function serializeObject($contextVar, $bodyVar, $object)
564
    {
565
        $this->methodBody->add('%s = \JMS\Serializer\SerializationContext::create();', $contextVar);
566
        if (!empty($this->serializationContext)) {
567
            $this->createContext($contextVar, $this->serializationContext);
568
        }
569
570
        $this->methodBody->add('%s = $this->serializer->serialize(%s, "json", %s);', $bodyVar, $object, $contextVar);
571
    }
572
573
    /**
574
     * Build the response
575
     */
576
    private function createResponse()
577
    {
578
        $this->methodBody->add('$request = new \GuzzleHttp\Psr7\Request("%s", $requestUrl, $headers, $body);', strtoupper($this->requestMethod));
579
        $this->methodBody->add('$beforeSendEvent = new \Tebru\Retrofit\Event\BeforeSendEvent($request);');
580
        $this->methodBody->add('$this->eventDispatcher->dispatch("retrofit.beforeSend", $beforeSendEvent);');
581
        $this->methodBody->add('$request = $beforeSendEvent->getRequest();');
582
        $this->methodBody->add('try {');
583
584 View Code Duplication
        if ($this->callback !== null && $this->callbackOptional) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
585
            $this->methodBody->add('if (%s !== null) {', $this->callback);
586
            $this->methodBody->add('$response = $this->client->sendAsync($request, %s);', $this->callback);
587
            $this->methodBody->add('} else {');
588
            $this->methodBody->add('$response = $this->client->send($request);');
589
            $this->methodBody->add('}');
590
        } elseif ($this->callback !== null && !$this->callbackOptional) {
591
            $this->methodBody->add('$response = $this->client->sendAsync($request, %s);', $this->callback);
592
        } else {
593
            $this->methodBody->add('$response = $this->client->send($request);');
594
        }
595
596
        $this->methodBody->add('} catch (\Exception $exception) {');
597
        $this->methodBody->add('$apiExceptionEvent = new \Tebru\Retrofit\Event\ApiExceptionEvent($exception, $request);');
598
        $this->methodBody->add('$this->eventDispatcher->dispatch("retrofit.apiException", $apiExceptionEvent);');
599
        $this->methodBody->add('$exception = $apiExceptionEvent->getException();');
600
        $this->methodBody->add('throw new \Tebru\Retrofit\Exception\RetrofitApiException(get_class($this), $exception->getMessage(), $exception->getCode(), $exception);');
601
        $this->methodBody->add('}');
602
        $this->methodBody->add('$afterSendEvent = new \Tebru\Retrofit\Event\AfterSendEvent($request, $response);');
603
        $this->methodBody->add('$this->eventDispatcher->dispatch("retrofit.afterSend", $afterSendEvent);');
604
        $this->methodBody->add('$response = $afterSendEvent->getResponse();');
605
    }
606
607
    /**
608
     * Build the return
609
     */
610
    private function createReturns()
611
    {
612 View Code Duplication
        if ($this->callback !== null && $this->callbackOptional) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
613
            $this->methodBody->add('if (%s !== null) {', $this->callback);
614
            $this->methodBody->add('$returnEvent = new \Tebru\Retrofit\Event\ReturnEvent(null);');
615
            $this->methodBody->add('$this->eventDispatcher->dispatch("retrofit.return", $returnEvent);');
616
            $this->methodBody->add('return $returnEvent->getReturn();');
617
            $this->methodBody->add('}');
618
        } elseif ($this->callback !== null && !$this->callbackOptional) {
619
            $this->methodBody->add('$returnEvent = new \Tebru\Retrofit\Event\ReturnEvent(null);');
620
            $this->methodBody->add('$this->eventDispatcher->dispatch("retrofit.return", $returnEvent);');
621
            $this->methodBody->add('return $returnEvent->getReturn();');
622
623
            return;
624
        }
625
626
        $returnType = (null === $this->responseType) ? $this->returnType : $this->responseType;
627
        $responseReturn = (null !== $this->responseType);
628
629
        $this->methodBody->add(
630
            '$retrofitResponse = new \Tebru\Retrofit\Http\Response($response, "%s", $this->serializer, %s);',
631
            $returnType,
632
            $this->arrayToString($this->deserializationContext)
633
        );
634
635
        if ($responseReturn) {
636
            $this->methodBody->add('$return = $retrofitResponse;');
637
        } else {
638
            $this->methodBody->add('$return = $retrofitResponse->body();');
639
        }
640
641
        $this->methodBody->add('$returnEvent = new \Tebru\Retrofit\Event\ReturnEvent($return);');
642
        $this->methodBody->add('$this->eventDispatcher->dispatch("retrofit.return", $returnEvent);');
643
        $this->methodBody->add('return $returnEvent->getReturn();');
644
    }
645
646
    /**
647
     * Build the serialization context
648
     *
649
     * @param $contextVar
650
     * @param $context
651
     */
652
    private function createContext($contextVar, &$context)
653
    {
654
        if (!empty($context['groups'])) {
655
            $this->methodBody->add('%s->setGroups(%s);', $contextVar, $this->arrayToString($context['groups']));
656
        }
657
658
        if (!empty($context['version'])) {
659
            $this->methodBody->add('%s->setVersion(%d);', $contextVar, (int) $context['version']);
660
        }
661
662
        if (!empty($context['serializeNull'])) {
663
            $this->methodBody->add('%s->setSerializeNull(%d);', $contextVar, (bool) $context['serializeNull']);
664
        }
665
666
        if (!empty($context['enableMaxDepthChecks'])) {
667
            $this->methodBody->add('%s->enableMaxDepthChecks();', $contextVar);
668
        }
669
670 View Code Duplication
        if (!empty($context['attributes'])) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
671
            foreach ($context['attributes'] as $key => $value) {
672
                $this->methodBody->add('%s->setAttribute("%s", "%s");', $contextVar, $key, $value);
673
            }
674
        }
675
    }
676
677
    /**
678
     * Create a string representation of an array
679
     *
680
     * @param array $array
681
     * @return string
682
     */
683
    private function arrayToString(array $array)
684
    {
685
        $string = var_export($array, true);
686
        $string = preg_replace('/\'\$(.+)\'/', '$' . '\\1', $string);
687
688
        return $string;
689
    }
690
}
691