Completed
Branch develop (972ca0)
by Nate
02:45
created

AnnotationProvider   F

Complexity

Total Complexity 62

Size/Duplication

Total Lines 506
Duplicated Lines 21.94 %

Coupling/Cohesion

Components 1
Dependencies 17

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 62
c 1
b 0
f 0
lcom 1
cbo 17
dl 111
loc 506
rs 3.5483

29 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A getBaseUrl() 0 11 2
A getRequestMethod() 0 4 1
A getRequestUri() 0 4 1
A getQueries() 0 14 4
A getQueryMap() 0 11 2
A getHeaders() 16 16 3
A getStaticHeaders() 0 16 3
A isJsonEncoded() 0 8 2
A isFormUrlEncoded() 0 8 3
A isMultipart() 0 8 2
A getMultipartBoundary() 0 12 2
A hasBody() 0 4 2
A hasBodyAnnotation() 0 4 1
A getBody() 0 8 2
A getBodyParts() 16 16 3
A isBodyObject() 8 8 2
A isBodyArray() 8 8 2
A isBodyOptional() 8 8 2
A isBodyJsonSerializable() 0 13 2
A getSerializationContext() 17 17 2
A getDeserializationContext() 18 18 2
A getReturnType() 0 11 2
A getResponseType() 0 11 2
A getCallback() 10 10 2
A isCallbackOptional() 10 10 2
A getRequestAnnotation() 0 10 2
A getBodyAnnotation() 0 4 1
B getCallbackParameter() 0 24 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like AnnotationProvider often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AnnotationProvider, and based on these observations, apply Extract Interface, too.

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\Provider;
8
9
use LogicException;
10
use OutOfBoundsException;
11
use ReflectionClass;
12
use Tebru\Dynamo\Collection\AnnotationCollection;
13
use Tebru\Dynamo\Model\MethodModel;
14
use Tebru\Dynamo\Model\ParameterModel;
15
use Tebru\Retrofit\Annotation\BaseUrl;
16
use Tebru\Retrofit\Annotation\Body;
17
use Tebru\Retrofit\Annotation\FormUrlEncoded;
18
use Tebru\Retrofit\Annotation\Header;
19
use Tebru\Retrofit\Annotation\Headers;
20
use Tebru\Retrofit\Annotation\HttpRequest;
21
use Tebru\Retrofit\Annotation\JsonBody;
22
use Tebru\Retrofit\Annotation\Multipart;
23
use Tebru\Retrofit\Annotation\Part;
24
use Tebru\Retrofit\Annotation\Query;
25
use Tebru\Retrofit\Annotation\QueryMap;
26
use Tebru\Retrofit\Annotation\ResponseType;
27
use Tebru\Retrofit\Annotation\Returns;
28
use Tebru\Retrofit\Annotation\Serializer\DeserializationContext;
29
use Tebru\Retrofit\Annotation\Serializer\SerializationContext;
30
use Tebru\Retrofit\Exception\RetrofitException;
31
32
/**
33
 * Class AnnotationProvider
34
 *
35
 * @author Nate Brunette <[email protected]>
36
 */
