Completed
Push — master ( 4cc180...eceda3 )
by Nate
03:04
created

MethodBodyBuilder::createDepthContext()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 10
rs 9.4285
cc 2
eloc 6
nc 2
nop 0
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
     * The default body value
94
     *
95
     * @var mixed
96
     */
97
    private $bodyDefaultValue;
98
99
    /**
100
     * True if the body implements \JsonSerializable
101
     *
102
     * @var boolean
103
     */
104
    private $bodyIsJsonSerializable;
105
106
    /**
107
     * True if the body is an array
108
     *
109
     * @var bool
110
     */
111
    private $bodyIsArray = false;
112
113
    /**
114
     * If we should json encode the body
115
     *
116
     * @var bool
117
     */
118
    private $jsonEncode = false;
119
120
    /**
121
     * Method return type
122
     *
123
     * @var string
124
     */
125
    private $returnType = 'array';
126
127
    /**
128
     * JMS Serializer serialization context
129
     *
130
     * @var array
131
     */
132
    private $serializationContext = [];
133
134
    /**
135
     * JMS Serializer deserialization attributes
136
     *
137
     * @var array
138
     */
139
    private $deserializationContext = [];
140
141
    /**
142
     * Request callback variable name
143
     *
144
     * @var string
145
     */
146
    private $callback;
147
148
    /**
149
     * Async callback is optional
150
     *
151
     * @var bool
152
     */
153
    private $callbackOptional = false;
154
155
    /**
156
     * Constructor
157
     */
158
    public function __construct()
159
    {
160
        $this->methodBody = new Body();
161
    }
162
163
    /**
164
     * @param string $baseUrl
165
     */
166
    public function setBaseUrl($baseUrl)
167
    {
168
        $this->baseUrl = $baseUrl;
169
    }
170
171
    /**
172
     * @param string $requestMethod
173
     */
174
    public function setRequestMethod($requestMethod)
175
    {
176
        $this->requestMethod = $requestMethod;
177
    }
178
179
    /**
180
     * @param string $uri
181
     */
182
    public function setUri($uri)
183
    {
184
        $this->uri = $uri;
185
    }
186
187
    /**
188
     * @param array $queries
189
     */
190
    public function setQueries(array $queries)
191
    {
192
        $this->queries = $queries;
193
    }
194
195
    /**
196
     * @param string $queryMap
197
     */
198
    public function setQueryMap($queryMap)
199
    {
200
        $this->queryMap = $queryMap;
201
    }
202
203
    /**
204
     * @param array $headers
205
     */
206
    public function setHeaders(array $headers)
207
    {
208
        $this->headers = $headers;
209
    }
210
211
    /**
212
     * @param string $body
213
     */
214
    public function setBody($body)
215
    {
216
        $this->body = $body;
217
    }
218
219
    /**
220
     * @param array $bodyParts
221
     */
222
    public function setBodyParts(array $bodyParts)
223
    {
224
        $this->bodyParts = $bodyParts;
225
    }
226
227
    /**
228
     * @param boolean $bodyIsObject
229
     */
230
    public function setBodyIsObject($bodyIsObject)
231
    {
232
        $this->bodyIsObject = $bodyIsObject;
233
    }
234
235
    /**
236
     * @param boolean $bodyIsOptional
237
     */
238
    public function setBodyIsOptional($bodyIsOptional)
239
    {
240
        $this->bodyIsOptional = $bodyIsOptional;
241
    }
242
243
    /**
244
     * @param mixed $bodyDefaultValue
245
     */
246
    public function setBodyDefaultValue($bodyDefaultValue)
247
    {
248
        $this->bodyDefaultValue = $bodyDefaultValue;
249
    }
250
251
    /**
252
     * @param boolean $bodyIsJsonSerializable
253
     */
254
    public function setBodyIsJsonSerializable($bodyIsJsonSerializable)
