Response: Added with and withInput methods and back/redirect functions

This commit is contained in:
Igor Scheller 2019-11-27 19:11:37 +01:00
parent be39c63f46
commit 89742ecd55
11 changed files with 353 additions and 30 deletions

View File

@ -26,6 +26,7 @@ return [
\Engelsystem\Middleware\RequestHandlerServiceProvider::class, \Engelsystem\Middleware\RequestHandlerServiceProvider::class,
\Engelsystem\Middleware\SessionHandlerServiceProvider::class, \Engelsystem\Middleware\SessionHandlerServiceProvider::class,
\Engelsystem\Http\Validation\ValidationServiceProvider::class, \Engelsystem\Http\Validation\ValidationServiceProvider::class,
\Engelsystem\Http\RedirectServiceProvider::class,
// Additional services // Additional services
\Engelsystem\Helpers\VersionServiceProvider::class, \Engelsystem\Helpers\VersionServiceProvider::class,

View File

@ -0,0 +1,13 @@
<?php
namespace Engelsystem\Http;
use Engelsystem\Container\ServiceProvider;
class RedirectServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind('redirect', Redirector::class);
}
}

55
src/Http/Redirector.php Normal file
View File

@ -0,0 +1,55 @@
<?php
namespace Engelsystem\Http;
class Redirector
{
/** @var Request */
protected $request;
/** @var Response */
protected $response;
/**
* @param Request $request
* @param Response $response
*/
public function __construct(Request $request, Response $response)
{
$this->request = $request;
$this->response = $response;
}
/**
* @param string $path
* @param int $status
* @param array $headers
* @return Response
*/
public function to(string $path, int $status = 302, array $headers = []): Response
{
return $this->response->redirectTo($path, $status, $headers);
}
/**
* @param int $status
* @param array $headers
* @return Response
*/
public function back(int $status = 302, array $headers = []): Response
{
return $this->to($this->getPreviousUrl(), $status, $headers);
}
/**
* @return string
*/
protected function getPreviousUrl(): string
{
if ($header = $this->request->getHeader('referer')) {
return array_pop($header);
}
return '/';
}
}

View File

@ -6,27 +6,37 @@ use Engelsystem\Renderer\Renderer;
use InvalidArgumentException; use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse; use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
class Response extends SymfonyResponse implements ResponseInterface class Response extends SymfonyResponse implements ResponseInterface
{ {
use MessageTrait; use MessageTrait;
/**
* @var SessionInterface
*/
protected $session;
/** @var Renderer */ /** @var Renderer */
protected $renderer; protected $renderer;
/** /**
* @param string $content * @param string $content
* @param int $status * @param int $status
* @param array $headers * @param array $headers
* @param Renderer $renderer * @param Renderer $renderer
* @param SessionInterface $session
*/ */
public function __construct( public function __construct(
$content = '', $content = '',
int $status = 200, int $status = 200,
array $headers = [], array $headers = [],
Renderer $renderer = null Renderer $renderer = null,
SessionInterface $session = null
) { ) {
$this->renderer = $renderer; $this->renderer = $renderer;
$this->session = $session;
parent::__construct($content, $status, $headers); parent::__construct($content, $status, $headers);
} }
@ -155,4 +165,44 @@ class Response extends SymfonyResponse implements ResponseInterface
{ {
$this->renderer = $renderer; $this->renderer = $renderer;
} }
/**
* Sets a session attribute (which is mutable)
*
* @param string $key
* @param mixed|mixed[] $value
* @return Response
*/
public function with(string $key, $value)
{
if (!$this->session instanceof SessionInterface) {
throw new InvalidArgumentException('Session not defined');
}
$data = $this->session->get($key);
if (is_array($data) && is_array($value)) {
$value = array_merge_recursive($data, $value);
}
$this->session->set($key, $value);
return $this;
}
/**
* Sets form data to the mutable session
*
* @param array $input
* @return Response
*/
public function withInput(array $input)
{
if (!$this->session instanceof SessionInterface) {
throw new InvalidArgumentException('Session not defined');
}
$this->session->set('form-data', $input);
return $this;
}
} }

View File

