10000 merged branch jfsimon/accept-header-parsing (PR #5841) · s7ntech/symfony@53fad04 · GitHub
[go: up one dir, main page]

Skip to content

Commit 53fad04

Browse files
committed
merged branch jfsimon/accept-header-parsing (PR symfony#5841)
This PR was squashed before being merged into the master branch (closes symfony#5841). Commits ------- 6b601bd [http-foudation] Better accept header parsing Discussion ---------- [http-foudation] Better accept header parsing Bug fix: no Feature addition: yes Backwards compatibility break: yes Symfony2 tests pass: yes **Quality:** The special `q` item attribute represents its quality. I had to make some choices: * if I set `q` attribute, it's assigned to quality property, but not to attributes * the `__toString()` method only render `q` attribute if quality is less than 1 **BC break:** The return of `Request::splitHttpAcceptHeader()` has changed. It's result was an array of qualities indexed by an accept value, it now returns an array of `AcceptHeaderItem` indexed by its value. --------------------------------------------------------------------------- by jfsimon at 2012-10-26T08:35:55Z As dicussed in symfony#5711. --------------------------------------------------------------------------- by Seldaek at 2012-10-27T10:35:49Z Maybe you can pull Seldaek@5e8a526 into your branch (for some reason I can't send a PR to your repo, it doesn't show up in github's repo selector.. looks like they don't like projects with too many forks). It allows you to use usort() which hopefully is faster than your merge sort, though I did not bench it. I also added tests to confirm the functionality. --------------------------------------------------------------------------- by Seldaek at 2012-10-27T10:40:27Z Sorry please check Seldaek@376dd93 instead, I missed a few tests in the RequestTest class. --------------------------------------------------------------------------- by jfsimon at 2012-10-29T16:26:03Z @fabpot do you think the introduced BC break is acceptable? --------------------------------------------------------------------------- by fabpot at 2012-10-29T16:37:06Z @jfsimon Are all getAccept*() method BC? --------------------------------------------------------------------------- by jfsimon at 2012-10-29T16:39:26Z @fabpot nope, just `Request::splitHttpAcceptHeader()` --------------------------------------------------------------------------- by jfsimon at 2012-10-29T16:43:18Z @fabpot I think missunderstood... only `Request::splitHttpAcceptHeader()` breaks BC. --------------------------------------------------------------------------- by fabpot at 2012-10-29T16:53:22Z So, a BC break on just splitHttpAcceptHeader is possible... but should be documented properly. Another option would be to deprecate the current method (and keep it as is), and just use the new version everywhere. Sounds better as it won"t introduce any BC breaks. --------------------------------------------------------------------------- by jfsimon at 2012-10-29T16:55:57Z @fabpot Okay, I'll update this PR according to your second option. --------------------------------------------------------------------------- by jfsimon at 2012-10-29T20:14:46Z @fabpot done. As you can see here: https://github.com/symfony/symfony/pull/5841/files#L5L1029 value returned by `Request::splitHttpAcceptHeader()` is not **exactly** the same as before because all attributes are present (not only those before the `q` one). --------------------------------------------------------------------------- by fabpot at 2012-10-30T06:16:23Z The last thing missing before I can merge is a PR to update the documentation (should probably be just a note somewhere with the example you have in the UPGRADE file). --------------------------------------------------------------------------- by jfsimon at 2012-10-30T07:07:08Z @fabpot I could add this example here: http://symfony.com/doc/current/components/http_foundation/introduction.html#request after `Accessing the session`, what do you think? --------------------------------------------------------------------------- by fabpot at 2012-10-30T07:14:10Z Yes, looks good to me.
2 parents 275cf8c + 6b601bd commit 53fad04

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