Completed
Push — 1.x ( b209ea...8cf922 )
by Akihito
14s queued 11s
created

HalRenderer::valuateElements()   A

Complexity

Conditions 6
Paths 10

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6.0106

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 6
eloc 15
c 6
b 0
f 0
nc 10
nop 1
dl 0
loc 27
ccs 14
cts 15
cp 0.9333
crap 6.0106
rs 9.2222
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Resource;
6
7
use Doctrine\Common\Annotations\Reader;
8
use Nocarrier\Hal;
9
use ReflectionMethod;
10
use RuntimeException;
11
12
use function assert;
13
use function http_build_query;
14
use function is_array;
15
use function is_object;
16
use function is_scalar;
17
use function is_string;
18 1
use function json_decode;
19
use function method_exists;
20 1
use function ucfirst;
21 1
22
use const PHP_EOL;
23
24
final class HalRenderer implements RenderInterface
25
{
26
    /** @var Reader */
27
    private $reader;
28 1
29
    /** @var HalLink */
30 1
    private $link;
31
32 1
    public function __construct(Reader $reader, HalLink $link)
33 1
    {
34 1
        $this->reader = $reader;
35
        $this->link = $link;
36 1
    }
37 1
38 1
    /**
39
     * {@inheritdoc}
40 1
     */
41
    public function render(ResourceObject $ro)
42
    {
43 1
        $this->renderHal($ro);
44
        $this->updateHeaders($ro);
45 1
46 1
        return (string) $ro->view;
47
    }
48
49
    /**
50
     * {@inheritdoc}
51
     *
52
     * @throws RuntimeException
53
     */
54
    public function renderHal(ResourceObject $ro): void
55
    {
56 1
        [$ro, $body] = $this->valuate($ro);
57
        $method = 'on' . ucfirst($ro->uri->method);
58
        $hasMethod = method_exists($ro, $method);
59 1
        /** @var list<object> $annotations */
60
        $annotations = $hasMethod ? $this->reader->getMethodAnnotations(new ReflectionMethod($ro, $method)) : [];
61
        $hal = $this->getHal($ro->uri, $body, $annotations);
0 ignored issues
show
Bug introduced by
$annotations of type BEAR\Resource\list is incompatible with the type array expected by parameter $annotations of BEAR\Resource\HalRenderer::getHal(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

61
        $hal = $this->getHal($ro->uri, $body, /** @scrutinizer ignore-type */ $annotations);
Loading history...
62
        $json = $hal->asJson(true);
63
        assert(is_string($json));
64
        $ro->view = $json . PHP_EOL;
65
        $ro->headers['Content-Type'] = 'application/hal+json';
66
    }
67
68
    private function valuateElements(ResourceObject $ro): void
69 1
    {
70
        assert(is_array($ro->body));
71 1
        /** @var mixed $embeded */
72
        foreach ($ro->body as $key => &$embeded) {
73
            if (! ($embeded instanceof AbstractRequest)) {
74 1
                continue;
75
            }
76 1
77 1
            $isNotArray = ! isset($ro->body['_embedded']) || ! is_array($ro->body['_embedded']);
78 1
            if ($isNotArray) {
79 1
                $ro->body['_embedded'] = [];
80 1
            }
81
82 1
            assert(is_array($ro->body['_embedded']));
83
            // @codeCoverageIgnoreStart
84
            if ($this->isDifferentSchema($ro, $embeded->resourceObject)) {
85 1
                $ro->body['_embedded'][$key] = $embeded()->body;
86
                unset($ro->body[$key]);
87
88 1
                continue;
89 1
            }
90
91
            // @codeCoverageIgnoreEnd
92 1
            unset($ro->body[$key]);
93 1
            $view = $this->render($embeded());
94
            $ro->body['_embedded'][$key] = json_decode($view);
95
        }
96
    }
97
98
    /**
99 1
     * @codeCoverageIgnore
100
     */
101
    private function isDifferentSchema(ResourceObject $parentRo, ResourceObject $childRo): bool
102 1
    {
103
        return $parentRo->uri->scheme . $parentRo->uri->host !== $childRo->uri->scheme . $childRo->uri->host;
104 1
    }
105 1
106 1
    /**
107 1
     * @param array<array-key, mixed> $body
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>.
Loading history...
108 1
     * @psalm-param list<object>       $annotations
109
     * @phpstan-param array<object>    $annotations
110
     */
111 1
    private function getHal(AbstractUri $uri, array $body, array $annotations): Hal
112
    {
113
        $query = $uri->query ? '?' . http_build_query($uri->query) : '';
114
        $path = $uri->path . $query;
115
        $selfLink = $this->link->getReverseLink($path);
116
        $hal = new Hal($selfLink, $body);
117
118
        return $this->link->addHalLink($body, $annotations, $hal);
119
    }
120
121
    /**
122
     * @return array{0: ResourceObject, 1: array<array-key, mixed>}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{0: ResourceObject,...rray<array-key, mixed>} at position 10 could not be parsed: Unknown type name 'array-key' at position 10 in array{0: ResourceObject, 1: array<array-key, mixed>}.
Loading history...
123
     */
124
    private function valuate(ResourceObject $ro): array
125
    {
126
        if (is_scalar($ro->body)) {
127
            $ro->body = ['value' => $ro->body];
128
        }
129
130
        if ($ro->body === null) {
131
            $ro->body = [];
132
        }
133
134
        if (is_object($ro->body)) {
135
            $ro->body = (array) $ro->body;
136
        }
137
138
        // evaluate all request in body.
139
        $this->valuateElements($ro);
140
        assert(is_array($ro->body));
141
142
        return [$ro, $ro->body];
143
    }
144
145
    private function updateHeaders(ResourceObject $ro): void
146
    {
147
        $ro->headers['Content-Type'] = 'application/hal+json';
148
        if (! isset($ro->headers['Location'])) {
149
            return;
150
        }
151
152
        $ro->headers['Location'] = $this->link->getReverseLink($ro->headers['Location']);
153
    }
154
}
155