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

AnnotationProcessor::process()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 24
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 8.9713
c 0
b 0
f 0
cc 3
eloc 17
nc 3
nop 4
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;
10
11
use LogicException;
12
use ReflectionMethod;
13
use Tebru\AnnotationReader\AbstractAnnotation;
14
use Tebru\PhpType\TypeToken;
15
use Tebru\Retrofit\AnnotationHandler;
16
use Tebru\Retrofit\Converter;
17
use Tebru\Retrofit\Internal\Converter\ConverterProvider;
18
use Tebru\Retrofit\Annotation\ParameterAwareAnnotation;
19
use Tebru\Retrofit\RequestBodyConverter;
20
use Tebru\Retrofit\ServiceMethodBuilder;
21
use Tebru\Retrofit\StringConverter;
22
23
/**
24
 * Class AnnotationProcessor
25
 *
26
 * Given an array of handlers, process an [@see AbstractAnnotation]
27
 *
28
 * @author Nate Brunette <[email protected]>
29
 */
30
final class AnnotationProcessor
31
{
32
    /**
33
     * An array of annotation handlers
34
     *
35
     * @var AnnotationHandler[]
36
     */
37
    private $handlers;
38
39
    /**
40
     * Constructor
41
     *
42
     * @param AnnotationHandler[] $handlers
43
     */
44
    public function __construct(array $handlers)
45
    {
46
        $this->handlers = $handlers;
47
    }
48
49
    /**
50
     * Accepts an annotation and delegates to an [@see AnnotationHandler]
51
     *
52
     * @param AbstractAnnotation $annotation
53
     * @param ServiceMethodBuilder $serviceMethodBuilder
54
     * @param ConverterProvider $converterProvider
55
     * @param ReflectionMethod $reflectionMethod
56
     * @throws \LogicException
57
     */
58
    public function process(
59
        AbstractAnnotation $annotation,
60
        ServiceMethodBuilder $serviceMethodBuilder,
61
        ConverterProvider $converterProvider,
62
        ReflectionMethod $reflectionMethod
63
    ): void {
64
        $name = $annotation->getName();
65
66
        if (!isset($this->handlers[$name])) {
67
            return;
68
        }
69
70
        $handler = $this->handlers[$name];
71
        $converter = null;
72
        $index = null;
73
74
        if ($annotation instanceof ParameterAwareAnnotation) {
75
            $index = $this->findMethodParameterIndex($reflectionMethod, $annotation->getVariableName());
76
            $type = $this->getParameterType($reflectionMethod, $index);
77
            $converter = $this->getConverter($annotation, $converterProvider, $type);
78
        }
79
80
        $handler->handle($annotation, $serviceMethodBuilder, $converter, $index);
81
    }
82
83
    /**
84
     * Find the position of the method parameter
85
     *
86
     * @param ReflectionMethod $reflectionMethod
87
     * @param string $name
88
     * @return int
89
     * @throws \LogicException
90
     */
91
    private function findMethodParameterIndex(ReflectionMethod $reflectionMethod, string $name): int
92
    {
93
        $reflectionParameters = $reflectionMethod->getParameters();
94
        foreach ($reflectionParameters as $index => $reflectionParameter) {
95
            if ($reflectionParameter->getName() === $name) {
0 ignored issues
show
Bug introduced by
Consider using $reflectionParameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
96
                return $index;
97
            }
98
        }
99
100
        throw new LogicException(sprintf(
101
            'Retrofit: Could not find parameter named %s in %s::%s. Please double check that annotations are properly ' .
102
            'referencing method parameters.',
103
            $name,
104
            $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...
105
            $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...
106
        ));
107
    }
108
109
    /**
110
     * Get the parameter type
111
     *
112
     * @param ReflectionMethod $reflectionMethod
113
     * @param int $index
114
     * @return TypeToken
115
     * @throws \LogicException
116
     */
117
    private function getParameterType(ReflectionMethod $reflectionMethod, int $index): TypeToken
118
    {
119
        $reflectionParameter = $reflectionMethod->getParameters()[$index];
120
        $reflectionType = $reflectionParameter->getType();
121
122
        if ($reflectionType === null) {
123
            throw new LogicException(sprintf(
124
                'Retrofit: Parameter type was not found for method %s::%s',
125
                $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...
126
                $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...
127
            ));
128
        }
129
130
        /** @noinspection ExceptionsAnnotatingAndHandlingInspection */
131
        return new TypeToken($reflectionType->getName());
132
    }
133
134
    /**
135
     * Get the converter from annotation converter class
136
     *
137
     * @param ParameterAwareAnnotation $annotation
138
     * @param ConverterProvider $converterProvider
139
     * @param TypeToken $type
140
     * @return Converter
141
     * @throws \LogicException
142
     */
143
    private function getConverter(
144
        ParameterAwareAnnotation $annotation,
145
        ConverterProvider $converterProvider,
146
        TypeToken $type
147
    ): Converter {
148
        switch ($annotation->converterType()) {
149
            case RequestBodyConverter::class:
150
                return $converterProvider->getRequestBodyConverter($type);
151
            case StringConverter::class:
152
                return $converterProvider->getStringConverter($type);
153
        }
154
155
        throw new LogicException(sprintf(
156
            'Retrofit: Unable to handle converter of type %s. Please use RequestBodyConverter or StringConverter',
157
            $annotation->converterType()
158
        ));
159
    }
160
}
161