summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--composer.json3
-rw-r--r--src/Controller/ContentNegotiation.php108
-rw-r--r--tests/Controller/ContentNegotiationTest.php140
-rw-r--r--tests/support/TestHelper.php29
4 files changed, 273 insertions, 7 deletions
diff --git a/composer.json b/composer.json
index 90e4820..bd02105 100644
--- a/composer.json
+++ b/composer.json
@@ -18,7 +18,8 @@
"php": ">=5.6.0",
"psr/http-message": "^1.0",
"jasny/php-functions": "^2.0",
- "dflydev/apache-mime-types": "^1.0"
+ "dflydev/apache-mime-types": "^1.0",
+ "willdurand/negotiation": "^2.2"
},
"require-dev": {
"jasny/php-code-quality": "^2.0",
diff --git a/src/Controller/ContentNegotiation.php b/src/Controller/ContentNegotiation.php
new file mode 100644
index 0000000..75aa4f1
--- /dev/null
+++ b/src/Controller/ContentNegotiation.php
@@ -0,0 +1,108 @@
+<?php
+
+namespace Jasny\Controller;
+
+/**
+ * Controller methods to negotiate content
+ */
+trait ContentNegotiation
+{
+ /**
+ * Get request, set for controller
+ *
+ * @return ServerRequestInterface
+ */
+ abstract public function getRequest();
+
+ /**
+ * Pick best content type
+ *
+ * @param array $priorities
+ * @return string
+ */
+ public function negotiateContentType(array $priorities)
+ {
+ return $this->negotiate($priorities);
+ }
+
+ /**
+ * Pick best language
+ *
+ * @param array $priorities
+ * @return string
+ */
+ public function negotiateLanguage(array $priorities)
+ {
+ return $this->negotiate($priorities, 'language');
+ }
+
+ /**
+ * Pick best encoding
+ *
+ * @param array $priorities
+ * @return string
+ */
+ public function negotiateEncoding(array $priorities)
+ {
+ return $this->negotiate($priorities, 'encoding');
+ }
+
+ /**
+ * Pick best charset
+ *
+ * @param array $priorities
+ * @return string
+ */
+ public function negotiateCharset(array $priorities)
+ {
+ return $this->negotiate($priorities, 'charset');
+ }
+
+ /**
+ * Generalize negotiation
+ *
+ * @param array $priorities
+ * @param string $type Negotiator type
+ * @return string
+ */
+ protected function negotiate(array $priorities, $type = '')
+ {
+ $header = 'Accept';
+
+ if ($type) {
+ $header .= '-' . ucfirst($type);
+ }
+
+ $header = $this->getRequest()->getHeader($header);
+ $header = join(', ', $header);
+
+ $negotiator = $this->getNegotiator($type);
+ $chosen = $negotiator->getBest($header, $priorities);
+
+ return $chosen ? $chosen->getType() : '';
+ }
+
+ /**
+ * Get negotiation library instance
+ *
+ * @param string $type Negotiator type
+ * @return Negotiation\AbstractNegotiator
+ */
+ protected function getNegotiator($type = '')
+ {
+ $class = $this->getNegotiatorName($type);
+
+ return new $class();
+ }
+
+ /**
+ * Get negotiator name
+ *
+ * @param string $type
+ * @return string
+ */
+ protected function getNegotiatorName($type = '')
+ {
+ return 'Negotiation\\' . ucfirst($type) . 'Negotiator';
+ }
+}
diff --git a/tests/Controller/ContentNegotiationTest.php b/tests/Controller/ContentNegotiationTest.php
new file mode 100644
index 0000000..1a390a4
--- /dev/null
+++ b/tests/Controller/ContentNegotiationTest.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace Jasny\Controller;
+
+use Jasny\Controller\ContentNegotiation;
+use Psr\Http\Message\ServerRequestInterface;
+use Jasny\Controller\TestHelper;
+use Negotiation\Negotiator;
+use Negotiation\BaseAccept;
+
+/**
+ * @covers Jasny\Controller\ContentNegotiation
+ */
+class ContentNegotiationTest extends \PHPUnit_Framework_TestCase
+{
+ use TestHelper;
+
+ /**
+ * Test negotiation
+ *
+ * @dataProvider negotiateProvider
+ * @param string $result
+ * @param array $header
+ * @param array $priorities
+ */
+ public function testNegotiate($method, $negotiatorClass, $type, $expected, $headerName, array $headerValue, array $priorities)
+ {
+ $request = $this->createMock(ServerRequestInterface::class);
+ $request->expects($this->once())->method('getHeader')->with($this->equalTo($headerName))->will($this->returnValue($headerValue));
+
+ $expectedObj = $this->createMock(BaseAccept::class);
+ $expectedObj->expects($this->once())->method('getType')->will($this->returnValue($expected));
+
+ $negotiator = $this->createMock($negotiatorClass);
+ $negotiator->expects($this->once())->method('getBest')->with($this->equalTo(join(', ', $headerValue)), $this->equalTo($priorities))->will($this->returnValue($expectedObj));
+
+ $trait = $this->getController(['getRequest', 'getNegotiator']);
+ $trait->expects($this->once())->method('getRequest')->will($this->returnValue($request));
+ $trait->expects($this->once())->method('getNegotiator')->with($this->equalTo($type))->will($this->returnValue($negotiator));
+
+ $builtClass = $this->callProtectedMethod($trait, 'getNegotiatorName', [$type]);
+ $result = $trait->{$method}($priorities);
+
+ $this->assertEquals($builtClass, $negotiatorClass, "Obtained wrong negotiator class");
+ $this->assertEquals($result, $expected, "Obtained result does not match expected result");
+ }
+
+ /**
+ * Provide data for testing negotiation
+ *
+ * @return array
+ */
+ public function negotiateProvider()
+ {
+ return [
+ [
+ 'negotiateContentType',
+ 'Negotiation\\Negotiator',
+ '',
+ 'text/html',
+ 'Accept',
+ ['text/html', 'application/xhtml+xml', 'application/xml;q=0.9', '*/*;q=0.8'],
+ ['text/html', 'application/xhtml+xml', 'application/xml']
+ ],
+ [
+ 'negotiateContentType',
+ 'Negotiation\\Negotiator',
+ '',
+ '',
+ 'Accept',
+ ['text/html', 'application/xhtml+xml', 'application/xml;q=0.9'],
+ ['text/plain', 'application/json']
+ ],
+ [
+ 'negotiateLanguage',
+ 'Negotiation\\LanguageNegotiator',
+ 'language',
+ 'en',
+ 'Accept-Language',
+ ['en', 'fr; q=0.4', 'fu; q=0.9', 'de; q=0.2'],
+ ['en', 'fr']
+ ],
+ [
+ 'negotiateLanguage',
+ 'Negotiation\\LanguageNegotiator',
+ 'language',
+ '',
+ 'Accept-Language',
+ ['en', 'fr; q=0.4', 'fu; q=0.9', 'de; q=0.2'],
+ ['ru', 'es']
+ ],
+ [
+ 'negotiateEncoding',
+ 'Negotiation\\EncodingNegotiator',
+ 'encoding',
+ 'gzip',
+ 'Accept-Encoding',
+ ['gzip', 'compress', 'deflate'],
+ ['gzip', 'compress']
+ ],
+ [
+ 'negotiateEncoding',
+ 'Negotiation\\EncodingNegotiator',
+ 'encoding',
+ '',
+ 'Accept-Encoding',
+ ['gzip', 'compress', 'deflate'],
+ ['br', 'identity']
+ ],
+ [
+ 'negotiateCharset',
+ 'Negotiation\\CharsetNegotiator',
+ 'charset',
+ 'utf-8',
+ 'Accept-Charset',
+ ['utf-8', 'iso-8859-1;q=0.5'],
+ ['utf-8', 'iso-8859-1;q=0.5']
+ ],
+ [
+ 'negotiateCharset',
+ 'Negotiation\\CharsetNegotiator',
+ 'charset',
+ '',
+ 'Accept-Charset',
+ ['utf-8', 'iso-8859-1;q=0.5'],
+ ['windows-1251']
+ ]
+ ];
+ }
+
+ /**
+ * Get the controller class
+ *
+ * @return string
+ */
+ protected function getControllerClass()
+ {
+ return ContentNegotiation::class;
+ }
+}
diff --git a/tests/support/TestHelper.php b/tests/support/TestHelper.php
index a046e4e..05b40d7 100644
--- a/tests/support/TestHelper.php
+++ b/tests/support/TestHelper.php
@@ -20,14 +20,14 @@ trait TestHelper
/**
* Get the controller class
- *
+ *
* @return string
*/
protected function getControllerClass()
{
return Controller::class;
}
-
+
/**
* Get mock for controller
*
@@ -46,14 +46,14 @@ trait TestHelper
if (isset($mockClassName)) {
$builder->setMockClassName($mockClassName);
}
-
+
$getMock = trait_exists($class) ? 'getMockForTrait' : 'getMockForAbstractClass';
return $builder->$getMock();
}
-
+
/**
* Set a private or protected property of the given object
- *
+ *
* @param object $object
* @param string $property
* @param mixed $value
@@ -63,9 +63,26 @@ trait TestHelper
if (!is_object($object)) {
throw new \InvalidArgumentException("Excpected an object, got a " . gettype($object));
}
-
+
$refl = new \ReflectionProperty($object, $property);
$refl->setAccessible(true);
$refl->setValue($object, $value);
}
+
+ /**
+ * Call protected method on some object
+ *
+ * @param object $object
+ * @param string $name Method name
+ * @param array $args
+ * @return mixed Result of method call
+ */
+ protected function callProtectedMethod($object, $name, $args)
+ {
+ $class = new \ReflectionClass($object);
+ $method = $class->getMethod($name);
+ $method->setAccessible(true);
+
+ return $method->invokeArgs($object, $args);
+ }
}