Skip to content
This repository has been archived by the owner on Jan 29, 2020. It is now read-only.

Commit

Permalink
Merging develop to master in preparation for 1.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
weierophinney committed Mar 17, 2016
2 parents 9eb4d42 + fabd6ce commit 3795010
Show file tree
Hide file tree
Showing 11 changed files with 299 additions and 46 deletions.
46 changes: 46 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,52 @@ Versions prior to 1.0 were originally released as `phly/conduit`; please visit
its [CHANGELOG](https://github.com/phly/conduit/blob/master/CHANGELOG.md) for
details.

## 1.2.0 - TBD

This release contains two potential backwards compatibility breaks:

- In versions prior to 1.2.0, after `Zend\Stratigility\Http\Response::end()` was
called, `with*()` operations were performed as no-ops, which led to
hard-to-detect errors. Starting with 1.2.0, they now raise a
`RuntimeException`.

- In versions prior to 1.2.0, `Zend\Stratigility\FinalHandler` always provided
exception details in the response payload for errors. Starting with 1.2.0, it
only does so if not in a production environment (which is the default
environment).

### Added

- [#36](https://github.com/zendframework/zend-stratigility/pull/36) adds a new
`InvalidMiddlewareException`, with the static factory `fromValue()` that
provides an exception message detailing the invalid type. `MiddlewarePipe` now
throws this exception from the `pipe()` method when a non-callable value is
provided.
- [#46](https://github.com/zendframework/zend-stratigility/pull/46) adds
`FinalHandler::setOriginalResponse()`, allowing you to alter the response used
for comparisons when the `FinalHandler` is invoked.
- [#37](https://github.com/zendframework/zend-stratigility/pull/37) and
[#49](https://github.com/zendframework/zend-stratigility/pull/49) add
support in `Zend\Stratigility\Dispatch` to catch PHP 7 `Throwable`s.

### Deprecated

- Nothing.

### Removed

- Nothing.

### Fixed

- [#30](https://github.com/zendframework/zend-stratigility/pull/30) updates the
`Response` implementation to raise exceptions from `with*()` methods if they
are called after `end()`.
- [#46](https://github.com/zendframework/zend-stratigility/pull/46) fixes the
behavior of `FinalHandler::handleError()` to only display exception details
when not in production environments, and changes the default environment to
production.

## 1.1.3 - 2016-03-17

### Added
Expand Down
2 changes: 1 addition & 1 deletion doc/book/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ following convenience methods:
- `write()`, which proxies to the `write()` method of the composed response stream.
- `end()`, which marks the response as complete; it can take an optional argument, which, when
provided, will be passed to the `write()` method. Once `end()` has been called, the response is
immutable.
immutable and will throw an exception if a state mutating method like `withHeader` is called.
- `isComplete()` indicates whether or not `end()` has been called.

Additionally, it provides access to the original response created by the server via the method
Expand Down
7 changes: 5 additions & 2 deletions src/Dispatch.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace Zend\Stratigility;

use Exception;
use Throwable;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

Expand Down Expand Up @@ -80,8 +81,10 @@ public function __invoke(
if (! $hasError && $arity < 4) {
return $handler($request, $response, $next);
}
} catch (Exception $e) {
$err = $e;
} catch (Throwable $throwable) {
return $next($request, $response, $throwable);
} catch (Exception $exception) {
return $next($request, $response, $exception);
}

return $next($request, $response, $err);
Expand Down
37 changes: 37 additions & 0 deletions src/Exception/InvalidMiddlewareException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @see http://github.com/zendframework/zend-stratigility for the canonical source repository
* @copyright Copyright (c) 2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-stratigility/blob/master/LICENSE.md New BSD License
*/

namespace Zend\Stratigility\Exception;

use InvalidArgumentException;

class InvalidMiddlewareException extends InvalidArgumentException
{
/**
* Create and return an InvalidArgumentException detailing the invalid middleware type.
*
* @param mixed $value
* @return InvalidArgumentException
*/
public static function fromValue($value)
{
$received = gettype($value);

if (is_object($value)) {
$received = get_class($value);
}

return new self(
sprintf(
'Middleware must be callable, %s found',
$received
)
);
}
}
28 changes: 19 additions & 9 deletions src/FinalHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,9 @@ class FinalHandler
*/
public function __construct(array $options = [], ResponseInterface $response = null)
{
$this->options = $options;
$this->response = $response;
$this->options = $options;

if ($response) {
$this->bodySize = $response->getBody()->getSize();
}
$this->setOriginalResponse($response);
}

/**
Expand Down Expand Up @@ -97,6 +94,20 @@ public function __invoke(RequestInterface $request, ResponseInterface $response,
return $this->create404($request, $response);
}

/**
* Set the original response and response body size for comparison.
*
* @param ResponseInterface $response
*/
public function setOriginalResponse(ResponseInterface $response = null)
{
$this->response = $response;

if ($response) {
$this->bodySize = $response->getBody()->getSize();
}
}

/**
* Handle an error condition
*
Expand All @@ -110,13 +121,12 @@ public function __invoke(RequestInterface $request, ResponseInterface $response,
private function handleError($error, RequestInterface $request, ResponseInterface $response)
{
$response = $response->withStatus(
Utils::getStatusCode($error, $response)
Utils::getStatusCode($error, $response),
$response->getReasonPhrase()
);

$message = $response->getReasonPhrase() ?: 'Unknown Error';
if (! isset($this->options['env'])
|| $this->options['env'] !== 'production'
) {
if (isset($this->options['env']) && $this->options['env'] !== 'production') {
$message = $this->createDevelopmentErrorMessage($error);
}

Expand Down
31 changes: 25 additions & 6 deletions src/Http/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace Zend\Stratigility\Http;

use RuntimeException;
use Psr\Http\Message\ResponseInterface as PsrResponseInterface;
use Psr\Http\Message\StreamInterface;

Expand Down Expand Up @@ -57,11 +58,12 @@ public function getOriginalResponse()
*
* @param string $data
* @return self
* @throws RuntimeException if response is already completed
*/
public function write($data)
{
if ($this->complete) {
return $this;
throw $this->responseIsAlreadyCompleted(__METHOD__);
}

$this->getBody()->write($data);
Expand Down Expand Up @@ -142,11 +144,12 @@ public function getBody()
* Proxy to PsrResponseInterface::withBody()
*
* {@inheritdoc}
* @throws RuntimeException if response is already completed
*/
public function withBody(StreamInterface $body)
{
if ($this->complete) {
return $this;
throw $this->responseIsAlreadyCompleted(__METHOD__);
}

$new = $this->psrResponse->withBody($body);
Expand Down Expand Up @@ -197,11 +200,12 @@ public function getHeaderLine($header)
* Proxy to PsrResponseInterface::withHeader()
*
* {@inheritdoc}
* @throws RuntimeException if response is already completed
*/
public function withHeader($header, $value)
{
if ($this->complete) {
return $this;
throw $this->responseIsAlreadyCompleted(__METHOD__);
}

$new = $this->psrResponse->withHeader($header, $value);
Expand All @@ -212,11 +216,12 @@ public function withHeader($header, $value)
* Proxy to PsrResponseInterface::withAddedHeader()
*
* {@inheritdoc}
* @throws RuntimeException if response is already completed
*/
public function withAddedHeader($header, $value)
{
if ($this->complete) {
return $this;
throw $this->responseIsAlreadyCompleted(__METHOD__);
}

$new = $this->psrResponse->withAddedHeader($header, $value);
Expand All @@ -227,11 +232,12 @@ public function withAddedHeader($header, $value)
* Proxy to PsrResponseInterface::withoutHeader()
*
* {@inheritdoc}
* @throws RuntimeException if response is already completed
*/
public function withoutHeader($header)
{
if ($this->complete) {
return $this;
throw $this->responseIsAlreadyCompleted(__METHOD__);
}

$new = $this->psrResponse->withoutHeader($header);
Expand All @@ -252,11 +258,12 @@ public function getStatusCode()
* Proxy to PsrResponseInterface::withStatus()
*
* {@inheritdoc}
* @throws RuntimeException if response is already completed
*/
public function withStatus($code, $reasonPhrase = null)
{
if ($this->complete) {
return $this;
throw $this->responseIsAlreadyCompleted(__METHOD__);
}

$new = $this->psrResponse->withStatus($code, $reasonPhrase);
Expand All @@ -272,4 +279,16 @@ public function getReasonPhrase()
{
return $this->psrResponse->getReasonPhrase();
}

/**
* @param string $detectedInMethod
* @return RuntimeException
*/
private function responseIsAlreadyCompleted($detectedInMethod)
{
return new RuntimeException(sprintf(
'Calling %s is not possible, as the response is already marked as completed.',
$detectedInMethod
));
}
}
4 changes: 2 additions & 2 deletions src/MiddlewarePipe.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace Zend\Stratigility;

use InvalidArgumentException;
use Zend\Stratigility\Exception\InvalidMiddlewareException;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use SplQueue;
Expand Down Expand Up @@ -108,7 +108,7 @@ public function pipe($path, $middleware = null)

// Ensure we have a valid handler
if (! is_callable($middleware)) {
throw new InvalidArgumentException('Middleware must be callable');
throw InvalidMiddlewareException::fromValue($middleware);
}

$this->pipeline->enqueue(new Route(
Expand Down
55 changes: 55 additions & 0 deletions test/DispatchTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,23 @@
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use RuntimeException;
use stdClass;
use TypeError;
use Zend\Stratigility\Dispatch;
use Zend\Stratigility\Route;

class DispatchTest extends TestCase
{
/**
* @var \Zend\Stratigility\Http\Request|\PHPUnit_Framework_MockObject_MockObject
*/
private $request;

/**
* @var \Zend\Stratigility\Http\Response|\PHPUnit_Framework_MockObject_MockObject
*/
private $response;

public function setUp()
{
$this->request = $this->getMockBuilder('Zend\Stratigility\Http\Request')
Expand Down Expand Up @@ -199,4 +211,47 @@ public function testShouldAllowDispatchingPsr7Instances()
$result = $dispatch($route, $err, $request->reveal(), $response->reveal(), $next);
$this->assertSame($response->reveal(), $result);
}

/**
* @requires PHP 7.0
* @group 37
*/
public function testWillCatchPhp7Throwable()
{
$callableWithHint = function (stdClass $parameter) {
// will not be executed
};

$middleware = function ($req, $res, $next) use ($callableWithHint) {
$callableWithHint('not an stdClass');
};

$errorHandler = $this->getMock('stdClass', ['__invoke']);
$errorHandler
->expects(self::once())
->method('__invoke')
->with(
$this->request,
$this->response,
self::callback(function (TypeError $throwable) {
self::assertStringStartsWith(
'Argument 1 passed to ZendTest\Stratigility\DispatchTest::ZendTest\Stratigility\{closure}()'
. ' must be an instance of stdClass, string given',
$throwable->getMessage()
);

return true;
})
);

$dispatch = new Dispatch();

$dispatch(
new Route('/foo', $middleware),
null,
$this->request,
$this->response,
$errorHandler
);
}
}
Loading

0 comments on commit 3795010

Please sign in to comment.