255
    {
256
        $this->bodyIsJsonSerializable = $bodyIsJsonSerializable;
257
    }
258
259
    /**
260
     * @param boolean $bodyIsArray
261
     */
262
    public function setBodyIsArray($bodyIsArray)
263
    {
264
        $this->bodyIsArray = $bodyIsArray;
265
    }
266
267
    /**
268
     * @param boolean $jsonEncode
269
     */
270
    public function setJsonEncode($jsonEncode)
271
    {
272
        $this->jsonEncode = $jsonEncode;
273
    }
274
275
    /**
276
     * @param string $returnType
277
     */
278
    public function setReturnType($returnType)
279
    {
280
        $this->returnType = $returnType;
281
    }
282
283
    /**
284
     * @param array $serializationContext
285
     */
286
    public function setSerializationContext(array $serializationContext)
287
    {
288
        $this->serializationContext = $serializationContext;
289
    }
290
291
    /**
292
     * @param array $deserializationContext
293
     */
294
    public function setDeserializationContext(array $deserializationContext)
295
    {
296
        $this->deserializationContext = $deserializationContext;
297
    }
298
299
    /**
300
     * @param string $callback
301
     */
302
    public function setCallback($callback)
303
    {
304
        $this->callback = $callback;
305
    }
306
307
    /**
308
     * @param boolean $callbackOptional
309
     */
310
    public function setCallbackOptional($callbackOptional)
311
    {
312
        $this->callbackOptional = $callbackOptional;
313
    }
314
315
    /**
316
     * Build the method body
317
     *
318
     * @return string
319
     */
320
    public function build()
321
    {
322
        $this->createRequestUrl();
323
        $this->createHeaders();
324
        $this->createBody();
325
        $this->createResponse();
326
        $this->createReturns();
327
328
        return (string)$this->methodBody;
329
    }
330
331
    /**
332
     * Build the request url
333
     */
334
    private function createRequestUrl()
335
    {
336
        Tebru\assertNotNull($this->uri, 'Request annotation not found (e.g. @GET, @POST)');
337
338
        $baseUrl = (null !== $this->baseUrl) ? $this->baseUrl : '$this->baseUrl';
339
340
        // request request params using http_build_query if we have a query map
341
        if (null !== $this->queryMap) {
342
            // if we have regular queries, add them to the query builder
343 View Code Duplication
            if (!empty($this->queries)) {
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...
344
                $queryArray = $this->arrayToString($this->queries);
345
                $this->methodBody->add('$queryString = http_build_query(%s + %s);', $queryArray, $this->queryMap);
346
            } else {
347
                $this->methodBody->add('$queryString = http_build_query(%s);', $this->queryMap);
348
            }
349
350
            $this->methodBody->add('$requestUrl = %s . "%s?" . $queryString;', $baseUrl, $this->uri);
351
352
            // if we have queries, add them to the request url
353 View Code Duplication
        } elseif (!empty($this->queries)) {
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...
354
            $queryArray = $this->arrayToString($this->queries);
355
            $this->methodBody->add('$queryString = http_build_query(%s);', $queryArray);
356
            $this->methodBody->add('$requestUrl = %s . "%s" . "?" . $queryString;', $baseUrl, $this->uri);
357
        } else {
358
            $this->methodBody->add('$requestUrl = %s . "%s";', $baseUrl, $this->uri);
359
        }
360
    }
361
362
    /**
363
     * Build the headers
364
     */
365
    private function createHeaders()
366
    {
367
        if (empty($this->headers)) {
368
            $this->methodBody->add('$headers = [];');
369
370
            return;
371
        }
372
373
        $this->methodBody->add('$headers = %s;', $this->arrayToString($this->headers));
374
    }
375
376
    /**
377
     * Build the request body
378
     */
379
    private function createBody()
