FormattedResponder::priorities()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 8
cts 8
cp 1
rs 9.8666
c 0
b 0
f 0
cc 3
nc 3
nop 0
crap 3
1
<?php
2
3
namespace Equip\Responder;
4
5
use Equip\Adr\PayloadInterface;
6
use Equip\Adr\ResponderInterface;
7
use Equip\Exception\FormatterException;
8
use Equip\Formatter\FormatterInterface;
9
use Equip\Formatter\JsonFormatter;
10
use Equip\Resolver\ResolverTrait;
11
use Equip\Structure\SortedDictionary;
12
use Negotiation\Negotiator;
13
use Psr\Http\Message\ResponseInterface;
14
use Psr\Http\Message\ServerRequestInterface;
15
use Relay\ResolverInterface;
16
17
class FormattedResponder extends SortedDictionary implements ResponderInterface
18
{
19
    use ResolverTrait;
20
21
    /**
22
     * @var Negotiator
23
     */
24
    private $negotiator;
25
26
    /**
27
     * @param Negotiator $negotiator
28
     * @param ResolverInterface $resolver
29
     */
30 10
    public function __construct(
31
        Negotiator $negotiator,
32
        ResolverInterface $resolver,
33
        array $formatters = [
34
            JsonFormatter::class => 1.0,
35
        ]
36
    ) {
37 10
        $this->negotiator = $negotiator;
38 10
        $this->resolver   = $resolver;
39
40 10
        parent::__construct($formatters);
41 10
    }
42
43
    /**
44
     * @inheritDoc
45
     */
46 5
    public function __invoke(
47
        ServerRequestInterface $request,
48
        ResponseInterface $response,
49
        PayloadInterface $payload
50
    ) {
51 5
        if ($this->hasOutput($payload)) {
52 2
            $response = $this->format($request, $response, $payload);
53 2
        }
54
55 5
        return $response;
56
    }
57
58
    /**
59
     * Determine if the payload has usable output
60
     *
61
     * @param PayloadInterface $payload
62
     *
63
     * @return boolean
64
     */
65 5
    protected function hasOutput(PayloadInterface $payload)
66
    {
67 5
        return (bool) $payload->getOutput();
68
    }
69
70
    /**
71
     * Retrieve a map of accepted priorities with the responsible formatter.
72
     *
73
     * @return array
74
     */
75 2
    protected function priorities()
76
    {
77 2
        $priorities = [];
78
79 2
        foreach ($this as $formatter => $quality) {
80 2
            foreach ($formatter::accepts() as $type) {
81 2
                $priorities[$type] = $formatter;
82 2
            }
83 2
        }
84
85 2
        return $priorities;
86
    }
87
88
    /**
89
     * Retrieve the formatter to use for the current request.
90
     *
91
     * Uses content negotiation to find the best available output format for
92
     * the requested content type.
93
     *
94
     * @param ServerRequestInterface $request
95
     *
96
     * @return FormatterInterface
97
     */
98 2
    protected function formatter(ServerRequestInterface $request)
99
    {
100 2
        $accept = $request->getHeaderLine('Accept');
101 2
        $priorities = $this->priorities();
102
103 2
        if (!empty($accept)) {
104 1
            $preferred = $this->negotiator->getBest($accept, array_keys($priorities));
105 1
        }
106
107 2
        if (!empty($preferred)) {
108 1
            $formatter = $priorities[$preferred->getValue()];
109 1
        } else {
110 1
            $formatter = array_shift($priorities);
111
        }
112
113 2
        return $this->resolve($formatter);
114
    }
115
116
    /**
117
     * Update the response by formatting the payload.
118
     *
119
     * @param ServerRequestInterface $request
120
     * @param ResponseInterface $response
121
     * @param PayloadInterface $payload
122
     *
123
     * @return ResponseInterface
124
     */
125 2
    protected function format(
126
        ServerRequestInterface $request,
127
        ResponseInterface $response,
128
        PayloadInterface $payload
129
    ) {
130 2
        $formatter = $this->formatter($request);
131
132 2
        $response = $response->withHeader('Content-Type', $formatter->type());
133
        // Overwrite the body instead of making a copy and dealing with the stream.
134 2
        $response->getBody()->write($formatter->body($payload));
135
136 2
        return $response;
137
    }
138
139
    /**
140
     * @inheritDoc
141
     *
142
     * @throws FormatterException
143
     *  If $classes does not implement the correct interface,
144
     *  or does not have a quality value.
145
     */
146 10
    protected function assertValid(array $classes)
147
    {
148 10
        parent::assertValid($classes);
149
150 10
        foreach ($classes as $formatter => $quality) {
151 10
            if (!is_subclass_of($formatter, FormatterInterface::class)) {
152 1
                throw FormatterException::invalidClass($formatter);
153
            }
154
155 10
            if (!is_float($quality)) {
156 1
                throw FormatterException::needsQuality($formatter);
157
            }
158 10
        }
159 10
    }
160
161
    /**
162
     * @inheritDoc
163
     */
164 3
    protected function sortValues()
165
    {
166 3
        arsort($this->values);
167 3
    }
168
}
169