Passed
Branch master (203e41)
by Nate
02:00
created

ServiceMethodFactory::create()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 50
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 50
ccs 30
cts 30
cp 1
rs 8.6315
c 0
b 0
f 0
cc 6
eloc 31
nc 8
nop 2
crap 6
1
<?php
2
/*
3
 * Copyright (c) Nate Brunette.
4
 * Distributed under the MIT License (http://opensource.org/licenses/MIT)
5
 */
6
7
declare(strict_types=1);
8
9
namespace Tebru\Retrofit\Internal\ServiceMethod;
10
11
use LogicException;
12
use Psr\Http\Message\StreamInterface;
13
use ReflectionMethod;
14
use Tebru\AnnotationReader\AbstractAnnotation;
15
use Tebru\AnnotationReader\AnnotationCollection;
16
use Tebru\AnnotationReader\AnnotationReaderAdapter;
17
use Tebru\PhpType\TypeToken;
18
use Tebru\Retrofit\Annotation as Annot;
19
use Tebru\Retrofit\CallAdapter;
20
use Tebru\Retrofit\Converter;
21
use Tebru\Retrofit\Internal\AnnotationProcessor;
22
use Tebru\Retrofit\Internal\CallAdapter\CallAdapterProvider;
23
use Tebru\Retrofit\Internal\Converter\ConverterProvider;
24
use Tebru\Retrofit\ServiceMethodBuilder;
25
26
/**
27
 * Class ServiceMethodFactory
28
 *
29
 * Creates and sets up values for [@see ServiceMethodBuilder], then delegates final setup
30
 * to annotation handles.
31
 *
32
 * @author Nate Brunette <[email protected]>
33
 */
