From 174979277f155df9bfa2291f8281132fa2ce3a9e Mon Sep 17 00:00:00 2001 From: Ian Bytchek Date: Wed, 1 Oct 2014 23:55:20 +0100 Subject: [PATCH] Backport fix for #38 and #39 from 1.3 --- src/Negotiation/LanguageNegotiator.php | 32 +++++++++++ src/Negotiation/Negotiator.php | 53 ++++++++++++------- tests/Negotiation/Tests/AcceptHeaderTest.php | 4 ++ .../Tests/FormatNegotiatorTest.php | 23 ++++++-- .../Tests/LanguageNegotiatorTest.php | 43 ++++++++++++++- tests/Negotiation/Tests/NegotiatorTest.php | 21 +++++--- 6 files changed, 143 insertions(+), 33 deletions(-) diff --git a/src/Negotiation/LanguageNegotiator.php b/src/Negotiation/LanguageNegotiator.php index d0a909b..bd3e53d 100644 --- a/src/Negotiation/LanguageNegotiator.php +++ b/src/Negotiation/LanguageNegotiator.php @@ -44,4 +44,36 @@ protected function parseHeader($header) return $this->sortAcceptHeaders($acceptHeaders, $catchAll); } + + /** + * {@inheritDoc} + */ + protected function match(array $acceptHeaders, array $priorities = array()) + { + $wildcardAccept = null; + + $prioritiesSet = array(); + $prioritiesSet[] = $priorities; + $prioritiesSet[] = array_map(function ($priority) { + return strtok($priority, '-'); + }, $priorities); + + foreach ($acceptHeaders as $accept) { + foreach ($prioritiesSet as $availablePriorities) { + $sanitizedPriorities = $this->sanitize($availablePriorities); + + if (false !== $found = array_search(strtolower($accept->getValue()), $sanitizedPriorities)) { + return $priorities[$found]; + } + + if ('*' === $accept->getValue()) { + $wildcardAccept = $accept; + } + } + } + + if (null !== $wildcardAccept) { + return reset($priorities); + } + } } diff --git a/src/Negotiation/Negotiator.php b/src/Negotiation/Negotiator.php index 02215ee..7c874e4 100644 --- a/src/Negotiation/Negotiator.php +++ b/src/Negotiation/Negotiator.php @@ -20,28 +20,13 @@ public function getBest($header, array $priorities = array()) return null; } - if (0 !== count($priorities)) { - $priorities = $this->sanitize($priorities); - - $wildcardAccept = null; - foreach ($acceptHeaders as $accept) { - if (in_array(strtolower($accept->getValue()), $priorities)) { - return $accept; - } - - if ('*' === $accept->getValue()) { - $wildcardAccept = $accept; - } - } - - if (null !== $wildcardAccept) { - $value = reset($priorities); - - return new AcceptHeader($value, $wildcardAccept->getQuality(), $this->parseParameters($value)); - } + if (0 === count($priorities)) { + return reset($acceptHeaders); } - return reset($acceptHeaders); + $value = $this->match($acceptHeaders, $priorities); + + return empty($value) ? null : new AcceptHeader($value, 1.0, $this->parseParameters($value)); } /** @@ -168,4 +153,32 @@ protected function parseParameters($value) return $parameters; } + + /** + * @param AcceptHeader[] $acceptHeaders Sorted by quality + * @param array $priorities Configured priorities + * + * @return string|null Header string matched + */ + protected function match(array $acceptHeaders, array $priorities = array()) + { + $wildcardAccept = null; + $sanitizedPriorities = $this->sanitize($priorities); + + foreach ($acceptHeaders as $accept) { + if (false !== $found = array_search(strtolower($accept->getValue()), $sanitizedPriorities)) { + return $priorities[$found]; + } + + if ('*' === $accept->getValue()) { + $wildcardAccept = $accept; + } + } + + if (null !== $wildcardAccept) { + return reset($priorities); + } + + return null; + } } diff --git a/tests/Negotiation/Tests/AcceptHeaderTest.php b/tests/Negotiation/Tests/AcceptHeaderTest.php index be7035b..b875bf7 100644 --- a/tests/Negotiation/Tests/AcceptHeaderTest.php +++ b/tests/Negotiation/Tests/AcceptHeaderTest.php @@ -6,6 +6,10 @@ class AcceptHeaderTest extends TestCase { + + /** + * @var AcceptHeader + */ private $acceptHeader; protected function setUp() diff --git a/tests/Negotiation/Tests/FormatNegotiatorTest.php b/tests/Negotiation/Tests/FormatNegotiatorTest.php index 5ed0b9b..88a9b71 100644 --- a/tests/Negotiation/Tests/FormatNegotiatorTest.php +++ b/tests/Negotiation/Tests/FormatNegotiatorTest.php @@ -9,6 +9,10 @@ */ class FormatNegotiatorTest extends TestCase { + + /** + * @var FormatNegotiator + */ private $negotiator; protected function setUp() @@ -28,7 +32,7 @@ public function testGetBest($acceptHeader, $priorities, $expected) } else { $this->assertNotNull($acceptHeader); if (is_array($expected)) { - $this->assertEquals($expected['value'], $acceptHeader->getValue()); + $this->assertEquals($expected['value'], $acceptHeader->getValue()); $this->assertEquals($expected['quality'], $acceptHeader->getQuality()); if (isset($expected['parameters'])) { @@ -331,7 +335,7 @@ public function testRegisterFormat() } /** - * @expectedException InvalidArgumentException + * @expectedException \InvalidArgumentException * @expectedExceptionMessage Format "html" already registered, and override was set to "false". */ public function testRegisterFormatWithExistingFormat() @@ -352,9 +356,18 @@ public function testNormalizePriorities($priorities, $expected) public static function dataProviderForNormalizePriorities() { return array( - array(array('application/json', 'application/xml'), array('application/json', 'application/xml')), - array(array('json', 'application/xml', 'text/*', 'rdf', '*/*'), array('application/json', 'application/x-json', 'application/xml', 'text/*', 'application/rdf+xml', '*/*')), - array(array('json', 'html', '*/*'), array('application/json', 'application/x-json', 'text/html', 'application/xhtml+xml', '*/*')), + array( + array('application/json', 'application/xml'), + array('application/json', 'application/xml') + ), + array( + array('json', 'application/xml', 'text/*', 'rdf', '*/*'), + array('application/json', 'application/x-json', 'application/xml', 'text/*', 'application/rdf+xml', '*/*') + ), + array( + array('json', 'html', '*/*'), + array('application/json', 'application/x-json', 'text/html', 'application/xhtml+xml', '*/*') + ), ); } } diff --git a/tests/Negotiation/Tests/LanguageNegotiatorTest.php b/tests/Negotiation/Tests/LanguageNegotiatorTest.php index 74b8232..3e4d377 100644 --- a/tests/Negotiation/Tests/LanguageNegotiatorTest.php +++ b/tests/Negotiation/Tests/LanguageNegotiatorTest.php @@ -6,6 +6,10 @@ class LanguageNegotiatorTest extends TestCase { + + /** + * @var LanguageNegotiator + */ private $negotiator; protected function setUp() @@ -22,7 +26,7 @@ protected function setUp() public function testGetBestUsesQuality() { $acceptLanguageHeader = 'en; q=0.1, fr; q=0.4, fu; q=0.9, de; q=0.2'; - $acceptHeader = $this->negotiator->getBest($acceptLanguageHeader); + $acceptHeader = $this->negotiator->getBest($acceptLanguageHeader); $this->assertInstanceOf('Negotiation\AcceptHeader', $acceptHeader); $this->assertEquals('fu', $acceptHeader->getValue()); @@ -37,7 +41,7 @@ public function testGetBestUsesQuality() public function testGetBestIgnoresNonExistentContent() { $acceptLanguageHeader = 'en; q=0.1, fr; q=0.4, bu; q=1.0'; - $acceptHeader = $this->negotiator->getBest($acceptLanguageHeader, array('en', 'fr')); + $acceptHeader = $this->negotiator->getBest($acceptLanguageHeader, array('en', 'fr')); $this->assertInstanceOf('Negotiation\AcceptHeader', $acceptHeader); $this->assertEquals('fr', $acceptHeader->getValue()); @@ -58,6 +62,41 @@ public function testGetBest($acceptLanguageHeader, $expected) } } + /** + * Given a accept header containing a generic language (here 'en') + * And priorities containing a localized version of that language + * Then the best language is mapped to 'en' + */ + public function testGenericLanguageAreMappedToSpecific() + { + $acceptLanguageHeader = 'fr-FR, en;q=0.8'; + $priorities = array('en-US', 'de-DE'); + + $acceptHeader = $this->negotiator->getBest($acceptLanguageHeader, $priorities); + + $this->assertInstanceOf('Negotiation\AcceptHeader', $acceptHeader); + $this->assertEquals('en-US', $acceptHeader->getValue()); + } + + public function testGetBestWithWildcard() + { + $acceptLanguageHeader = 'en, *;q=0.9'; + $priorities = array('fr'); + + $acceptHeader = $this->negotiator->getBest($acceptLanguageHeader, $priorities); + + $this->assertInstanceOf('Negotiation\AcceptHeader', $acceptHeader); + $this->assertEquals('fr', $acceptHeader->getValue()); + } + + public function testGetBestDoesNotMatchPriorities() + { + $acceptLanguageHeader = 'en, de'; + $priorities = array('fr'); + + $this->assertNull($this->negotiator->getBest($acceptLanguageHeader, $priorities)); + } + public static function dataProviderForGetBest() { return array( diff --git a/tests/Negotiation/Tests/NegotiatorTest.php b/tests/Negotiation/Tests/NegotiatorTest.php index 27f26a6..f60771c 100644 --- a/tests/Negotiation/Tests/NegotiatorTest.php +++ b/tests/Negotiation/Tests/NegotiatorTest.php @@ -9,6 +9,10 @@ */ class NegotiatorTest extends TestCase { + + /** + * @var Negotiator + */ private $negotiator; protected function setUp() @@ -26,6 +30,11 @@ public function testGetBestReturnsNullWithEmptyHeader() $this->assertNull($this->negotiator->getBest('')); } + public function testGetBestReturnsNullWithUnmatchedHeader() + { + $this->assertNull($this->negotiator->getBest('foo, bar, yo', array('baz'))); + } + public function testGetBestRespectsPriorities() { $acceptHeader = $this->negotiator->getBest('foo, bar, yo', array('yo')); @@ -39,7 +48,7 @@ public function testGetBestInCaseInsensitive() $acceptHeader = $this->negotiator->getBest('foo, bar, yo', array('YO')); $this->assertInstanceOf('Negotiation\AcceptHeader', $acceptHeader); - $this->assertEquals('yo', $acceptHeader->getValue()); + $this->assertEquals('YO', $acceptHeader->getValue()); } public function testGetBestWithQualities() @@ -105,7 +114,7 @@ public function testParseAcceptHeaderEnsuresPrecedence($header, $expected) $i = 0; foreach ($expected as $value => $quality) { - $this->assertEquals($value, $accepts[$i]->getValue()); + $this->assertEquals($value, $accepts[$i]->getValue()); $this->assertEquals($quality, $accepts[$i]->getQuality()); $i++; @@ -135,8 +144,8 @@ public static function dataProviderForTestParseAcceptHeader() array("gzip, deflate\t,sdch", array('gzip', 'deflate', 'sdch')), array('"this;should,not=matter"', array('"this;should,not=matter"')), array('*;q=0.3,ISO-8859-1,utf-8;q=0.7', array('ISO-8859-1', 'utf-8', '*')), - array('*;q=0.3,ISO-8859-1;q=0.7,utf-8;q=0.7', array('ISO-8859-1', 'utf-8', '*')), - array('*;q=0.3,utf-8;q=0.7,ISO-8859-1;q=0.7', array('utf-8', 'ISO-8859-1', '*')), + array('*;q=0.3,ISO-8859-1;q=0.7,utf-8;q=0.7', array('ISO-8859-1', 'utf-8', '*')), + array('*;q=0.3,utf-8;q=0.7,ISO-8859-1;q=0.7', array('utf-8', 'ISO-8859-1', '*')), ); } @@ -168,7 +177,7 @@ public static function dataProviderForTestGetBest() 'iso-8859-1', 'shift-jis', ), - 'ISO-8859-1' + 'iso-8859-1' ), array( $pearCharsetHeader, @@ -202,7 +211,7 @@ public static function dataProviderForTestGetBest() 'iso-8859-1', 'shift-jis', ), - 'ISO-8859-1' + 'iso-8859-1' ), array( $pearCharsetHeader2,