Skip to content

Commit bbfa5f4

Browse files
authored
Merge pull request #16 from kodedphp/v3
v3.0.0
2 parents d2aa893 + e4dcc76 commit bbfa5f4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+1144
-1237
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ vendor
44
.idea
55
.tmp
66
composer.lock
7+
*.cache

.scrutinizer.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ build:
77
- php-scrutinizer-run
88
environment:
99
php:
10-
version: '7.3'
10+
version: '8.0.1'
1111

1212
before_commands:
1313
- 'composer update -o --prefer-source --no-interaction'

.travis.yml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
language: php
22
os: linux
3-
dist: xenial
3+
dist: bionic
44

55
notifications:
66
email: false
77

88
php:
9-
- 7.2
10-
- 7.3
11-
- 7.4
9+
- 8.0.1
1210
- nightly
1311

1412
cache:
@@ -27,7 +25,7 @@ install:
2725
- composer update -o --prefer-source --no-interaction
2826

2927
script:
30-
- vendor/bin/phpunit --coverage-clover build/clover.xml
28+
- vendor/bin/phpunit --exclude-group internet --coverage-clover build/clover.xml
3129

3230
after_script:
3331
- wget https://scrutinizer-ci.com/ocular.phar

AcceptHeaderNegotiator.php

Lines changed: 61 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -25,56 +25,52 @@
2525
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation
2626
*/
2727

28-
use Generator;
29-
use InvalidArgumentException;
3028
use Koded\Http\Interfaces\HttpStatus;
3129

