Skip to content

[http-foudation] Better accept header parsing #5841

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions UPGRADE-2.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
UPGRADE FROM 2.1 to 2.2
=======================

#### Deprecations

* The `Request::splitHttpAcceptHeader()` is deprecated and will be removed in 2.3.

You should now use the `AcceptHeader` class which give you fluent methods to
parse request accept-* headers. Some examples:

```
$accept = AcceptHeader::fromString($request->headers->get('Accept'));
if ($accept->has('text/html') {
$item = $accept->get('html');
$charset = $item->getAttribute('charset', 'utf-8');
$quality = $item->getQuality();
}

// accepts items are sorted by descending quality
$accepts = AcceptHeader::fromString($request->headers->get('Accept'))->all();

```
172 changes: 172 additions & 0 deletions src/Symfony/Component/HttpFoundation/AcceptHeader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpFoundation;

/**
* Represents an Accept-* header.
*
* An accept header is compound with a list of items,
* sorted by descending quality.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class AcceptHeader
{
/**
* @var AcceptHeaderItem[]
*/
private $items = array();

/**
* @var bool
*/
private $sorted = true;

/**
* Constructor.
*
* @param AcceptHeaderItem[] $items
*/
public function __construct(array $items)
{
foreach ($items as $item) {
$this->add($item);
}
}

/**
* Builds an AcceptHeader instance from a string.
*
* @param string $headerValue
*
* @return AcceptHeader
*/
public static function fromString($headerValue)
{
$index = 0;

return new self(array_map(function ($itemValue) use (&$index) {
$item = AcceptHeaderItem::fromString($itemValue);
$item->setIndex($index++);

return $item;
}, preg_split('/\s*(?:,*("[^"]+"),*|,*(\'[^\']+\'),*|,+)\s*/', $headerValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE)));
}

/**
* Returns header value's string representation.
*
* @return string
*/
public function __toString()
{
return implode(',', $this->items);
}

/**
* Tests if header has given value.
*
* @param string $value
*
* @return Boolean
*/
public function has($value)
{
return isset($this->items[$value]);
}

/**
* Returns given value's item, if exists.
*
* @param string $value
*
* @return AcceptHeaderItem|null
*/
public function get($value)
{
return isset($this->items[$value]) ? $this->items[$value] : null;
}

/**
* Adds an item.
*
* @param AcceptHeaderItem $item
*
* @return AcceptHeader
*/
public function add(AcceptHeaderItem $item)
{
$this->items[$item->getValue()] = $item;
$this->sorted = false;

return $this;
}

/**
* Returns all items.
*
* @return AcceptHeaderItem[]
*/
public function all()
{
$this->sort();

return $this->items;
}

/**
* Filters items on their value using given regex.
*
* @param string $pattern
*
* @return AcceptHeader
*/
public function filter($pattern)
{
return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) {
return preg_match($pattern, $item->getValue());
}));
}

/**
* Returns first item.
*
* @return AcceptHeaderItem|null
*/
public function first()
{
$this->sort();

return !empty($this->items) ? current($this->items) : null;
}

/**
* Sorts items by descending quality
*/
private function sort()
{
if (!$this->sorted) {
uasort($this->items, function ($a, $b) {
$qA = $a->getQuality();
$qB = $b->getQuality();

if ($qA === $qB) {
return $a->getIndex() > $b->getIndex() ? 1 : -1;
}

return $qA > $qB ? -1 : 1;
});

$this->sorted = true;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it set $this->sorted = true; after sorting?!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed it should :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

}
Loading