Completed
Branch 1.x (f12d5a)
by Akihito
01:34
created

HalRenderer::hasReturnCreatedResource()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.072

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 4
cts 5
cp 0.8
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 5
nc 3
nop 1
crap 3.072
1
<?php
2
/**
3
 * This file is part of the BEAR.Package package.
4
 *
5
 * @license http://opensource.org/licenses/MIT MIT
6
 */
7
namespace BEAR\Package\Provide\Representation;
8
9
use BEAR\Package\Annotation\Curies;
10
use BEAR\Package\Annotation\ReturnCreatedResource;
11
use BEAR\Package\Exception\LocationHeaderRequestException;
12
use BEAR\Resource\AbstractRequest;
13
use BEAR\Resource\AbstractUri;
14
use BEAR\Resource\Annotation\Link;
15
use BEAR\Resource\RenderInterface;
16
use BEAR\Resource\ResourceInterface;
17
use BEAR\Resource\ResourceObject;
18
use BEAR\Resource\Uri;
19
use BEAR\Sunday\Extension\Router\RouterInterface;
20
use Doctrine\Common\Annotations\Reader;
21
use Nocarrier\Hal;
22
23
/**
24
 * HAL(Hypertext Application Language) renderer
25
 */
26
class HalRenderer implements RenderInterface
27
{
28
    /**
29
     * @var Reader
30
     */
31
    private $reader;
32
33
    /**
34
     * @var RouterInterface
35
     */
36
    private $router;
37
38
    /**
39
     * @var ResourceInterface
40
     */
41
    private $resource;
42
43
    /**
44
     * @var Curies
45
     */
46
    private $curies;
47
48
    /**
49
     * @param Reader          $reader
50
     * @param RouterInterface $router
51
     */
52 4
    public function __construct(Reader $reader, RouterInterface $router, ResourceInterface $resource)
53
    {
54 4
        $this->reader = $reader;
55 4
        $this->router = $router;
56 4
        $this->resource = $resource;
57 4
    }
58
59
    /**
60
     * {@inheritdoc}
61
     */
62 10
    public function render(ResourceObject $ro)
63
    {
64 10
        $method = 'on' . ucfirst($ro->uri->method);
65 10
        if (! method_exists($ro, $method)) {
66 1
            $ro->view = ''; // no view for OPTIONS request
67
68 1
            return '';
69
        }
70 9
        $annotations = $this->reader->getMethodAnnotations(new \ReflectionMethod($ro, $method));
71 9
        $this->curies = $this->reader->getClassAnnotation(new \ReflectionClass($ro), Curies::class);
72 9
        if ($this->isReturnCreatedResource($ro, $annotations)) {
73 1
            return $this->returnCreatedResource($ro);
74
        }
75
76 9
        return $this->renderHal($ro, $annotations);
77
    }
78
79 9
    private function renderHal(ResourceObject $ro, $annotations) : string
80
    {
81 9
        list($ro, $body) = $this->valuate($ro);
82
        /* @var $annotations Link[] */
83
        /* @var $ro ResourceObject */
84 9
        $hal = $this->getHal($ro->uri, $body, $annotations);
85 9
        $ro->view = $hal->asJson(true) . PHP_EOL;
86 9
        $this->updateHeaders($ro);
87
88 9
        return $ro->view;
89
    }
90
91 9
    private function isReturnCreatedResource(ResourceObject $ro, array $annotations) : bool
92
    {
93 9
        return $ro->code === 201 && $ro->uri->method === 'post' && isset($ro->headers['Location']) && $this->hasReturnCreatedResource($annotations);
94
    }
95
96 1
    private function hasReturnCreatedResource(array $annotations) : bool
97
    {
98 1
        foreach ($annotations as $annotation) {
99 1
            if ($annotation instanceof ReturnCreatedResource) {
100 1
                return true;
101
            }
102
        }
103
104
        return false;
105
    }
106
107 1
    private function returnCreatedResource(ResourceObject $ro) : string
108
    {
109 1
        $ro->view = $this->getLocatedView($ro);
110 1
        $this->updateHeaders($ro);
111
112 1
        return $ro->view;
113
    }
114
115 8
    private function valuateElements(ResourceObject &$ro)
116
    {
117 8
        foreach ($ro->body as $key => &$embeded) {
118 8
            if ($embeded instanceof AbstractRequest) {
119 2
                $isDefferentSchema = $this->isDifferentSchema($ro, $embeded->resourceObject);
120 2
                if ($isDefferentSchema === true) {
121 1
                    $ro->body['_embedded'][$key] = $embeded()->body;
122 1
                    unset($ro->body[$key]);
123 1
                    continue;
124
                }
125 1
                unset($ro->body[$key]);
126 1
                $view = $this->render($embeded());
127 8
                $ro->body['_embedded'][$key] = json_decode($view);
128
            }
129
        }
130 8
    }
131
132
    /**
133
     * Return "is different schema" (page <-> app)
134
     */
135 2
    private function isDifferentSchema(ResourceObject $parentRo, ResourceObject $childRo) : bool
136
    {
137 2
        return $parentRo->uri->scheme . $parentRo->uri->host !== $childRo->uri->scheme . $childRo->uri->host;
138
    }
139
140 9
    private function getHal(AbstractUri $uri, array $body, array $annotations) : Hal
141
    {
142 9
        $query = $uri->query ? '?' . http_build_query($uri->query) : '';
143 9
        $path = $uri->path . $query;
144 9
        $selfLink = $this->getReverseMatchedLink($path);
145
146 9
        $hal = new Hal($selfLink, $body);
147 9
        $hal = $this->getHalLink($body, $annotations, $hal);
148
149 9
        return $hal;
150
    }
151
152
    /**
153
     * @return mixed
154
     */
155 9
    private function getReverseMatchedLink(string $uri)
156
    {
157 9
        $urlParts = parse_url($uri);
158 9
        $routeName = $urlParts['path'];
159 9
        isset($urlParts['query']) ? parse_str($urlParts['query'], $value) : $value = [];
160 9
        if ($value === []) {
161 4
            return $uri;
162
        }
163 6
        $reverseUri = $this->router->generate($routeName, (array) $value);
164 6
        if (is_string($reverseUri)) {
165 2
            return $reverseUri;
166
        }
167
168 4
        return $uri;
169
    }
170
171
    /**
172
     * @return array [ResourceObject, array]
173
     */
174 9
    private function valuate(ResourceObject $ro) : array
175
    {
176
        // evaluate all request in body.
177 9
        if (is_array($ro->body)) {
178 8
            $this->valuateElements($ro);
179
        }
180
        // HAL
181 9
        $body = $ro->body ?: [];
182 9
        if (is_scalar($body)) {
183 1
            $body = ['value' => $body];
184
185 1
            return [$ro, $body];
186
        }
187
188 8
        return[$ro, (array) $body];
189
    }
190
191 9
    private function getHalLink(array $body, array $methodAnnotations, Hal $hal) : Hal
192
    {
193 9
        if ($this->curies instanceof Curies) {
194 1
            $hal->addCurie($this->curies->name, $this->curies->href);
195
        }
196 9
        if (! empty($methodAnnotations)) {
197 5
            $hal = $this->linkAnnotation($body, $methodAnnotations, $hal);
198
        }
199 9
        if (isset($body['_links'])) {
200 2
            $hal = $this->bodyLink($body, $hal);
201
        }
202
203 9
        return $hal;
204
    }
205
206 9
    private function updateHeaders(ResourceObject $ro)
207
    {
208 9
        $ro->headers['content-type'] = 'application/hal+json';
209 9
        if (isset($ro->headers['Location'])) {
210 3
            $ro->headers['Location'] = $this->getReverseMatchedLink($ro->headers['Location']);
211
        }
212 9
    }
213
214
    /**
215
     * Return `Location` URI view
216
     */
217 1
    private function getLocatedView(ResourceObject $ro) : string
218
    {
219 1
        $url = parse_url($ro->uri);
220 1
        $locationUri = sprintf('%s://%s%s', $url['scheme'], $url['host'], $ro->headers['Location']);
221
        try {
222 1
            $locatedResource = $this->resource->uri($locationUri)->eager->request();
0 ignored issues
show
Bug introduced by
Accessing eager on the interface BEAR\Resource\RequestInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
223
        } catch (\Exception $e) {
224
            throw new LocationHeaderRequestException($locationUri, 0, $e);
225
        }
226
227 1
        return $locatedResource->toString();
228
    }
229
230 5
    private function linkAnnotation(array $body, array $methodAnnotations, Hal $hal) : Hal
231
    {
232 5
        foreach ($methodAnnotations as $annotation) {
233 5
            if (! $annotation instanceof Link) {
234 2
                continue;
235
            }
236 4
            $uri = uri_template($annotation->href, $body);
237 4
            $reverseUri = $this->getReverseMatchedLink($uri);
238 4
            $hal->addLink($annotation->rel, $reverseUri);
239
        }
240
241 5
        return $hal;
242
    }
243
244 2
    private function bodyLink(array $body, Hal $hal) : Hal
245
    {
246 2
        foreach ($body['_links'] as $rel => $link) {
247 2
            $attr = $link;
248 2
            unset($attr['href']);
249 2
            $hal->addLink($rel, $link['href'], $attr);
250
        }
251
252 2
        return $hal;
253
    }
254
}
255