380
    {
381
        if (null === $this->body && empty($this->bodyParts)) {
382
            $this->methodBody->add('$body = null;');
383
384
            return;
385
        }
386
387
        Tebru\assertThat(null === $this->body || empty($this->bodyParts), 'Cannot have both @Body and @Part annotations');
388
389
        if ($this->bodyIsObject) {
390
            if ($this->bodyIsOptional) {
391
                $this->methodBody->add('if (null !== %s) {', $this->body);
392
            }
393
394
            if ($this->bodyIsJsonSerializable) {
395
                $this->methodBody->add('$body = json_encode(%s);', $this->body);
396
            } else {
397
                if (!empty($this->serializationContext)) {
398
                    $this->methodBody->add('$context = \JMS\Serializer\SerializationContext::create();');
399
                    $this->createContext($this->serializationContext);
400
                    $this->methodBody->add('$body = $this->serializer->serialize(%s, "json", $context);', $this->body);
401
                } else {
402
                    $this->methodBody->add('$body = $this->serializer->serialize(%s, "json");', $this->body);
403
                }
404
            }
405
406
407
            if (false === $this->jsonEncode) {
408
                $this->methodBody->add('$body = json_decode($body, true);');
409
                $this->methodBody->add('$body = \Tebru\Retrofit\Generation\Manipulator\BodyManipulator::boolToString($body);');
410
                $this->methodBody->add('$body = http_build_query($body);');
411
            }
412
413
            if ($this->bodyIsOptional) {
414
                $this->methodBody->add('} else { $body = %s; }', $this->bodyDefaultValue);
415
            }
416
        } elseif ($this->bodyIsArray) {
417
            (true === $this->jsonEncode)
418
                ? $this->methodBody->add('$body = json_encode(%s);', $this->body)
419
                : $this->methodBody->add('$body = http_build_query(%s);', $this->body);
420
        } elseif (null !== $this->body) {
421
            $this->methodBody->add('$body = %s;', $this->body);
422
        } else {
423
            (true === $this->jsonEncode)
424
                ? $this->methodBody->add('$body = json_encode(%s);', $this->arrayToString($this->bodyParts))
425
                : $this->methodBody->add('$body = http_build_query(%s);', $this->arrayToString($this->bodyParts));
426
        }
427
    }
428
429
    /**
430
     * Build the response
431
     */
432
    private function createResponse()
433
    {
434
        $this->methodBody->add('$request = new \GuzzleHttp\Psr7\Request("%s", $requestUrl, $headers, $body);', strtoupper($this->requestMethod));
435
        $this->methodBody->add('$beforeSendEvent = new \Tebru\Retrofit\Event\BeforeSendEvent($request);');
436
        $this->methodBody->add('$this->eventDispatcher->dispatch("retrofit.beforeSend", $beforeSendEvent);');
437
        $this->methodBody->add('$request = $beforeSendEvent->getRequest();');
438
        $this->methodBody->add('try {');
439
440 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...
441
            $this->methodBody->add('if (%s !== null) {', $this->callback);
442
            $this->methodBody->add('$response = $this->client->sendAsync($request, %s);', $this->callback);
443
            $this->methodBody->add('} else {');
444
            $this->methodBody->add('$response = $this->client->send($request);');
445
            $this->methodBody->add('}');
446
        } elseif ($this->callback !== null && !$this->callbackOptional) {
447
            $this->methodBody->add('$response = $this->client->sendAsync($request, %s);', $this->callback);
448
        } else {
449
            $this->methodBody->add('$response = $this->client->send($request);');
450
        }
451
452
        $this->methodBody->add('} catch (\Exception $exception) {');
453
        $this->methodBody->add('$apiExceptionEvent = new \Tebru\Retrofit\Event\ApiExceptionEvent($exception, $request);');
454
        $this->methodBody->add('$this->eventDispatcher->dispatch("retrofit.apiException", $apiExceptionEvent);');
455
        $this->methodBody->add('$exception = $apiExceptionEvent->getException();');
