Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #17 from thizzle/master

Various bug fixes for URI classes
  • Loading branch information...
commit a82fac83615489ae2e227024b4d532f19cc87e2a 2 parents a375814 + cb4ba7e
Dave Hauenstein davehauenstein authored
33 src/Sonno/Application/Application.php
View
@@ -145,11 +145,7 @@ public function run(RequestInterface $request)
// object is a scalar value: construct a new Response
if (is_scalar($result)) {
- $response = new Response(
- 200,
- $result,
- array('Content-Type' => $selectedVariant->getMediaType())
- );
+ $response = new Response(200, $result);
// object is already a Response
} else if ($result instanceof Response) {
@@ -158,17 +154,34 @@ public function run(RequestInterface $request)
// object implements the Renderable interface: construct a Response
// using the reprsentation produced by render()
} else if ($result instanceof Renderable) {
- $response = new Response(
- 200,
- $result->render($selectedVariant),
- array('Content-Type' => $selectedVariant->getMediaType())
- );
+ $response = new Response(200, $result->render($selectedVariant));
// cannot determine how to handle the object returned
} else {
throw new MalformedResourceRepresentationException;
}
+ // ensure a Content-Type header is present
+ if (!$response->hasHeader('Content-Type')
+ && $response->getStatusCode() < 300
+ && $response->getContent()
+ ) {
+ $response->setHeaders(
+ array(
+ 'Content-Type' => $selectedVariant->getMediaType()
+ )
+ );
+ }
+
+ // ensure a Content-Length header is present
+ if (!$response->hasHeader('Content-Length')) {
+ $response->setHeaders(
+ array(
+ 'Content-Length' => strlen($response->getContent())
+ )
+ );
+ }
+
// process any HTTP status filter callbacks
$statusCode = $response->getStatusCode();
if (isset($this->_responseFilters[$statusCode])) {
2  src/Sonno/Application/Renderable.php
View
@@ -42,5 +42,5 @@ public function render(Variant $mediaType);
*
* @return object
*/
- public function unrender($representation, Variant $mediaType);
+ public static function unrender($representation, Variant $mediaType);
}
2  src/Sonno/Configuration/Configuration.php
View
@@ -59,7 +59,7 @@ public function setBasePath($basePath)
*/
public function getBasePath()
{
- return $this->_basePath;
+ return $this->_basePath ?: '/';
}
/**
28 src/Sonno/Configuration/Driver/AnnotationDriver.php
View
@@ -207,7 +207,7 @@ protected function _extractClassParams(ReflectionClass $class)
// Examine Annotation: @Path
$annot = $this->_reader->getClassAnnotation(
$class,
- '\Sonno\Annotation\Path'
+ 'Sonno\Annotation\Path'
);
if ($annot) {
$params['classPath'] = $annot->getPath();
@@ -216,7 +216,7 @@ protected function _extractClassParams(ReflectionClass $class)
// Examine Annotation: @Consumes
$annot = $this->_reader->getClassAnnotation(
$class,
- '\Sonno\Annotation\Consumes'
+ 'Sonno\Annotation\Consumes'
);
if ($annot) {
$params['consumes'] = $annot->getMediaTypes();
@@ -225,7 +225,7 @@ protected function _extractClassParams(ReflectionClass $class)
// Examine Annotation: @Produces
$annot = $this->_reader->getClassAnnotation(
$class,
- '\Sonno\Annotation\Produces'
+ 'Sonno\Annotation\Produces'
);
if ($annot) {
$params['produces'] = $annot->getMediaTypes();
@@ -247,7 +247,7 @@ protected function _extractMethodParams(ReflectionMethod $method)
// Examine Annotation: @{HTTP_VERB}
$annot = $this->_reader->getMethodAnnotation(
$method,
- '\Sonno\Annotation\HttpMethod'
+ 'Sonno\Annotation\HttpMethod'
);
if ($annot) {
$params['httpMethod'] = (string) $annot;
@@ -258,7 +258,7 @@ protected function _extractMethodParams(ReflectionMethod $method)
// Examine Annotation: @Path
$annot = $this->_reader->getMethodAnnotation(
$method,
- '\Sonno\Annotation\Path'
+ 'Sonno\Annotation\Path'
);
if ($annot) {
$params['methodPath'] = $annot->getPath();
@@ -267,7 +267,7 @@ protected function _extractMethodParams(ReflectionMethod $method)
// Examine Annotation: @Consumes
$annot = $this->_reader->getMethodAnnotation(
$method,
- '\Sonno\Annotation\Consumes'
+ 'Sonno\Annotation\Consumes'
);
if ($annot) {
$params['consumes'] = $annot->getMediaTypes();
@@ -276,7 +276,7 @@ protected function _extractMethodParams(ReflectionMethod $method)
// Examine Annotation: @Produces
$annot = $this->_reader->getMethodAnnotation(
$method,
- '\Sonno\Annotation\Produces'
+ 'Sonno\Annotation\Produces'
);
if ($annot) {
$params['produces'] = $annot->getMediaTypes();
@@ -285,7 +285,7 @@ protected function _extractMethodParams(ReflectionMethod $method)
// Examine Annotation: @PathParam
$annot = $this->_reader->getMethodAnnotation(
$method,
- '\Sonno\Annotation\PathParam'
+ 'Sonno\Annotation\PathParam'
);
if ($annot) {
$params['pathParams'] = $annot->getParams();
@@ -294,7 +294,7 @@ protected function _extractMethodParams(ReflectionMethod $method)
// Examine Annotation: @CookieParam
$annot = $this->_reader->getMethodAnnotation(
$method,
- '\Sonno\Annotation\CookieParam'
+ 'Sonno\Annotation\CookieParam'
);
if ($annot) {
$params['cookieParams'] = $annot->getParams();
@@ -303,7 +303,7 @@ protected function _extractMethodParams(ReflectionMethod $method)
// Examine Annotation: @FormParam
$annot = $this->_reader->getMethodAnnotation(
$method,
- '\Sonno\Annotation\FormParam'
+ 'Sonno\Annotation\FormParam'
);
if ($annot) {
$params['formParams'] = $annot->getParams();
@@ -312,7 +312,7 @@ protected function _extractMethodParams(ReflectionMethod $method)
// Examine Annotation: @HeaderParam
$annot = $this->_reader->getMethodAnnotation(
$method,
- '\Sonno\Annotation\HeaderParam'
+ 'Sonno\Annotation\HeaderParam'
);
if ($annot) {
$params['headerParams'] = $annot->getParams();
@@ -321,10 +321,10 @@ protected function _extractMethodParams(ReflectionMethod $method)
// Examine Annotation: @QueryParam
$annot = $this->_reader->getMethodAnnotation(
$method,
- '\Sonno\Annotation\QueryParam'
+ 'Sonno\Annotation\QueryParam'
);
if ($annot) {
- $params['queryParam'] = $annot->getParams();
+ $params['queryParams'] = $annot->getParams();
}
return $params;
@@ -343,7 +343,7 @@ public function _extractPropertyParams(ReflectionProperty $property)
// Examine Annotation: @Context
$annot = $this->_reader->getPropertyAnnotation(
$property,
- '\Sonno\Annotation\Context'
+ 'Sonno\Annotation\Context'
);
if ($annot) {
$params['contexts'] = array(
20 src/Sonno/Dispatcher/Dispatcher.php
View
@@ -13,8 +13,9 @@
namespace Sonno\Dispatcher;
use Sonno\Application\WebApplicationException,
- Sonno\Http\Request\RequestInterface,
Sonno\Configuration\Route,
+ Sonno\Http\Request\RequestInterface,
+ Sonno\Http\Variant,
Sonno\Uri\UriInfo,
ReflectionClass,
ReflectionMethod;
@@ -155,6 +156,8 @@ protected function _getResourceMethodArguments(
ReflectionMethod $method
)
{
+ $clsRenderable = 'Sonno\Application\Renderable';
+
$pathParamValues = $this->_uriInfo->getPathParameters();
$queryParamValues = $this->_uriInfo->getQueryParameters();
$headerParamValues = $this->_request->getHeaders();
@@ -168,6 +171,21 @@ protected function _getResourceMethodArguments(
foreach ($method->getParameters() as $idx => $reflParam) {
$parameterName = $reflParam->getName();
+ $parameterClass = $reflParam->getClass();
+
+ // if the parameter is a type that implements Renderable, use the
+ // implementation's unrender() function to generate an instance
+ // of the class from the request body as the parameter value
+ if (null !== $parameterClass
+ && $parameterClass->implementsInterface($clsRenderable)
+ ) {
+ $parameterClassName = $parameterClass->getName();
+ $parameterValue = $parameterClassName::unrender(
+ $this->_request->getRequestBody(),
+ new Variant(null, null, $this->_request->getContentType())
+ );
+ $resourceMethodArgs[$idx] = $parameterValue;
+ }
// search for an argument value in the Path parameter collection
if (in_array($parameterName, $pathParams)
9 src/Sonno/Http/Exception/MethodNotAllowedException.php
View
@@ -24,8 +24,15 @@
*/
class MethodNotAllowedException extends WebApplicationException
{
- public function __construct()
+ /**
+ * @param array $allowedMethods HTTP methods that are allowed.
+ */
+ public function __construct(array $allowedMethods = array())
{
parent::__construct(405);
+
+ $this->_response->setHeaders(
+ array('Allow' => implode(', ', $allowedMethods))
+ );
}
}
4 src/Sonno/Router/Router.php
View
@@ -130,14 +130,16 @@ public function match(RequestInterface $request, &$pathParameters = array())
// filter candidate routes further by matching the incoming request
// method
+ $allowedMethods = array();
foreach ($candidateRoutes as $i => $route) {
if ($route->getHttpMethod() != $requestMethod) {
+ $allowedMethods[] = $route->getHttpMethod();
unset($candidateRoutes[$i]);
}
}
if (empty($candidateRoutes)) {
- throw new MethodNotAllowedException;
+ throw new MethodNotAllowedException($allowedMethods);
}
// filter candidate routes further by matching the incoming media type
29 src/Sonno/Uri/UriBuilder.php
View
@@ -169,13 +169,20 @@ public function resourcePath(
$resourceClassName,
$resourceMethodName = null)
{
+ $foundClass = false;
+
foreach ($this->_config->getRoutes() as $route) {
if ($resourceClassName == $route->getResourceClassName()) {
- $this->path($route->getClassPath());
+ if (!$foundClass) {
+ $foundClass = true;
+ $this->path($route->getClassPath());
+ }
if ($resourceMethodName == $route->getResourceMethodName()) {
$this->path($route->getMethodPath());
+ return $this;
}
+
}
}
@@ -272,7 +279,7 @@ public function build(array $values = array())
// perform URI template value substitution
$countMatches = preg_match_all('/{([^}]*)}/', $uri, $matches);
if ($countMatches > count($values)) {
- throw new LengthException(
+ throw new \LengthException(
sprintf(
'Need %d URI template values, but only %d values supplied.',
$countMatches,
@@ -295,26 +302,14 @@ public function build(array $values = array())
* @param array $values An associative array of URI template parameter
* values.
* @return string
- * @throws LengthException if there are any URI template parameters without
- * a supplied value
*/
public function buildFromMap(array $values = array())
{
$uri = $this->_concatUriComponents();
- // perform URI template value substitution
- $countMatches = preg_match_all('/{([^}]*)}/', $uri, $matches);
- if ($countMatches > count($values)) {
- throw new LengthException(
- sprintf(
- 'Need %d URI template values, but only %d values supplied.',
- $countMatches,
- count($values)
- )
- );
- } else if ($countMatches == count($values)) {
- foreach ($matches[0] as $idx => $varName) {
- $uri = str_replace($varName, $values[$matches[1][$idx]], $uri);
+ foreach ($values as $varName => $varValue) {
+ if (is_string($varValue)) {
+ $uri = str_replace("{{$varName}}", $varValue, $uri);
}
}
4 src/Sonno/Uri/UriInfo.php
View
@@ -114,7 +114,7 @@ public function getAbsolutePathBuilder()
*/
public function getBaseUri()
{
- return $this->_config->getBaseUri();
+ return $this->_config->getBasePath();
}
/**
@@ -125,7 +125,7 @@ public function getBaseUri()
public function getBaseUriBuilder()
{
$builder = new UriBuilder($this->_config, $this->_request);
- return $builder->replacePath($this->_config->getBaseUri());
+ return $builder->replacePath($this->getBaseUri());
}
/**
60 tests/Sonno/Test/Application/ApplicationTest.php
View
@@ -222,6 +222,7 @@ public function testUnsupportedMethod()
$response = $app->run($request);
$this->assertEquals(405, $response->getStatusCode());
+ $this->assertEquals('GET', $response->getHeader('Allow'));
}
/**
@@ -370,6 +371,43 @@ public function testRenderableReturnedByResource()
}
/**
+ * Test that a resource class method whose first argument is a type that
+ * impelements Renderable will be passed an instance of that class
+ * generated from the request body.
+ *
+ * @return void
+ */
+ public function testUnrenderedResourceMethodArgument()
+ {
+ $config = $this->buildMockConfiguration(array(
+ array(
+ 'path' => '/test/polo',
+ 'httpMethod' => 'POST',
+ 'resourceClassName' => 'Sonno\Test\Application\Asset\TestResource',
+ 'resourceMethodName' => 'createPolo',
+ 'produces' => array('application/xml'),
+ 'consumes' => array('application/json'),
+ 'contexts' => array())
+ ), '/service/v1');
+ $request = $this->buildMockRequest(
+ 'POST',
+ '/service/v1/test/polo',
+ 'application/json',
+ new Variant(null, null, 'application/xml'),
+ array(),
+ array(),
+ json_encode(array('colour' => 'Magenta'))
+ );
+
+ $app = new Application($config);
+ $response = $app->run($request);
+
+ $this->assertEquals(201, $response->getStatusCode());
+ $this->assertEquals('<polo><color>magenta</color></polo>', $response->getContent());
+ $this->assertEquals('application/xml', $response->getHeader('Content-Type'));
+ }
+
+ /**
* Test a successful execution of Application::run() that correctly
* processes the result of a resource method that returns a scalar value.
*
@@ -419,7 +457,6 @@ public function testCookieParam()
* Test that a cookie in the HTTP request is successfully passed to a
* resource method call.
*
- * @todo
* @return void
*/
public function testFormParam()
@@ -456,7 +493,6 @@ public function testFormParam()
* Test that a cookie in the HTTP request is successfully passed to a
* resource method call.
*
- * @todo
* @return void
*/
public function testHeaderParam()
@@ -493,7 +529,6 @@ public function testHeaderParam()
* resource method call.
*
* @return void
- * @todo
*/
public function testPathParam()
{
@@ -528,7 +563,6 @@ public function testPathParam()
* resource method call.
*
* @return void
- * @todo
*/
public function testQueryParam()
{
@@ -591,6 +625,12 @@ public function testThrownWebApplicationException()
$this->assertEquals(405, $response->getStatusCode());
}
+ /**
+ * Test registering a response filter callback and ensuring it is
+ * executed.
+ *
+ * @return void
+ */
public function testResponseFilterCallback()
{
$config = $this->buildMockConfiguration(array(
@@ -623,6 +663,12 @@ public function testResponseFilterCallback()
$this->assertEquals('Sorry, but that resource does not exist', $response->getContent());
}
+ /**
+ * Test registering, and then unregistering a specific response filter
+ * callback and ensure that it is not executed.
+ *
+ * @return void
+ */
public function testUnregisterSpecificFilterCallback()
{
$config = $this->buildMockConfiguration(array(
@@ -658,6 +704,12 @@ public function testUnregisterSpecificFilterCallback()
$this->assertEquals('Sorry, but that resource does not exist', $response->getContent());
}
+ /**
+ * Test registering and then unregistering all response filter callbacks
+ * for a specific status code, and ensure that they are not executed.
+ *
+ * @return void
+ */
public function testUnregisterAllFilterCallbacks()
{
$config = $this->buildMockConfiguration(array(
14 tests/Sonno/Test/Application/Asset/Polo.php
View
@@ -26,6 +26,11 @@ public function __construct($colour)
$this->colour = $colour;
}
+ public function getColour()
+ {
+ return $this->colour;
+ }
+
public function slideOffSlowly()
{
}
@@ -35,7 +40,14 @@ public function render(Variant $mediaType)
return $this->colour;
}
- public function unrender($representation, Variant $mediaType)
+ public static function unrender($representation, Variant $mediaType)
{
+ if ('application/json' == $mediaType->getMediaType())
+ {
+ $data = json_decode($representation);
+ return new static($data->colour);
+ }
+
+ return null;
}
}
6 tests/Sonno/Test/Application/Asset/TestResource.php
View
@@ -58,4 +58,10 @@ public function causeError()
{
throw new \Sonno\Http\Exception\MethodNotAllowedException;
}
+
+ public function createPolo(Polo $polo)
+ {
+ $entity = '<polo><color>' . strtolower($polo->getColour()) . '</color></polo>';
+ return new Response(201, $entity, array('Content-Type' => 'application/xml'));
+ }
}
Please sign in to comment.
Something went wrong with that request. Please try again.