8000 [http-foudation] Better accept header parsing · s7ntech/symfony@6b601bd · GitHub
[go: up one dir, main page]

Skip to content

Commit 6b601bd

Browse files
jfsimonfabpot
authored andcommitted
[http-foudation] Better accept header parsing
1 parent d8f6021 commit 6b601bd

File tree

8 files changed

+650
-36
lines changed

8 files changed

+650
-36
lines changed

UPGRADE-2.2.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
UPGRADE FROM 2.1 to 2.2
2+
=======================
3+
4+
#### Deprecations
5+
6+
* The `Request::splitHttpAcceptHeader()` is deprecated and will be removed in 2.3.
7+
8+
You should now use the `AcceptHeader` class which give you fluent methods to
9+
parse request accept-* headers. Some examples:
10+
11+
```
12+
$accept = AcceptHeader::fromString($request->headers->get('Accept'));
13+
if ($accept->has('text/html') {
14+
$item = $accept->get('html');
15+
$charset = $item->getAttribute('charset', 'utf-8');
16+
$quality = $item->getQuality();
17+
}
18+
19+
// accepts items are sorted by descending quality
20+
$accepts = AcceptHeader::fromString($request->headers->get('Accept'))->all();
21+
22+
```
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpFoundation;
13+
14+
/**
15+
* Represents an Accept-* header.
16+
*
17+
* An accept header is compound with a list of items,
18+
* sorted by descending quality.
19+
*
20+
* @author Jean-François Simon <contact@jfsimon.fr>
21+
*/
22+
class AcceptHeader
23+
{
24+
/**
25+
* @var AcceptHeaderItem[]
26+
*/
27+
private $items = array();
28+
29+
/**
30+
* @var bool
31+
*/
32+
private $sorted = true;
33+
34+
/**
35+
* Constructor.
36+
*
37+
* @param AcceptHeaderItem[] $items
38+
*/
39+
public function __construct(array $items)
40+
{
41+
foreach ($items as $item) {
42+
$this->add($item);
43+
}
44+
}
45+
46+
/**
47+
* Builds an AcceptHeader instance from a string.
48+
*
49+
* @param string $headerValue
50+
*
51+
* @return AcceptHeader
52+
*/
53+
public static function fromString($headerValue)
54+
{
55+
$index = 0;
56+
57+
return new self(array_map(function ($itemValue) use (&$index) {
58+
$item = AcceptHeaderItem::fromString($itemValue);
59+
$item->setIndex($index++);
60+
61+
return $item;
62+
}, preg_split('/\s*(?:,*("[^"]+"),*|,*(\'[^\']+\'),*|,+)\s*/', $headerValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE)));
63+
}
64+
65+
/**
66+
* Returns header value's string representation.
67+
*
68+
* @return string
69+
*/
70+
public function __toString()
71+
{
72+
return implode(',', $this->items);
73+
}
74+
75+
/**
76+
* Tests if header has given value.
77+
*
78+
* @param string $value
79+
*
80+
* @return Boolean
81+
*/
82+
public function has($value)
83+
{
84+
return isset($this->items[$value]);
85+
}
86+
87+
/**
88+
* Returns given value's item, if exists.
89+
*
90+
* @param string $value
91+
*
92+
* @return AcceptHeaderItem|null
93+
*/
94+
public function get($value)
95+
{
96+
return isset($this->items[$value]) ? $this->items[$value] : null;
97+
}
98+
99+
/**
100+
* Adds an item.
101+
*
102+
* @param AcceptHeaderItem $item
103+
*
104+
* @return AcceptHeader
105+
*/
106+
public function add(AcceptHeaderItem $item)
107+
{
108+
$this->items[$item->getValue()] = $item;
109+
$this->sorted = false;
110+
111+
return $this;
112+
}
113+
114+
/**
115+
* Returns all items.
116+
*
117+
* @return AcceptHeaderItem[]
118+
*/
119+
public function all()
120+
{
121+
$this->sort();
122+
123+
return $this->items;
124+
}
125+
126+
/**
127+
* Filters items on their value using given regex.
128+
*
129+
* @param string $pattern
130+
*
131+
* @return AcceptHeader
132+
*/
133+
public function filter($pattern)
134+
{
135+
return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) {
136+
return preg_match($pattern, $item->getValue());
137+
}));
138+
}
139+
140+
/**
141+
* Returns first item.
142+
*
143+
* @return AcceptHeaderItem|null
144+
*/
145+
public function first()
146+
{
147+
$this->sort();
148+
149+
return !empty($this->items) ? current($this->items) : null;
150+
}
151+
152+
/**
153+
* Sorts items by descending quality
154+
*/
155+
private function sort()
156+
{
157+
if (!$this->sorted) {
158+
uasort($this->items, function ($a, $b) {
159+
$qA = $a->getQuality();
160+
$qB = $b->getQuality();
161+
162+
if ($qA === $qB) {
163+
return $a->getIndex() > $b->getIndex() ? 1 : -1;
164+
}
165+
166+
return $qA > $qB ? -1 : 1;
167+
});
168+
169+
$this->sorted = true;
170+
}
171+
}
172+
}

0 commit comments

Comments
 (0)
0