Completed
Branch 1.x (7efb78)
by Akihito
04:17 queued 02:15
created

OptionsRenderer::unsetAssisted()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 6
cts 6
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 6
nc 2
nop 2
crap 3
1
<?php
2
/**
3
 * This file is part of the BEAR.Resource package.
4
 *
5
 * @license http://opensource.org/licenses/MIT MIT
6
 */
7
namespace BEAR\Resource;
8
9
use BEAR\Resource\Annotation\ResourceParam;
10
use Doctrine\Common\Annotations\Reader;
11
use phpDocumentor\Reflection\DocBlockFactory;
12
use Ray\Di\Di\Assisted;
13
14
/** @noinspection PhpInconsistentReturnPointsInspection */
15
16
/**
17
 * RFC2616 OPTIONS method renderer
18
 *
19
 * Set resource request information to `headers` and `view` in ResourceObject.
20
 *
21
 *
22
 * @link https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
23
 * @see /docs/options/README.md
24
 */
25
final class OptionsRenderer implements RenderInterface
26
{
27
    /**
28
     * @var Reader
29
     */
30
    private $reader;
31
32
    /**
33
     * @param Reader $reader
34
     */
35 84
    public function __construct(Reader $reader)
36
    {
37 84
        $this->reader = $reader;
38 84
    }
39
40
    /**
41
     * {@inheritdoc}
42
     */
43 4
    public function render(ResourceObject $ro)
44
    {
45 4
        $ro->headers['Content-Type'] = 'application/json';
46 4
        $allows = $this->getAllows((new \ReflectionClass($ro))->getMethods());
47 4
        $ro->headers['allow'] = implode(', ', $allows);
48 4
        $body = $this->getEntityBody($ro, $allows);
49 4
        $ro->view = json_encode($body, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL;
50
51 4
        return $ro;
52
    }
53
54
    /**
55
     * Return valid methods
56
     *
57
     * @param \ReflectionMethod[] $methods
58
     *
59
     * @return array
60
     */
61 4
    private function getAllows(array $methods)
62
    {
63 4
        $allows = [];
64 4
        foreach ($methods as $method) {
65 4
            if (in_array($method->name, ['onGet', 'onPost', 'onPut', 'onPatch', 'onDelete', 'onHead'], true)) {
66 4
                $allows[] = strtoupper(substr($method->name, 2));
67
            }
68
        }
69
70 4
        return $allows;
71
    }
72
73
    /**
74
     * @param ResourceObject $ro
75
     * @param array          $allows
76
     *
77
     * @return array
78
     */
79 4
    private function getEntityBody(ResourceObject $ro, $allows)
80
    {
81 4
        $mehtodList = [];
82 4
        foreach ($allows as $method) {
83 4
            $mehtodList[$method] = $this->getMethodParameters($ro, $method);
84
        }
85
86 4
        return $mehtodList;
87
    }
88
89
    /**
90
     * @param ResourceObject $ro
91
     * @param string         $requestMethod
92
     *
93
     * @return array
94
     */
95 4
    private function getMethodParameters(ResourceObject $ro, $requestMethod)
96
    {
97 4
        $method = new \ReflectionMethod($ro, 'on' . $requestMethod);
98 4
        $docComment = $method->getDocComment();
99 4
        $doc = $paramDoc = [];
100 4
        if ($docComment) {
101 4
            list($doc, $paramDoc) = $this->docBlock($docComment);
102
        }
103 4
        $parameters = $method->getParameters();
104 4
        list($paramDoc, $required) = $this->getParameterMetas($parameters, $paramDoc);
105 4
        $paramMetas = [];
106 4
        if ((bool) $paramDoc) {
107 2
            $paramMetas['parameters'] = $paramDoc;
108
        }
109 4
        if ((bool) $required) {
110 4
            $paramMetas['required'] = $required;
111
        }
112 4
        $paramMetas = $this->ignoreAssistedPrameter($method, $paramMetas);
113
114 4
        return $doc + $paramMetas;
115
    }
116
117
    /**
118
     * @param string $docComment
119
     *
120
     * @return array [$docs, $params]
121
     */
122 4
    private function docBlock($docComment)
123
    {
124 4
        $factory = DocBlockFactory::createInstance();
125 4
        $docblock = $factory->create($docComment);
126 4
        $summary = $docblock->getSummary();
127 4
        $docs = $params = [];
128 4
        if ($summary) {
129 1
            $docs['summary'] = $summary;
130
        }
131 4
        $description = (string) $docblock->getDescription();
132 4
        if ($description) {
133 1
            $docs['description'] = $description;
134
        }
135 4
        $tags = $docblock->getTagsByName('param');
136 4
        $params = $this->docBlogTags($tags, $params);
137
138 4
        return [$docs, $params];
139
    }
140
141
    /**
142
     * @param \ReflectionParameter $parameter
143
     * @param array                $paramDoc
144
     * @param string               $name
145
     *
146
     * @return string|null
147
     */
148 4
    private function getParameterType(\ReflectionParameter $parameter, array $paramDoc, $name)
149
    {
150 4
        $hasType = method_exists($parameter, 'getType') && $parameter->getType();
151 4
        if ($hasType) {
152 1
            return $this->getType($parameter);
153
        }
154 4
        if (isset($paramDoc[$name]['type'])) {
155 2
            return $paramDoc[$name]['type'];
156
        }
157 2
    }
158
159
    /**
160
     * @param \ReflectionParameter[] $parameters
161
     * @param array                  $paramDoc
162
     *
163
     * @return array [$paramDoc, $required]
164
     */
165 4
    private function getParameterMetas(array $parameters, array $paramDoc)
166
    {
167 4
        $required = [];
168 4
        foreach ($parameters as $parameter) {
169 4
            list($paramDoc, $required) = $this->getParamMetas($paramDoc, $parameter, $required);
170
        }
171
172 4
        return [$paramDoc, $required];
173
    }
174
175
    /**
176
     * @param array                $paramDoc
177
     * @param \ReflectionParameter $parameter
178
     * @param array                $required
179
     *
180
     * @return array
181
     */
182 4
    private function getParamMetas(array $paramDoc, \ReflectionParameter $parameter, array $required)
183
    {
184 4
        $paramDoc = $this->paramType($paramDoc, $parameter);
185 4
        if (! $parameter->isOptional()) {
186 4
            $required[] = $parameter->name;
187
        }
188 4
        $paramDoc = $this->paramDefault($paramDoc, $parameter);
189
190 4
        return [$paramDoc, $required];
191
    }
192
193
    /**
194
     * @return array
195
     */
196 4
    private function paramDefault(array $paramDoc, \ReflectionParameter $parameter)
197
    {
198 4
        $hasDefault = $parameter->isDefaultValueAvailable() && $parameter->getDefaultValue() !== null;
199 4
        if ($hasDefault) {
200 1
            $paramDoc[$parameter->name]['default'] = (string) $parameter->getDefaultValue();
201
        }
202
203 4
        return $paramDoc;
204
    }
205
206
    /**
207
     * @return array
208
     */
209 4
    private function paramType(array $paramDoc, \ReflectionParameter $parameter)
210
    {
211 4
        $type = $this->getParameterType($parameter, $paramDoc, $parameter->name);
212 4
        if (is_string($type)) {
213 2
            $paramDoc[$parameter->name]['type'] = $type;
214
        }
215
216 4
        return $paramDoc;
217
    }
218
219
    /**
220
     * @param \ReflectionParameter $parameter
221
     *
222
     * @return string
223
     */
224 1
    private function getType(\ReflectionParameter $parameter)
225
    {
226 1
        $type = (string) $parameter->getType();
227 1
        if ($type === 'int') {
228 1
            $type = 'integer';
229
        }
230
231 1
        return $type;
232
    }
233
234
    /**
235
     * @return array
236
     */
237 4
    private function docBlogTags(array $tags, array $params)
238
    {
239 4
        foreach ($tags as $tag) {
240
            /* @var $tag \phpDocumentor\Reflection\DocBlock\Tags\Param */
241 2
            $varName = $tag->getVariableName();
242 2
            $tagType = (string) $tag->getType();
243 2
            $type = $tagType === 'int' ? 'integer' : $tagType;
244 2
            $params[$varName] = [
245 2
                'type' => $type
246
            ];
247 2
            $description = (string) $tag->getDescription();
248 2
            if ($description) {
249 2
                $params[$varName]['description'] = $description;
250
            }
251
        }
252
253 4
        return $params;
254
    }
255
256
    /**
257
     * @return array
258
     */
259 4
    private function ignoreAssistedPrameter(\ReflectionMethod $method, array $paramMetas)
260
    {
261 4
        $annotations = $this->reader->getMethodAnnotations($method);
262 4
        foreach ($annotations as $annotation) {
263 2
            if ($annotation instanceof ResourceParam) {
264 1
                unset($paramMetas['parameters'][$annotation->param]);
265 1
                $paramMetas['required'] = array_values(array_diff($paramMetas['required'], [$annotation->param]));
266
            }
267 2
            $paramMetas = $this->unsetAssisted($paramMetas, $annotation);
268
        }
269
270 4
        return $paramMetas;
271
    }
272
273
    /**
274
     * @param array $paramMetas
275
     * @param mixed $annotation
276
     *
277
     * @return array
278
     */
279 2
    private function unsetAssisted(array $paramMetas, $annotation)
280
    {
281 2
        if ($annotation instanceof Assisted) {
282 1
            $paramMetas['required'] = array_values(array_diff($paramMetas['required'], $annotation->values));
283 1
            foreach ($annotation->values as $varName) {
284 1
                unset($paramMetas['parameters'][$varName]);
285
            }
286
        }
287
288 2
        return $paramMetas;
289
    }
290
}
291