456
        $this->methodBody->add('throw new \Tebru\Retrofit\Exception\RetrofitApiException(get_class($this), $exception->getMessage(), $exception->getCode(), $exception);');
457
        $this->methodBody->add('}');
458
        $this->methodBody->add('$afterSendEvent = new \Tebru\Retrofit\Event\AfterSendEvent($request, $response);');
459
        $this->methodBody->add('$this->eventDispatcher->dispatch("retrofit.afterSend", $afterSendEvent);');
460
        $this->methodBody->add('$response = $afterSendEvent->getResponse();');
461
    }
462
463
    /**
464
     * Build the return
465
     */
466
    private function createReturns()
467
    {
468 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...
469
            $this->methodBody->add('if (%s !== null) {', $this->callback);
470
            $this->methodBody->add('$returnEvent = new \Tebru\Retrofit\Event\ReturnEvent(null);');
471
            $this->methodBody->add('$this->eventDispatcher->dispatch("retrofit.return", $returnEvent);');
472
            $this->methodBody->add('return $returnEvent->getReturn();');
473
            $this->methodBody->add('}');
474
        } elseif ($this->callback !== null && !$this->callbackOptional) {
475
            $this->methodBody->add('$returnEvent = new \Tebru\Retrofit\Event\ReturnEvent(null);');
476
            $this->methodBody->add('$this->eventDispatcher->dispatch("retrofit.return", $returnEvent);');
477
            $this->methodBody->add('return $returnEvent->getReturn();');
478
479
            return;
480
        }
481
482
        $matches = [];
483
        $returnType = $this->returnType;
484
        $responseReturn = false;
485
        preg_match('/^Response<(.+)>$/', $returnType, $matches);
486
487
        if (isset($matches[1])) {
488
            $returnType = $matches[1];
489
            $responseReturn = true;
490
        }
491
492
        $this->methodBody->add(
493
            '$retrofitResponse = new \Tebru\Retrofit\Http\Response($response, "%s", $this->serializer, %s);',
494
            $returnType,
495
            $this->arrayToString($this->deserializationContext)
496
        );
497
498
        if ($responseReturn) {
499
            $this->methodBody->add('$return = $retrofitResponse;');
500
        } else {
501
            $this->methodBody->add('$return = $retrofitResponse->body();');
502
        }
503
504
        $this->methodBody->add('$returnEvent = new \Tebru\Retrofit\Event\ReturnEvent($return);');
505
        $this->methodBody->add('$this->eventDispatcher->dispatch("retrofit.return", $returnEvent);');
506
        $this->methodBody->add('return $returnEvent->getReturn();');
507
    }
508
509
    /**
510
     * Build the serialization context
511
     *
512
     * @param $context
513
     */
514
    private function createContext(&$context)
515
    {
516
        if (!empty($context['groups'])) {
517
            $this->methodBody->add('$context->setGroups(%s);', $this->arrayToString($context['groups']));
518
        }
519
520
        if (!empty($context['version'])) {
521
            $this->methodBody->add('$context->setVersion(%d);', (int)$context['version']);
522
        }
523
524
        if (!empty($context['serializeNull'])) {
525
            $this->methodBody->add('$context->setSerializeNull(%d);', (bool)$context['serializeNull']);
526
        }
527
528
        if (!empty($context['enableMaxDepthChecks'])) {
529
            $this->methodBody->add('$context->enableMaxDepthChecks();');
530
        }
531
532 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...
533
            foreach ($context['attributes'] as $key => $value) {
534
                $this->methodBody->add('$context->setAttribute("%s", "%s");', $key, $value);
535
            }
536
        }
537
    }
538
539
    /**
540
     * Create a string representation of an array
541
     *
542
     * @param array $array
543
     * @return string
544
     */
545
    private function arrayToString(array $array)
546
    {
547
        $string = var_export($array, true);
548
        $string = preg_replace('/\'\$(.+)\'/', '$' . '\\1', $string);
549
550
        return $string;
551
    }
552
}
553