37
class AnnotationProvider
38
{
39
    /**
40
     * @var AnnotationCollection
41
     */
42
    private $annotations;
43
44
    /**
45
     * @var MethodModel
46
     */
47
    private $methodModel;
48
49
    /**
50
     * @var string
51
     */
52
    private $multipartBoundary;
53
54
    /**
55
     * Constructor
56
     *
57
     * @param AnnotationCollection $annotations
58
     * @param MethodModel $methodModel
59
     */
60
    public function __construct(AnnotationCollection $annotations, MethodModel $methodModel)
61
    {
62
        $this->annotations = $annotations;
63
        $this->methodModel = $methodModel;
64
    }
65
66
    /**
67
     * Get base url
68
     *
69
     * @return null|string
70
     */
71
    public function getBaseUrl()
72
    {
73
        if (!$this->annotations->exists(BaseUrl::NAME)) {
74
            return null;
75
        }
76
77
        /** @var BaseUrl $baseUrlAnnotation */
78
        $baseUrlAnnotation = $this->annotations->get(BaseUrl::NAME);
79
80
        return $baseUrlAnnotation->getVariable();
81
    }
82
83
    /**
84
     * Get request method
85
     *
86
     * @return string
87
     * @throws LogicException
88
     */
89
    public function getRequestMethod()
90
    {
91
        return $this->getRequestAnnotation()->getType();
92
    }
93
94
    /**
95
     * Get request uri
96
     *
97
     * @return string
98
     * @throws LogicException
99
     */
100
    public function getRequestUri()
101
    {
102
        return $this->getRequestAnnotation()->getPath();
103
    }
104
105
    /**
106
     * Get request queries
107
     *
108
     * @return null|array
109
     * @throws LogicException
110
     */
111
    public function getQueries()
112
    {
113
        $queries = $this->getRequestAnnotation()->getQueries();
114
115
        if ($this->annotations->exists(Query::NAME)) {
116
            /** @var Query $queryAnnotation */
117
            foreach ($this->annotations->get(Query::NAME) as $queryAnnotation) {
0 ignored issues
show
Bug introduced by
The expression $this->annotations->get(...Annotation\Query::NAME) of type object<Tebru\Dynamo\Annotation\DynamoAnnotation> is not traversable.
Loading history...
118
                $queries[$queryAnnotation->getRequestKey()] = $queryAnnotation->getVariable();
119
            }
120
121
        }
122
123
        return 0 === count($queries) ? null : $queries;
124
    }
125
126
    /**
127
     * Get query map
128
     *
129
     * @return null|string
130
     */
131
    public function getQueryMap()
132
    {
133
        if (!$this->annotations->exists(QueryMap::NAME)) {
134
            return null;
135
        }
136
137
        /** @var QueryMap $queryMapAnnotation */
138
        $queryMapAnnotation = $this->annotations->get(QueryMap::NAME);
139
140
        return $queryMapAnnotation->getVariable();
141
    }
142
143
    /**
144
     * Get header variables
145
     *
146
     * @return array|null
147
     */
148 View Code Duplication
    public function getHeaders()
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...
149
    {
150
        if (!$this->annotations->exists(Header::NAME)) {
151
            return null;
152
        }
153
154
        /** @var Header[] $headerAnnotations */
155
        $headerAnnotations = $this->annotations->get(Header::NAME) ;
156
157
        $headers = [];
158
        foreach ($headerAnnotations as $headerAnnotation) {
159
            $headers[$headerAnnotation->getRequestKey()] = $headerAnnotation->getVariable();
160
        }
161
162
        return $headers;
163
    }
164
165
    /**
166
     * Get headers defined by "@Headers"
167
     *
168
     * @return null|array
169
     */
170
    public function getStaticHeaders()
171
    {
172
        if (!$this->annotations->exists(Headers::NAME)) {
173
            return null;
174
        }
175
176
        /** @var Headers $headersAnnotation */
177
        $headersAnnotation = $this->annotations->get(Headers::NAME);
178
179
        $headers = [];
180
        foreach ($headersAnnotation->getHeaders() as $key => $value) {
181
            $headers[$key] = $value;
182
        }
183
184
        return $headers;
185
    }
186
187
    /**
188
     * If the request is json encoded
189
     *
190
     * @return bool
191
     */
192
    public function isJsonEncoded()
193
    {
194
        if ($this->annotations->exists(JsonBody::NAME)) {
195
            return true;
196
        }
197
198
        return false;
199
    }
200
201
    /**
202
     * If the request is form encoded
203
     *
204
     * @return bool
205
     */
206
    public function isFormUrlEncoded()
207
    {
208
        if ($this->annotations->exists(FormUrlEncoded::NAME)) {
209
            return true;
210
        }
211
212
        return !$this->isMultipart() && !$this->isJsonEncoded();
213
    }
214
215
    /**
216
     * If the request is multipart encoded
217
     *
218
     * @return bool
219
     */
220
    public function isMultipart()
221
    {
222
        if ($this->annotations->exists(Multipart::NAME)) {
223
            return true;
224
        }
225
226
        return false;
227
    }
228
229
    /**
230
     * Get the multipart boundary
231
     *
232
     * @return string
233
     */
234
    public function getMultipartBoundary()
235
    {
236
        /** @var Multipart $multipartAnnotation */
237
        $multipartAnnotation = $this->annotations->get(Multipart::NAME);
238
        $this->multipartBoundary = $multipartAnnotation->getBoundary();
239
240
        if (null === $this->multipartBoundary) {
241
            $this->multipartBoundary = uniqid('', false);
242
        }
243
244
        return $this->multipartBoundary;
245
    }
246
247
    /**
248
     * If there is a request body
249
     *
250
     * @return bool
251
     */
252
    public function hasBody()
253
    {
254
        return $this->annotations->exists(Body::NAME) || $this->annotations->exists(Part::NAME);
255
    }
256
257
    /**
258
     * If there is a body annotation
259
     *
260
     * @return bool
261
     */
262
    public function hasBodyAnnotation()
263
    {
264
        return $this->annotations->exists(Body::NAME);
265
    }
266
267
    /**
268
     * Get body variable
269
     *
270
     * @return null|string
271
     * @throws LogicException
272
     */
273
    public function getBody()
274
    {
275
        if (!$this->annotations->exists(Body::NAME)) {
276
            return null;
277
        }
278
279
        return $this->getBodyAnnotation()->getVariable();
280
    }
281
282
    /**
283
     * Get body parts
284
     *
285
     * @return array|null
286
     */
287 View Code Duplication
    public function getBodyParts()
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...
288
    {
289
        if (!$this->annotations->exists(Part::NAME)) {
290
            return null;
291
        }
292
293
        /** @var Part[] $partAnnotations */
294
        $partAnnotations = $this->annotations->get(Part::NAME);
295
296
        $parts = [];
297
        foreach ($partAnnotations as $partAnnotation) {
298
            $parts[$partAnnotation->getRequestKey()] = $partAnnotation->getVariable();
299
        }
300
301
        return $parts;
302
    }
303
304
    /**
305
     * If the body parameter is an object
306
     *
307
     * @return bool
308
     * @throws LogicException
309
     */
310 View Code Duplication
    public function isBodyObject()
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...
311
    {
312
        if (!$this->annotations->exists(Body::NAME)) {
313
            return false;
314
        }
315
316
        return $this->methodModel->getParameter($this->getBodyAnnotation()->getVariableName())->isObject();
317
    }
318
319
    /**
320
     * If the body parameter is an array
321
     *
322
     * @return bool
323
     * @throws LogicException
324
     */
325 View Code Duplication
    public function isBodyArray()
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...
326
    {
327
        if (!$this->annotations->exists(Body::NAME)) {
328
            return false;
329
        }
330
331
        return $this->methodModel->getParameter($this->getBodyAnnotation()->getVariableName())->isArray();
332
    }
333
334
    /**
335
     * If the body parameter is optional
336
     *
337
     * @return bool
338
     * @throws LogicException
339
     */
340 View Code Duplication
    public function isBodyOptional()
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...
341
    {
342
        if (!$this->annotations->exists(Body::NAME)) {
343
            return false;
344
        }
345
346
        return $this->methodModel->getParameter($this->getBodyAnnotation()->getVariableName())->isOptional();
347
    }
348
349
    /**
350
     * If the body parameter implements \JsonSerializable
351
     *
352
     * @return bool
353
     * @throws LogicException
354
     */
355
    public function isBodyJsonSerializable()
356
    {
357
        if (!$this->isBodyObject()) {
358
            return false;
359
        }
360
361
        $typehint = $this->methodModel->getParameter($this->getBodyAnnotation()->getVariableName())->getTypeHint();
362
363
        $reflectionClass = new ReflectionClass($typehint);
364
        $interfaces = $reflectionClass->getInterfaceNames();
365
366
        return in_array('JsonSerializable', $interfaces, true);
367
    }
368
369
    /**
370
     * Get JMS Serialization context
371
     *
372
     * @return array|null
373
     */
374 View Code Duplication
    public function getSerializationContext()
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...
375
    {
376
        if (!$this->annotations->exists(SerializationContext::NAME)) {
377
            return null;
378
        }
379
380
        /** @var SerializationContext $contextAnnotation */
381
        $contextAnnotation = $this->annotations->get(SerializationContext::NAME);
382
383
        return [
384
            'groups' => $contextAnnotation->getGroups(),
385
            'version' => $contextAnnotation->getVersion(),
386
            'serializeNull' => $contextAnnotation->getSerializeNull(),
387
            'enableMaxDepthChecks' => $contextAnnotation->getEnableMaxDepthChecks(),
388
            'attributes' => $contextAnnotation->getAttributes(),
389
        ];
390
    }
391
392
    /**
393
     * Get JMS Deserialization context
394
     *
395
     * @return array|null
396
     */
397 View Code Duplication
    public function getDeserializationContext()
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...
398
    {
399
        if (!$this->annotations->exists(DeserializationContext::NAME)) {
400
            return null;
401
        }
402
403
        /** @var DeserializationContext $contextAnnotation */
404
        $contextAnnotation = $this->annotations->get(DeserializationContext::NAME);
405
406
        return [
407
            'groups' => $contextAnnotation->getGroups(),
408
            'version' => $contextAnnotation->getVersion(),
409
            'serializeNull' => $contextAnnotation->getSerializeNull(),
410
            'enableMaxDepthChecks' => $contextAnnotation->getEnableMaxDepthChecks(),
411
            'attributes' => $contextAnnotation->getAttributes(),
412
            'depth' => $contextAnnotation->getDepth(),
413
        ];
414
    }
415
416
    /**
417
     * Get the expected return
418
     *
419
     * @return null|string
420
     */
421
    public function getReturnType()
422
    {
423
        if (!$this->annotations->exists(Returns::NAME)) {
424
            return null;
425
        }
426
427
        /** @var Returns $returnAnnotation */
428
        $returnAnnotation = $this->annotations->get(Returns::NAME);
429
430
        return $returnAnnotation->getReturn();
431
    }
432
433
    /**
434
     * Get the return type for a response return
435
     *
436
     * @return null|string
437
     */
438
    public function getResponseType()
439
    {
440
        if (!$this->annotations->exists(ResponseType::NAME)) {
441
            return null;
442
        }
443
444
        /** @var ResponseType $returnAnnotation */
445
        $returnAnnotation = $this->annotations->get(ResponseType::NAME);
446
447
        return $returnAnnotation->getType();
448
    }
449
450
    /**
451
     * Get the callback parameter variable
452
     *
453
     * @return null|string
454
     * @throws RetrofitException
455
     */
456 View Code Duplication
    public function getCallback()
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...
457
    {
458
        $callback = $this->getCallbackParameter();
459
460
        if (null === $callback) {
461
            return null;
462
        }
463
464
        return '$' . $callback->getName();
465
    }
466
467
    /**
468
     * Returns if the callback is optional
469
     *
470
     * @return bool
471
     * @throws LogicException
472
     * @throws RetrofitException
473
     */
474 View Code Duplication
    public function isCallbackOptional()
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...
475
    {
476
        $callback = $this->getCallbackParameter();
477
478
        if (null === $callback) {
479
            throw new LogicException('Callback does not exist');
480
        }
481
482
        return $callback->isOptional();
483
    }
484
485
    /**
486
     * Get the request annotation
487
     *
488
     * @return HttpRequest
489
     * @throws LogicException
490
     */
491
    private function getRequestAnnotation()
492
    {
493
        try {
494
            $requestAnnotation = $this->annotations->get(HttpRequest::NAME);
495
        } catch (OutOfBoundsException $exception) {
496
            throw new LogicException('Request annotation not found (e.g. @GET, @POST)');
497
        }
498
499
        return $requestAnnotation;
500
    }
501
502
    /***
503
     * Get the body annotation
504
     *
505
     * @return Body
506
     */
507
    private function getBodyAnnotation()
508
    {
509
        return $this->annotations->get(Body::NAME);
510
    }
511
512
    /**
513
     * Get the callback method parameter
514
     *
515
     * @return null|ParameterModel
516
     * @throws RetrofitException
517
     */
518
    private function getCallbackParameter()
519
    {
520
        $parameters = array_reverse($this->methodModel->getParameters());
521
        $callback = null;
522
523
        /** @var ParameterModel $parameter */
524
        foreach ($parameters as $parameter) {
525
            if ('\Tebru\Retrofit\Http\Callback' === $parameter->getTypeHint()) {
526
                $callback = $parameter;
527
            }
528
        }
529
530
        if (null === $callback) {
531
            return null;
532
        }
533
534
        $reflectionClass = new ReflectionClass($this->methodModel->getClassModel()->getInterface());
535
536
        if (!in_array('Tebru\Retrofit\Http\AsyncAware', $reflectionClass->getInterfaceNames(), true)) {
537
            throw new RetrofitException('Interfaces using async methods must implement the "AsyncAware" class');
538
        }
539
540
        return $callback;
541
    }
542
}
543