Completed
Pull Request — master (#72)
by Nate
03:35
created

ServiceMethodFactory::create()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 50
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 50
rs 8.6315
c 0
b 0
f 0
cc 6
eloc 31
nc 8
nop 2
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
    public function __construct(
81
        AnnotationProcessor $annotationProcessor,
82
        CallAdapterProvider $callAdapterProvider,
83
        ConverterProvider $converterProvider,
84
        AnnotationReaderAdapter $annotationReader,
85
        string $baseUrl
86
    ) {
87
        $this->annotationProcessor = $annotationProcessor;
88
        $this->callAdapterProvider = $callAdapterProvider;
89
        $this->converterProvider = $converterProvider;
90
        $this->annotationReader = $annotationReader;
91
        $this->baseUrl = $baseUrl;
92
    }
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
    public function create(string $interfaceName, string $methodName): DefaultServiceMethod
104
    {
105
        $serviceMethodBuilder = new DefaultServiceMethodBuilder();
106
        $annotations = $this->annotationReader->readMethod($methodName, $interfaceName, true, true);
107
108
        $reflectionMethod = new ReflectionMethod($interfaceName, $methodName);
109
        $returnType = $reflectionMethod->getReturnType();
110
        if ($returnType === null) {
111
            throw new LogicException(sprintf(
112
                'Retrofit: All service methods must contain a return type. None found for %s::%s()',
113
                $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
                $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
        $returnTypeToken = new TypeToken($returnType->getName());
120
121
        $serviceMethodBuilder->setBaseUrl($this->baseUrl);
122
        $serviceMethodBuilder->setCallAdapter($this->callAdapterProvider->get($returnTypeToken));
123
124
        foreach ($annotations as $annotationArray) {
125
            if (!is_array($annotationArray)) {
126
                $annotationArray = [$annotationArray];
127
            }
128
129
            foreach ($annotationArray as $annotation) {
130
                try {
131
                    $this->annotationProcessor->process(
132
                        $annotation,
133
                        $serviceMethodBuilder,
134
                        $this->converterProvider,
135
                        $reflectionMethod
136
                    );
137
                } catch (LogicException $exception) {
138
                    throw new LogicException(
139
                        $exception->getMessage() .
140
                        sprintf(' for %s::%s()',
141
                            $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
                            $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
        $this->applyConverters($annotations, $serviceMethodBuilder);
150
151
        return $serviceMethodBuilder->build();
152
    }
153
154
    /**
155
     * @param AnnotationCollection $annotations
156
     * @param DefaultServiceMethodBuilder $builder
157
     * @throws \LogicException
158
     */
159
    private function applyConverters(AnnotationCollection $annotations, DefaultServiceMethodBuilder $builder): void
160
    {
161
        $responseBody = $annotations->get(Annot\ResponseBody::class);
162 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
            $builder->setResponseBodyConverter(
165
                $this->converterProvider->getResponseBodyConverter(new TypeToken($responseBody->getValue()))
166
            );
167
        } else {
168
            /** @noinspection ExceptionsAnnotatingAndHandlingInspection */
169
            $builder->setResponseBodyConverter(
170
                $this->converterProvider->getResponseBodyConverter(new TypeToken(StreamInterface::class))
171
            );
172
        }
173
174
        $errorBody = $annotations->get(Annot\ErrorBody::class);
175 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
            $builder->setErrorBodyConverter(
178
                $this->converterProvider->getResponseBodyConverter(new TypeToken($errorBody->getValue()))
179
            );
180
        } else {
181
            /** @noinspection ExceptionsAnnotatingAndHandlingInspection */
182
            $builder->setErrorBodyConverter(
183
                $this->converterProvider->getResponseBodyConverter(new TypeToken(StreamInterface::class))
184
            );
185
        }
186
    }
187
}
188