@ -63,17 +63,11 @@ class ErrorHandler implements MiddlewareInterface
} catch (HttpException $e) { } catch (HttpException $e) {
$response = $this->createResponse($e->getMessage(), $e->getStatusCode(), $e->getHeaders()); $response = $this->createResponse($e->getMessage(), $e->getStatusCode(), $e->getHeaders());
} catch (ValidationException $e) { } catch (ValidationException $e) {
$response = $this->createResponse('', 302, ['Location' => $this->getPreviousUrl($request)]); $response = $this->redirectBack();
$response->with('errors', ['validation' => $e->getValidator()->getErrors()]);
if ($request instanceof Request) { if ($request instanceof Request) {
$session = $request->getSession(); $response->withInput(Arr::except($request->request->all(), $this->formIgnore));
$errors = array_merge_recursive(
$session->get('errors', []),
['validation' => $e->getValidator()->getErrors()]
);
$session->set('errors', $errors);
$session->set('form-data', Arr::except($request->request->all(), $this->formIgnore));
} }
} }
@ -140,15 +134,12 @@ class ErrorHandler implements MiddlewareInterface
} }
/** /**
* @param ServerRequestInterface $request * Create a redirect back response
* @return string *
* @return Response
*/ */
protected function getPreviousUrl(ServerRequestInterface $request) protected function redirectBack()
{ {
if ($header = $request->getHeader('referer')) { return back();
return array_pop($header);
}
return '/';
} }
} }

View File

