Completed
Pull Request — 1.x (#118)
by HAYASHI
03:07
created

Linker::isList()   B

Complexity

Conditions 6
Paths 24

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 8
cts 8
cp 1
rs 8.8571
c 0
b 0
f 0
cc 6
eloc 8
nc 24
nop 1
crap 6
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\Link;
10
use BEAR\Resource\Exception\LinkQueryException;
11
use BEAR\Resource\Exception\LinkRelException;
12
use Doctrine\Common\Annotations\Reader;
13
14
/**
15
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
16
 */
17
final class Linker implements LinkerInterface
18
{
19
    /**
20
     * @var Reader
21
     */
22
    private $reader;
23
24
    /**
25
     * @var InvokerInterface
26
     */
27
    private $invoker;
28
29
    /**
30
     * @var FactoryInterface
31
     */
32
    private $factory;
33
34
    /**
35
     * memory cache for linker
36
     *
37
     * @var array
38
     */
39
    private $cache = [];
40
41 47
    public function __construct(
42
        Reader $reader,
43
        InvokerInterface $invoker,
44
        FactoryInterface $factory
45
    ) {
46 47
        $this->reader = $reader;
47 47
        $this->invoker = $invoker;
48 47
        $this->factory = $factory;
49 47
    }
50
51
    /**
52
     * {@inheritdoc}
53
     *
54
     * @throws LinkQueryException
55
     * @throws \BEAR\Resource\Exception\LinkRelException
56
     */
57 6
    public function invoke(AbstractRequest $request)
58
    {
59 6
        $this->invoker->invoke($request);
60 6
        $current = clone $request->resourceObject;
61 6
        foreach ($request->links as $link) {
62
            /* @noinspection ExceptionsAnnotatingAndHandlingInspection */
63 6
            $nextResource = $this->annotationLink($link, $current, $request);
64 4
            $current = $this->nextLink($link, $current, $nextResource);
65
        }
66
67 4
        return $current;
68
    }
69
70
    /**
71
     * How next linked resource treated (add ? replace ?)
72
     *
73
     * @param LinkType       $link
74
     * @param ResourceObject $ro
75
     * @param mixed          $nextResource
76
     *
77
     * @return ResourceObject
78
     */
79 4
    private function nextLink(LinkType $link, ResourceObject $ro, $nextResource)
80
    {
81 4
        $nextBody = $nextResource instanceof ResourceObject ? $nextResource->body : $nextResource;
82
83 4
        if ($link->type === LinkType::SELF_LINK) {
84 1
            $ro->body = $nextBody;
85
86 1
            return $ro;
87
        }
88
89 3
        if ($link->type === LinkType::NEW_LINK) {
90 1
            $ro->body[$link->key] = $nextBody;
91
92 1
            return $ro;
93
        }
94
95
        // crawl
96 2
        return $ro;
97
    }
98
99
    /**
100
     * Annotation link
101
     *
102
     * @param LinkType        $link
103
     * @param ResourceObject  $current
104
     * @param AbstractRequest $request
105
     *
106
     * @throws \BEAR\Resource\Exception\MethodException
107
     * @throws \BEAR\Resource\Exception\LinkRelException
108
     * @throws Exception\LinkQueryException
109
     *
110
     * @return ResourceObject|mixed
111
     */
112 6
    private function annotationLink(LinkType $link, ResourceObject $current, AbstractRequest $request)
113
    {
114 6
        if (! is_array($current->body)) {
115 1
            throw new Exception\LinkQueryException('Only array is allowed for link in ' . get_class($current), 500);
116
        }
117 5
        $classMethod = 'on' . ucfirst($request->method);
118 5
        $annotations = $this->reader->getMethodAnnotations(new \ReflectionMethod($current, $classMethod));
119 5
        if ($link->type === LinkType::CRAWL_LINK) {
120 2
            return $this->annotationCrawl($annotations, $link, $current);
121
        }
122
123
        /* @noinspection ExceptionsAnnotatingAndHandlingInspection */
124 3
        return $this->annotationRel($annotations, $link, $current)->body;
125
    }
126
127
    /**
128
     * Annotation link (new, self)
129
     *
130
     * @param \BEAR\Resource\Annotation\Link[] $annotations
131
     * @param LinkType                         $link
132
     * @param ResourceObject                   $current
133
     *
134
     * @throws \BEAR\Resource\Exception\UriException
135
     * @throws \BEAR\Resource\Exception\MethodException
136
     * @throws Exception\LinkQueryException
137
     * @throws Exception\LinkRelException
138
     *
139
     * @return ResourceObject
140
     */
141 3
    private function annotationRel(array $annotations, LinkType $link, ResourceObject $current)
142
    {
143
        /* @noinspection LoopWhichDoesNotLoopInspection */
144 3
        foreach ($annotations as $annotation) {
145 3
            if ($annotation->rel !== $link->key) {
146 1
                continue;
147
            }
148 2
            $uri = uri_template($annotation->href, $current->body);
149 2
            $rel = $this->factory->newInstance($uri);
150
            /* @noinspection UnnecessaryParenthesesInspection */
151 2
            $request = new Request($this->invoker, $rel, Request::GET, (new Uri($uri))->query);
152 2
            $linkedResource = $this->invoker->invoke($request);
153
154 2
            return $linkedResource;
155
        }
156 1
        throw new LinkRelException("rel:{$link->key} class:" . get_class($current), 500);
157
    }
158
159
    /**
160
     * Link annotation crawl
161
     *
162
     * @param array          $annotations
163
     * @param LinkType       $link
164
     * @param ResourceObject $current
165
     *
166
     * @throws \BEAR\Resource\Exception\MethodException
167
     *
168
     * @return ResourceObject
169
     */
170 2
    private function annotationCrawl(array $annotations, LinkType $link, ResourceObject $current)
171
    {
172 2
        $isList = $this->isList($current->body);
173 2
        $bodyList = $isList ? $current->body : [$current->body];
174
        /** @var $bodyList array */
175 2
        foreach ($bodyList as &$body) {
176
            /* @noinspection ExceptionsAnnotatingAndHandlingInspection */
177 2
            $this->crawl($annotations, $link, $body);
178
        }
179 2
        unset($body);
180 2
        $current->body = $isList ? $bodyList : $bodyList[0];
181
182 2
        return $current;
183
    }
184
185
    /**
186
     * @param array    $annotations
187
     * @param LinkType $link
188
     * @param array    $body
189
     *
190
     * @throws \BEAR\Resource\Exception\LinkQueryException
191
     * @throws \BEAR\Resource\Exception\MethodException
192
     * @throws \BEAR\Resource\Exception\LinkRelException
193
     * @throws \BEAR\Resource\Exception\UriException
194
     */
195 2
    private function crawl(array $annotations, LinkType $link, array &$body)
196
    {
197 2
        foreach ($annotations as $annotation) {
198
            /* @var $annotation Link */
199 2
            if ($annotation->crawl !== $link->key) {
200 2
                continue;
201
            }
202 2
            $uri = uri_template($annotation->href, (array) $body);
203 2
            $rel = $this->factory->newInstance($uri);
204
            /* @noinspection UnnecessaryParenthesesInspection */
205 2
            $request = new Request($this->invoker, $rel, Request::GET, (new Uri($uri))->query, [$link], $this);
206 2
            $hash = $request->hash();
207 2
            if (array_key_exists($hash, $this->cache)) {
208 2
                $body[$annotation->rel] = $this->cache[$hash];
209
210 2
                continue;
211
            }
212 2
            $this->cache[$hash] = $body[$annotation->rel] = $this->invoke($request)->body;
213
        }
214 2
    }
215
216
    /**
217
     * @param mixed $value
218
     *
219
     * @return bool
220
     */
221 2
    private function isList($value)
222
    {
223 2
        $list = $value;
224 2
        $firstRow = array_pop($list);
225 2
        $keys = array_keys((array) $firstRow);
226 2
        $isMultiColumnMultiRowList = $keys !== [0 => 0] && ($keys === array_keys((array) array_pop($list)));
227 2
        $isMultiColumnList = is_array($firstRow) && array_filter(array_keys($value), 'is_numeric') === array_keys($value);
228 2
        $isSingleColumnList = (count($value) === 1) && $keys === array_keys((array) $list);
229
230 2
        return $isSingleColumnList || $isMultiColumnMultiRowList || $isMultiColumnList;
231
    }
232
}
233