Completed
Push — code201 ( 677523...68154a )
by Akihito
08:44 queued 05:59
created

Linker::isList()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

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