3230
class AcceptHeaderNegotiator
3331
{
34-
/** @var AcceptHeader[] */
35-
private $supports;
32+
private string $supports = '';
3633

3734
public function __construct(string $supportHeader)
3835
{
3936
$this->supports = $supportHeader;
4037
}
4138

42-
4339
public function match(string $accepts): AcceptHeader
40+
{
41+
return $this->matches($accepts)[0];
42+
}
43+
44+
public function matches(string $accepts): array
4445
{
4546
/** @var AcceptHeader $support */
4647
foreach ($this->parse($accepts) as $accept) {
4748
foreach ($this->parse($this->supports) as $support) {
48-
$support->matches($accept, $types);
49+
$support->matches($accept, $matches);
4950
}
5051
}
51-
52-
usort($types, function(AcceptHeader $a, AcceptHeader $b) {
53-
return $b->weight() <=> $a->weight();
54-
});
55-
56-
if (empty($types)) {
52+
usort($matches, fn(AcceptHeader $a, AcceptHeader $b) => $b->weight() <=> $a->weight());
53+
if (empty($matches)) {
5754
/* Set "q=0", meaning the header is explicitly rejected.
5855
* The consuming clients should handle this according to
5956
* their internal logic. This is much better then throwing
6057
* exceptions which must be handled in every place where
6158
* match() is called. For example, the client may issue a
6259
* 406 status code and be done with it.
6360
*/
64-
$types[] = new class('*;q=0') extends AcceptHeader {};
61+
$matches[] = new class('*;q=0') extends AcceptHeader {};
6562
}
66-
67-
return $types[0];
63+
return $matches;
6864
}
6965

7066
/**
7167
* @param string $header
7268
*
73-
* @return Generator
69+
* @return \Generator
7470
*/
75-
private function parse(string $header): Generator
71+
private function parse(string $header): \Generator
7672
{
77-
foreach (explode(',', $header) as $header) {
73+
foreach (\explode(',', $header) as $header) {
7874
yield new class($header) extends AcceptHeader {};
7975
}
8076
}
@@ -83,177 +79,151 @@ private function parse(string $header): Generator
8379

8480
abstract class AcceptHeader
8581
{
86-
private $header;
87-
private $separator;
88-
private $type;
89-
private $subtype;
90-
private $quality = 1.0;
91-
private $weight = 0.0;
92-
private $catchAll = false;
93-
private $params = [];
82+
private string $header = '';
83+
private string $separator = '/';
84+
private string $type = '';
85+
private string $subtype = '*';
86+
private float $quality = 1.0;
87+
private float $weight = 0.0;
88+
private bool $catchAll = false;
89+
private array $params = [];
9490

9591
public function __construct(string $header)
9692
{
9793
$this->header = $header;
9894

99-
$header = preg_replace('/[[:space:]]/', '', $header);
100-
$bits = explode(';', $header);
101-
$type = array_shift($bits);
102-
103-
if (!empty($type) && !preg_match('~^(\*|[a-z0-9._]+)([/|_-])?(\*|[a-z0-9.\-_+]+)?$~i', $type, $matches)) {
104-
throw new InvalidArgumentException(sprintf('"%s" is not a valid Access header', $header),
95+
$header = \preg_replace('/[[:space:]]/', '', $header);
96+
$bits = \explode(';', $header);
97+
$type = \array_shift($bits);
98+
if (!empty($type) && !\preg_match('~^(\*|[a-z0-9._]+)([/|_\-])?(\*|[a-z0-9.\-_+]+)?$~i', $type, $matches)) {
99+
throw new \InvalidArgumentException(\sprintf('"%s" is not a valid Access header', $header),
105100
HttpStatus::NOT_ACCEPTABLE);
106101
}
107-
108102
$this->separator = $matches[2] ?? '/';
109-
[$type, $subtype] = explode($this->separator, $type, 2) + [1 => '*'];
110-
103+
[$type, $subtype] = \explode($this->separator, $type, 2) + [1 => '*'];
111104
if ('*' === $type && '*' !== $subtype) {
112105
// @see https://tools.ietf.org/html/rfc7231#section-5.3.2
113-
throw new InvalidArgumentException(sprintf('"%s" is not a valid Access header', $header),
106+
throw new \InvalidArgumentException(\sprintf('"%s" is not a valid Access header', $header),
114107
HttpStatus::NOT_ACCEPTABLE);
115108
}
116-
117109
// @see https://tools.ietf.org/html/rfc7540#section-8.1.2
118-
$this->type = strtolower($type);
119-
120-
/* Uses a simple heuristic to check if subtype is part of
121-
* some obscure media type like "vnd.api-v1+json".
110+
$this->type = \trim(\strtolower($type));
111+
/*
112+
* Uses a simple heuristic to check if subtype is part of
113+
* some convoluted media type like "vnd.api-v1+json".
122114
*
123115
* NOTE: It is a waste of time to negotiate on the basis
124116
* of obscure parameters while using a meaningless media
125-
* type like "vnd.whatever". But the web world is a big mess
126-
* and this module can handle the Dunning-Kruger effect.
117+
* type like "vnd.whatever". The web world is a big mess
118+
* but this module can handle the Dunning-Kruger effect.
127119
*/
128-
$this->subtype = explode('+', $subtype)[1] ?? $subtype;
129-
$this->catchAll = '*' === $this->type && '*' === $this->subtype;
130-
131-
parse_str(join('&', $bits), $this->params);
120+
$this->subtype = \trim(\explode('+', $subtype)[1] ?? $subtype);
121+
$this->catchAll = ('*' === $this->type) && ('*' === $this->subtype);
122+
\parse_str(\join('&', $bits), $this->params);
132123
$this->quality = (float)($this->params['q'] ?? 1);
133124
unset($this->params['q']);
134125
}
135126

136-
137127
public function __toString(): string
138128
{
139129
return $this->value();
140130
}
141131

142-
143132
public function value(): string
144133
{
145134
// The header is explicitly rejected
146135
if (0.0 === $this->quality) {
136+
$this->type = $this->subtype = '';
147137
return '';
148138
}
149-
150139
// If language, encoding or charset
151140
if ('*' === $this->subtype) {
152141
return $this->type;
153142
}
154-
155143
return $this->type . $this->separator . $this->subtype;
156144
}
157145

158-
159146
public function quality(): float
160147
{
161148
return $this->quality;
162149
}
163150

164-
165151
public function weight(): float
166152
{
167153
return $this->weight;
168154
}
169155

156+
public function is(string $type): bool
157+
{
158+
return ($type === $this->subtype) && ($this->subtype !== '*');
159+
}
160+
170161
/**
171-
* @internal
172-
*
173162
* @param AcceptHeader $accept The accept header part
174163
* @param AcceptHeader[] $matches Matched types
175164
*
176-
* @return bool TRUE if the accept header part is a match
177-
* against the supported (this) header part
178-
*
179165
* This method finds the best match for the Accept header,
180-
* including all the nonsense that may be passed by the
166+
* including lots of nonsense that may be passed by the
181167
* developers who do not follow RFC standards.
182168
*
169+
* @internal
183170
*/
184-
public function matches(AcceptHeader $accept, array &$matches = null): bool
171+
public function matches(AcceptHeader $accept, array &$matches = null): void
185172
{
186-
$matches = (array)$matches;
187-
$accept = clone $accept;
188-
189-
$typeMatch = $this->type === $accept->type;
190-
173+
$matches = (array)$matches;
174+
$accept = clone $accept;
175+
$typeMatch = ($this->type === $accept->type);
191176
if (1.0 === $accept->quality) {
192177
$accept->quality = (float)$this->quality;
193178
}
194-
195179
if ($accept->catchAll) {
196180
$accept->type = $this->type;
197181
$accept->subtype = $this->subtype;
198182
$matches[] = $accept;
199-
200-
return true;
183+
return;
201184
}
202-
203185
// Explicitly denied
204186
if (0.0 === $this->quality) {
205187
$matches[] = clone $this;
206-
207-
return true;
188+
return;
208189
}
209-
210190
// Explicitly denied
211191
if (0.0 === $accept->quality) {
212192
$matches[] = $accept;
213-
214-
return true;
193+
return;
215194
}
216-
217195
// Explicit type mismatch (w/o asterisk); bail out
218-
if (false === $typeMatch && '*' !== $this->type) {
219-
return false;
196+
if ((false === $typeMatch) && ('*' !== $this->type)) {
197+
return;
220198
}
221-
222199
if ('*' === $accept->subtype) {
223200
$accept->subtype = $this->subtype;
224201
}
225-
226-
if ($accept->subtype !== $this->subtype && '*' !== $this->subtype) {
227-
return false;
202+
if (($accept->subtype !== $this->subtype) && ('*' !== $this->subtype)) {
203+
return;
228204
}
229-
230205
$matches[] = $this->rank($accept);
231-
232-
return true;
233206
}
234207

235208

236209
private function rank(AcceptHeader $accept): AcceptHeader
237210
{
238211
// +100 if types are exact match w/o asterisk
239-
if ($this->type === $accept->type && '*' !== $accept->type) {
212+
if (($this->type === $accept->type) &&
213+
($this->subtype === $accept->subtype)) {
240214
$accept->weight += 100;
241215
}
242-
243-
$accept->weight += $this->catchAll ? 0.0 : $accept->quality;
244-
216+
$accept->weight += ($this->catchAll ? 0.0 : $accept->quality);
245217
// +1 for each parameter that matches, except "q"
246218
foreach ($this->params as $k => $v) {
247-
if (isset($accept->params[$k]) && $accept->params[$k] === $v) {
219+
if (isset($accept->params[$k]) && ($accept->params[$k] === $v)) {
248220
$accept->weight += 1;
249221
} else {
250222
$accept->weight -= 1;
251223
}
252224
}
253-
254225
// Add "q"
255226
$accept->weight += $accept->quality;
256-
257227
return $accept;
258228
}
259229
}

0 commit comments

Comments
 (0)