@ -4,6 +4,7 @@ use Engelsystem\Application;
use Engelsystem\Config\Config; use Engelsystem\Config\Config;
use Engelsystem\Helpers\Authenticator; use Engelsystem\Helpers\Authenticator;
use Engelsystem\Helpers\Translation\Translator; use Engelsystem\Helpers\Translation\Translator;
use Engelsystem\Http\Redirector;
use Engelsystem\Http\Request; use Engelsystem\Http\Request;
use Engelsystem\Http\Response; use Engelsystem\Http\Response;
use Engelsystem\Http\UrlGeneratorInterface; use Engelsystem\Http\UrlGeneratorInterface;
@ -28,7 +29,7 @@ function app($id = null)
/** /**
* @return Authenticator * @return Authenticator
*/ */
function auth() function auth(): Authenticator
{ {
return app('authenticator'); return app('authenticator');
} }
@ -37,11 +38,24 @@ function auth()
* @param string $path * @param string $path
* @return string * @return string
*/ */
function base_path($path = '') function base_path($path = ''): string
{ {
return app('path') . (empty($path) ? '' : DIRECTORY_SEPARATOR . $path); return app('path') . (empty($path) ? '' : DIRECTORY_SEPARATOR . $path);
} }
/**
* @param int $status
* @param array $headers
* @return Response
*/
function back($status = 302, $headers = []): Response
{
/** @var Redirector $redirect */
$redirect = app('redirect');
return $redirect->back($status, $headers);
}
/** /**
* Get or set config values * Get or set config values
* *
@ -70,11 +84,25 @@ function config($key = null, $default = null)
* @param string $path * @param string $path
* @return string * @return string
*/ */
function config_path($path = '') function config_path($path = ''): string
{ {
return app('path.config') . (empty($path) ? '' : DIRECTORY_SEPARATOR . $path); return app('path.config') . (empty($path) ? '' : DIRECTORY_SEPARATOR . $path);
} }
/**
* @param string $path
* @param int $status
* @param array $headers
* @return Response
*/
function redirect(string $path, $status = 302, $headers = []): Response
{
/** @var Redirector $redirect */
$redirect = app('redirect');
return $redirect->to($path, $status, $headers);
}
/** /**
* @param string $key * @param string $key
* @param mixed $default * @param mixed $default
@ -97,7 +125,7 @@ function request($key = null, $default = null)
* @param array $headers * @param array $headers
* @return Response * @return Response
*/ */
function response($content = '', $status = 200, $headers = []) function response($content = '', $status = 200, $headers = []): Response
{ {
/** @var Response $response */ /** @var Response $response */
$response = app('psr7.response'); $response = app('psr7.response');
@ -155,7 +183,7 @@ function trans($key = null, $replace = [])
* @param array $replace * @param array $replace
* @return string * @return string
*/ */
function __($key, $replace = []) function __($key, $replace = []): string
{ {
/** @var Translator $translator */ /** @var Translator $translator */
$translator = app('translator'); $translator = app('translator');
@ -172,7 +200,7 @@ function __($key, $replace = [])
* @param array $replace * @param array $replace
* @return string * @return string
*/ */
function _e($key, $keyPlural, $number, $replace = []) function _e($key, $keyPlural, $number, $replace = []): string
{ {
/** @var Translator $translator */ /** @var Translator $translator */
$translator = app('translator'); $translator = app('translator');

View File

@ -7,6 +7,7 @@ use Engelsystem\Config\Config;
use Engelsystem\Container\Container; use Engelsystem\Container\Container;
use Engelsystem\Helpers\Authenticator; use Engelsystem\Helpers\Authenticator;
use Engelsystem\Helpers\Translation\Translator; use Engelsystem\Helpers\Translation\Translator;
use Engelsystem\Http\Redirector;
use Engelsystem\Http\Request; use Engelsystem\Http\Request;
use Engelsystem\Http\Response; use Engelsystem\Http\Response;
use Engelsystem\Http\UrlGeneratorInterface; use Engelsystem\Http\UrlGeneratorInterface;
@ -98,6 +99,29 @@ class HelpersTest extends TestCase
$this->assertEquals(['user' => 'FooBar'], config('mail')); $this->assertEquals(['user' => 'FooBar'], config('mail'));
} }
/**
* @covers \back
*/
public function testBack()
{
$response = new Response();
/** @var Redirector|MockObject $redirect */
$redirect = $this->createMock(Redirector::class);
$redirect->expects($this->exactly(2))
->method('back')
->withConsecutive([302, []], [303, ['test' => 'ing']])
->willReturn($response);
$app = new Application();
$app->instance('redirect', $redirect);
$return = back();
$this->assertEquals($response, $return);
$return = back(303, ['test' => 'ing']);
$this->assertEquals($response, $return);
}
/** /**
* @covers \config_path * @covers \config_path
*/ */
@ -117,6 +141,29 @@ class HelpersTest extends TestCase
$this->assertEquals('/foo/conf/bar.php', config_path('bar.php')); $this->assertEquals('/foo/conf/bar.php', config_path('bar.php'));
} }
/**
* @covers \redirect
*/
public function testRedirect()
{
$response = new Response();
/** @var Redirector|MockObject $redirect */
$redirect = $this->createMock(Redirector::class);
$redirect->expects($this->exactly(2))
->method('to')
->withConsecutive(['/lorem', 302, []], ['/ipsum', 303, ['test' => 'er']])
->willReturn($response);
$app = new Application();
$app->instance('redirect', $redirect);
$return = redirect('/lorem');
$this->assertEquals($response, $return);
$return = redirect('/ipsum', 303, ['test' => 'er']);
$this->assertEquals($response, $return);
}
/** /**
* @covers \request * @covers \request
*/ */

View File

@ -0,0 +1,23 @@
<?php
namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Application;
use Engelsystem\Http\RedirectServiceProvider;
use Engelsystem\Test\Unit\ServiceProviderTest;
class RedirectServiceProviderTest extends ServiceProviderTest
{
/**
* @covers \Engelsystem\Http\RedirectServiceProvider::register
*/
public function testRegister()
{
$app = new Application();
$serviceProvider = new RedirectServiceProvider($app);
$serviceProvider->register();
$this->assertTrue($app->has('redirect'));
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Http\Redirector;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use PHPUnit\Framework\TestCase;
class RedirectorTest extends TestCase
{
/**
* @covers \Engelsystem\Http\Redirector::__construct
* @covers \Engelsystem\Http\Redirector::to
*/
public function testTo()
{
$request = new Request();
$response = new Response();
$redirector = new Redirector($request, $response);
$return = $redirector->to('/test');
$this->assertEquals(['/test'], $return->getHeader('location'));
$this->assertEquals(302, $return->getStatusCode());
$return = $redirector->to('/foo', 303, ['test' => 'data']);
$this->assertEquals(['/foo'], $return->getHeader('location'));
$this->assertEquals(303, $return->getStatusCode());
$this->assertEquals(['data'], $return->getHeader('test'));
}
/**
* @covers \Engelsystem\Http\Redirector::back
* @covers \Engelsystem\Http\Redirector::getPreviousUrl
*/
public function testBack()
{
$request = new Request();
$response = new Response();
$redirector = new Redirector($request, $response);
$return = $redirector->back();
$this->assertEquals(['/'], $return->getHeader('location'));
$this->assertEquals(302, $return->getStatusCode());
$request = $request->withHeader('referer', '/old-page');
$redirector = new Redirector($request, $response);
$return = $redirector->back(303, ['foo' => 'bar']);
$this->assertEquals(303, $return->getStatusCode());
$this->assertEquals(['/old-page'], $return->getHeader('location'));
$this->assertEquals(['bar'], $return->getHeader('foo'));
}
}

View File

@ -10,6 +10,8 @@ use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse; use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
class ResponseTest extends TestCase class ResponseTest extends TestCase
{ {
@ -116,4 +118,59 @@ class ResponseTest extends TestCase
$newResponse->getHeaders() $newResponse->getHeaders()
); );
} }
/**
* @covers \Engelsystem\Http\Response::with
*/
public function testWith()
{
$session = new Session(new MockArraySessionStorage());
$response = new Response('', 200, [], null, $session);
$response->with('foo', 'bar');
$this->assertEquals('bar', $session->get('foo'));
$response->with('lorem', ['ipsum', 'dolor' => ['foo' => 'bar']]);
$this->assertEquals(['ipsum', 'dolor' => ['foo' => 'bar']], $session->get('lorem'));
$response->with('lorem', ['dolor' => ['test' => 'er']]);
$this->assertEquals(['ipsum', 'dolor' => ['foo' => 'bar', 'test' => 'er']], $session->get('lorem'));
}
/**
* @covers \Engelsystem\Http\Response::with
*/
public function testWithNoSession()
{
$this->expectException(InvalidArgumentException::class);
$response = new Response();
$response->with('foo', 'bar');
}
/**
* @covers \Engelsystem\Http\Response::withInput
*/
public function testWithInput()
{
$session = new Session(new MockArraySessionStorage());
$response = new Response('', 200, [], null, $session);
$response->withInput(['some' => 'value']);
$this->assertEquals(['some' => 'value'], $session->get('form-data'));
$response->withInput(['lorem' => 'ipsum']);
$this->assertEquals(['lorem' => 'ipsum'], $session->get('form-data'));
}
/**
* @covers \Engelsystem\Http\Response::withInput
*/
public function testWithInputNoSession()
{
$this->expectException(InvalidArgumentException::class);
$response = new Response();
$response->withInput(['some' => 'value']);
}
} }