34
final class ServiceMethodFactory
35
{
36
    /**
37
     * Handles an [@see AbstractAnnotation]
38
     *
39
     * @var AnnotationProcessor
40
     */
41
    private $annotationProcessor;
42
43
    /**
44
     * Fetches a [@see CallAdapter]
45
     *
46
     * @var CallAdapterProvider
47
     */
48
    private $callAdapterProvider;
49
50
    /**
51
     * Fetches a [@see Converter]
52
     *
53
     * @var ConverterProvider
54
     */
55
    private $converterProvider;
56
57
    /**
58
     * Reads annotations from service interface
59
     *
60
     * @var AnnotationReaderAdapter
61
     */
62
    private $annotationReader;
63
64
    /**
65
     * The request base url
66
     *
67
     * @var string
68
     */
69
    private $baseUrl;
70
71
    /**
72
     * Constructor
73
     *
74
     * @param AnnotationProcessor $annotationProcessor
75
     * @param CallAdapterProvider $callAdapterProvider
76
     * @param ConverterProvider $converterProvider
77
     * @param AnnotationReaderAdapter $annotationReader
78
     * @param string $baseUrl
79
     */
80 26
    public function __construct(
81
        AnnotationProcessor $annotationProcessor,
82
        CallAdapterProvider $callAdapterProvider,
83
        ConverterProvider $converterProvider,
84
        AnnotationReaderAdapter $annotationReader,
85
        string $baseUrl
86
    ) {
87 26
        $this->annotationProcessor = $annotationProcessor;
88 26
        $this->callAdapterProvider = $callAdapterProvider;
89 26
        $this->converterProvider = $converterProvider;
90 26
        $this->annotationReader = $annotationReader;
91 26
        $this->baseUrl = $baseUrl;
92 26
    }
93
94
    /**
95
     * Creates a [@see DefaultServiceMethod]
96
     *
97
     * @param string $interfaceName
98
     * @param string $methodName
99
     * @return DefaultServiceMethod
100
     * @throws \LogicException
101
     * @throws \ReflectionException
102
     */
103 16
    public function create(string $interfaceName, string $methodName): DefaultServiceMethod
104
    {
105 16
        $serviceMethodBuilder = new DefaultServiceMethodBuilder();
106 16
        $annotations = $this->annotationReader->readMethod($methodName, $interfaceName, true, true);
107
108 15
        $reflectionMethod = new ReflectionMethod($interfaceName, $methodName);
109 15
        $returnType = $reflectionMethod->getReturnType();
110 15
        if ($returnType === null) {
111 1
            throw new LogicException(sprintf(
112 1
                'Retrofit: All service methods must contain a return type. None found for %s::%s()',
113 1
                $reflectionMethod->getDeclaringClass()->getName(),
0 ignored issues
show
introduced by
Consider using $reflectionMethod->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
114 1
                $reflectionMethod->getName()
0 ignored issues
show
Bug introduced by
Consider using $reflectionMethod->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
115
            ));
116
        }
117
118
        /** @noinspection ExceptionsAnnotatingAndHandlingInspection */
119 14
        $returnTypeToken = new TypeToken($returnType->getName());
120
121 14
        $serviceMethodBuilder->setBaseUrl($this->baseUrl);
122 14
        $serviceMethodBuilder->setCallAdapter($this->callAdapterProvider->get($returnTypeToken));
123
124 14
        foreach ($annotations as $annotationArray) {
125 14
            if (!is_array($annotationArray)) {
126 14
                $annotationArray = [$annotationArray];
127
            }
128
129 14
            foreach ($annotationArray as $annotation) {
130
                try {
131 14
                    $this->annotationProcessor->process(
132 14
                        $annotation,
133 14
                        $serviceMethodBuilder,
134 14
                        $this->converterProvider,
135 14
                        $reflectionMethod
136
                    );
137 1
                } catch (LogicException $exception) {
138 1
                    throw new LogicException(
139 1
                        $exception->getMessage() .
140 1
                        sprintf(' for %s::%s()',
141 1
                            $reflectionMethod->getDeclaringClass()->getName(),
0 ignored issues
show
introduced by
Consider using $reflectionMethod->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
142 14
                            $reflectionMethod->getName()
0 ignored issues
show
Bug introduced by
Consider using $reflectionMethod->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
143
                        )
144
                    );
145
                }
146
            }
147
        }
148
149 13
        $this->applyConverters($annotations, $serviceMethodBuilder);
150
151 13
        return $serviceMethodBuilder->build();
152
    }
153
154
    /**
155
     * @param AnnotationCollection $annotations
156
     * @param DefaultServiceMethodBuilder $builder
157
     * @throws \LogicException
158
     */
159 13
    private function applyConverters(AnnotationCollection $annotations, DefaultServiceMethodBuilder $builder): void
160
    {
161 13
        $responseBody = $annotations->get(Annot\ResponseBody::class);
162 13 View Code Duplication
        if ($responseBody !== null) {
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...
163
            /** @noinspection ExceptionsAnnotatingAndHandlingInspection */
164 2
            $builder->setResponseBodyConverter(
165 2
                $this->converterProvider->getResponseBodyConverter(new TypeToken($responseBody->getValue()))
166
            );
167
        } else {
168
            /** @noinspection ExceptionsAnnotatingAndHandlingInspection */
169 11
            $builder->setResponseBodyConverter(
170 11
                $this->converterProvider->getResponseBodyConverter(new TypeToken(StreamInterface::class))
171
            );
172
        }
173
174 13
        $errorBody = $annotations->get(Annot\ErrorBody::class);
175 13 View Code Duplication
        if ($errorBody !== null) {
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...
176
            /** @noinspection ExceptionsAnnotatingAndHandlingInspection */
177 1
            $builder->setErrorBodyConverter(
178 1
                $this->converterProvider->getResponseBodyConverter(new TypeToken($errorBody->getValue()))
179
            );
180
        } else {
181
            /** @noinspection ExceptionsAnnotatingAndHandlingInspection */
182 12
            $builder->setErrorBodyConverter(
183 12
                $this->converterProvider->getResponseBodyConverter(new TypeToken(StreamInterface::class))
184
            );
185
        }
186 13
    }
187
}
188