View File

@ -6,6 +6,7 @@ use Engelsystem\Application;
use Engelsystem\Http\Exceptions\HttpException; use Engelsystem\Http\Exceptions\HttpException;
use Engelsystem\Http\Exceptions\ValidationException; use Engelsystem\Http\Exceptions\ValidationException;
use Engelsystem\Http\Psr7ServiceProvider; use Engelsystem\Http\Psr7ServiceProvider;
use Engelsystem\Http\RedirectServiceProvider;
use Engelsystem\Http\Request; use Engelsystem\Http\Request;
use Engelsystem\Http\Response; use Engelsystem\Http\Response;
use Engelsystem\Http\ResponseServiceProvider; use Engelsystem\Http\ResponseServiceProvider;
@ -18,6 +19,7 @@ use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface; use Psr\Http\Server\RequestHandlerInterface;
use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Twig\Loader\LoaderInterface as TwigLoader; use Twig\Loader\LoaderInterface as TwigLoader;
@ -155,7 +157,7 @@ class ErrorHandlerTest extends TestCase
/** /**
* @covers \Engelsystem\Middleware\ErrorHandler::process * @covers \Engelsystem\Middleware\ErrorHandler::process
* @covers \Engelsystem\Middleware\ErrorHandler::getPreviousUrl * @covers \Engelsystem\Middleware\ErrorHandler::redirectBack
*/ */
public function testProcessValidationException() public function testProcessValidationException()
{ {
@ -185,11 +187,13 @@ class ErrorHandlerTest extends TestCase
/** @var Application $app */ /** @var Application $app */
$app = app(); $app = app();
$app->instance(Session::class, $session);
$app->bind(SessionInterface::class, Session::class);
(new ResponseServiceProvider($app))->register(); (new ResponseServiceProvider($app))->register();
(new Psr7ServiceProvider($app))->register(); (new Psr7ServiceProvider($app))->register();
(new RedirectServiceProvider($app))->register();
$errorHandler = new ErrorHandler($twigLoader); $errorHandler = new ErrorHandler($twigLoader);
$return = $errorHandler->process($request, $handler); $return = $errorHandler->process($request, $handler);
$this->assertEquals(302, $return->getStatusCode()); $this->assertEquals(302, $return->getStatusCode());
@ -209,6 +213,7 @@ class ErrorHandlerTest extends TestCase
], $session->all()); ], $session->all());
$request = $request->withAddedHeader('referer', '/foo/batz'); $request = $request->withAddedHeader('referer', '/foo/batz');
$app->instance(Request::class, $request);
$return = $errorHandler->process($request, $handler); $return = $errorHandler->process($request, $handler);
$this->assertEquals('/foo/batz', $return->getHeaderLine('location')); $this->assertEquals('/foo/batz', $return->getHeaderLine('location'));