Merge remote-tracking branch 'MyIgel/rebuild-psr7'

This commit is contained in:
Igor Scheller 2018-09-03 22:43:19 +02:00
commit 36dafdb68a
46 changed files with 2786 additions and 278 deletions

View File

@ -15,13 +15,20 @@
],
"require": {
"php": ">=7.0.0",
"ext-gettext": "*",
"ext-json": "*",
"ext-PDO": "*",
"erusev/parsedown": "^1.6",
"illuminate/container": "5.5.*",
"illuminate/database": "5.5.*",
"illuminate/support": "^5.5",
"psr/container": "^1.0",
"psr/http-server-middleware": "^1.0",
"psr/log": "^1.0",
"symfony/http-foundation": "^3.3"
"symfony/http-foundation": "^3.3",
"symfony/psr-http-message-bridge": "^1.0",
"twbs/bootstrap": "^3.3",
"zendframework/zend-diactoros": "^1.7"
},
"require-dev": {
"filp/whoops": "^2.1",

View File

@ -13,5 +13,15 @@ return [
\Engelsystem\Database\DatabaseServiceProvider::class,
\Engelsystem\Http\RequestServiceProvider::class,
\Engelsystem\Http\SessionServiceProvider::class,
\Engelsystem\Http\ResponseServiceProvider::class,
\Engelsystem\Http\Psr7ServiceProvider::class,
],
// Application middleware
'middleware' => [
\Engelsystem\Middleware\SendResponseHandler::class,
\Engelsystem\Middleware\ExceptionHandler::class,
\Engelsystem\Middleware\LegacyMiddleware::class,
\Engelsystem\Middleware\NotFoundResponse::class,
],
];

View File

@ -1,6 +1,6 @@
FROM composer AS composer
COPY composer.json /app/
RUN composer --no-ansi install --no-dev
RUN composer --no-ansi install --no-dev --ignore-platform-reqs
RUN composer --no-ansi dump-autoload --optimize
FROM node:8-alpine as themes

View File

@ -73,8 +73,6 @@ function success($msg, $immediately = false)
*/
function alert($class, $msg, $immediately = false)
{
$session = session();
if (empty($msg)) {
return '';
}
@ -83,6 +81,7 @@ function alert($class, $msg, $immediately = false)
return '<div class="alert alert-' . $class . '">' . $msg . '</div>';
}
$session = session();
$message = $session->get('msg', '');
$message .= alert($class, $msg, true);
$session->set('msg', $message);

View File

@ -2876,13 +2876,13 @@ msgstr ""
"keine Nummer hast, bitte einfach \"-\" angeben."
#: /Users/msquare/workspace/projects/engelsystem/public/index.php:218
msgid "No Access"
msgstr "Kein Zugriff"
msgid "Page not found"
msgstr "Seite nicht gefunden"
#: /Users/msquare/workspace/projects/engelsystem/public/index.php:219
msgid ""
"You don't have permission to view this page . You probably have to sign in "
"or register in order to gain access!"
"This page could not be found or you don't have permission to view it. You "
"probably have to sign in or register in order to gain access!"
msgstr ""
"Du hast keinen Zugriff auf diese Seite. Registriere Dich und logge Dich "
"bitte ein, um Zugriff zu erhalten!"
"Diese Seite existiert nicht oder Du hast keinen Zugriff. Melde Dich an um Zugriff "
"zu erhalten!"

View File

@ -1,247 +1,19 @@
<?php
use Engelsystem\Http\Request;
use Engelsystem\Application;
use Engelsystem\Middleware\Dispatcher;
use Psr\Http\Message\ServerRequestInterface;
require_once realpath(__DIR__ . '/../includes/engelsystem.php');
$free_pages = [
'admin_event_config',
'angeltypes',
'api',
'atom',
'credits',
'ical',
'login',
'public_dashboard',
'rooms',
'shift_entries',
'shifts',
'shifts_json_export',
'stats',
'users',
'user_driver_licenses',
'user_password_recovery',
'user_worklog'
];
/** @var Application $app */
$app = app();
// Gewünschte Seite/Funktion
$page = '';
$title = '';
$content = '';
/** @var ServerRequestInterface $request */
$request = $app->get('psr7.request');
$middleware = $app->getMiddleware();
/** @var Request $request */
$request = $app->get('request');
$page = $request->query->get('p');
if (empty($page)) {
$page = $request->path();
$page = str_replace('-', '_', $page);
}
if ($page == '/') {
$page = isset($user) ? 'news' : 'login';
}
$dispatcher = new Dispatcher($middleware);
$dispatcher->setContainer($app);
if (
preg_match('/^\w*$/i', $page)
&& (
in_array($page, $free_pages)
|| (isset($privileges) && in_array($page, $privileges))
)
) {
$title = $page;
switch ($page) {
case 'api':
error('Api disabled temporarily.');
redirect(page_link_to());
break;
case 'ical':
require_once realpath(__DIR__ . '/../includes/pages/user_ical.php');
user_ical();
break;
case 'atom':
require_once realpath(__DIR__ . '/../includes/pages/user_atom.php');
user_atom();
break;
case 'shifts_json_export':
require_once realpath(__DIR__ . '/../includes/controller/shifts_controller.php');
shifts_json_export_controller();
break;
case 'stats':
require_once realpath(__DIR__ . '/../includes/pages/guest_stats.php');
guest_stats();
break;
case 'user_password_recovery':
require_once realpath(__DIR__ . '/../includes/controller/users_controller.php');
$title = user_password_recovery_title();
$content = user_password_recovery_controller();
break;
case 'public_dashboard':
list($title, $content) = public_dashboard_controller();
break;
case 'angeltypes':
list($title, $content) = angeltypes_controller();
break;
case 'shift_entries':
list($title, $content) = shift_entries_controller();
break;
case 'shifts':
list($title, $content) = shifts_controller();
break;
case 'users':
list($title, $content) = users_controller();
break;
case 'user_angeltypes':
list($title, $content) = user_angeltypes_controller();
break;
case 'user_driver_licenses':
list($title, $content) = user_driver_licenses_controller();
break;
case 'shifttypes':
list($title, $content) = shifttypes_controller();
break;
case 'admin_event_config':
list($title, $content) = event_config_edit_controller();
break;
case 'rooms':
list($title, $content) = rooms_controller();
break;
case 'news':
$title = news_title();
$content = user_news();
break;
case 'news_comments':
require_once realpath(__DIR__ . '/../includes/pages/user_news.php');
$title = user_news_comments_title();
$content = user_news_comments();
break;
case 'user_meetings':
$title = meetings_title();
$content = user_meetings();
break;
case 'user_myshifts':
$title = myshifts_title();
$content = user_myshifts();
break;
case 'user_shifts':
$title = shifts_title();
$content = user_shifts();
break;
case 'user_worklog':
list($title, $content) = user_worklog_controller();
break;
case 'user_messages':
$title = messages_title();
$content = user_messages();
break;
case 'user_questions':
$title = questions_title();
$content = user_questions();
break;
case 'user_settings':
$title = settings_title();
$content = user_settings();
break;
case 'login':
$title = login_title();
$content = guest_login();
break;
case 'register':
$title = register_title();
$content = guest_register();
break;
case 'logout':
$title = logout_title();
$content = guest_logout();
break;
case 'admin_questions':
$title = admin_questions_title();
$content = admin_questions();
break;
case 'admin_user':
$title = admin_user_title();
$content = admin_user();
break;
case 'admin_arrive':
$title = admin_arrive_title();
$content = admin_arrive();
break;
case 'admin_active':
$title = admin_active_title();
$content = admin_active();
break;
case 'admin_free':
$title = admin_free_title();
$content = admin_free();
break;
case 'admin_news':
require_once realpath(__DIR__ . '/../includes/pages/admin_news.php');
$content = admin_news();
break;
case 'admin_rooms':
$title = admin_rooms_title();
$content = admin_rooms();
break;
case 'admin_groups':
$title = admin_groups_title();
$content = admin_groups();
break;
case 'admin_import':
$title = admin_import_title();
$content = admin_import();
break;
case 'admin_shifts':
$title = admin_shifts_title();
$content = admin_shifts();
break;
case 'admin_log':
$title = admin_log_title();
$content = admin_log();
break;
case 'credits':
require_once realpath(__DIR__ . '/../includes/pages/guest_credits.php');
$title = credits_title();
$content = guest_credits();
break;
default:
require_once realpath(__DIR__ . '/../includes/pages/guest_start.php');
$content = guest_start();
break;
}
} else {
// Wenn schon eingeloggt, keine-Berechtigung-Seite anzeigen
if (isset($user)) {
$title = _('No Access');
$content = _('You don\'t have permission to view this page . You probably have to sign in or register in order to gain access!');
} else {
// Sonst zur Loginseite leiten
redirect(page_link_to('login'));
}
}
$event_config = EventConfig();
$parameters = [
'key' => (isset($user) ? $user['api_key'] : ''),
];
if ($page == 'user_meetings') {
$parameters['meetings'] = 1;
}
echo view(__DIR__ . '/../templates/layout.html', [
'theme' => isset($user) ? $user['color'] : config('theme'),
'title' => $title,
'atom_link' => ($page == 'news' || $page == 'user_meetings')
? ' <link href="'
. page_link_to('atom', $parameters)
. '" type = "application/atom+xml" rel = "alternate" title = "Atom Feed">'
: '',
'start_page_url' => page_link_to('/'),
'credits_url' => page_link_to('credits'),
'menu' => make_menu(),
'content' => msg() . $content,
'header_toolbar' => header_toolbar(),
'faq_url' => config('faq_url'),
'contact_email' => config('contact_email'),
'locale' => locale_short(),
'event_info' => EventConfig_info($event_config) . ' <br />'
]);
$dispatcher->handle($request);

View File

@ -7,6 +7,7 @@ use Engelsystem\Container\Container;
use Engelsystem\Container\ServiceProvider;
use Illuminate\Container\Container as IlluminateContainer;
use Psr\Container\ContainerInterface;
use Psr\Http\Server\MiddlewareInterface;
class Application extends Container
{
@ -16,6 +17,9 @@ class Application extends Container
/** @var bool */
protected $isBootstrapped = false;
/** @var MiddlewareInterface[]|string[] */
protected $middleware;
/**
* Registered service providers
*
@ -85,6 +89,8 @@ class Application extends Container
foreach ($config->get('providers', []) as $provider) {
$this->register($provider);
}
$this->middleware = $config->get('middleware', []);
}
foreach ($this->serviceProviders as $provider) {
@ -136,4 +142,12 @@ class Application extends Container
{
return $this->isBootstrapped;
}
/**
* @return MiddlewareInterface[]|string[]
*/
public function getMiddleware()
{
return $this->middleware;
}
}

View File

@ -54,8 +54,10 @@ class Handler
/**
* @param Throwable $e
* @param bool $return
* @return string
*/
public function exceptionHandler($e)
public function exceptionHandler($e, $return = false)
{
if (!$this->request instanceof Request) {
$this->request = new Request();
@ -63,8 +65,21 @@ class Handler
$handler = $this->handler[$this->environment];
$handler->report($e);
ob_start();
$handler->render($this->request, $e);
if ($return) {
$output = ob_get_contents();
ob_end_clean();
return $output;
}
http_response_code(500);
ob_end_flush();
$this->terminateApplicationImmediately();
return '';
}
/**

View File

@ -34,6 +34,8 @@ class Whoops extends Legacy implements HandlerInterface
$whoops = $this->app->make(WhoopsRunner::class);
$handler = $this->getPrettyPageHandler($e);
$whoops->pushHandler($handler);
$whoops->writeToOutput(false);
$whoops->allowQuit(false);
if ($request->isXmlHttpRequest()) {
$handler = $this->getJsonResponseHandler();

248
src/Http/MessageTrait.php Normal file
View File

@ -0,0 +1,248 @@
<?php
namespace Engelsystem\Http;
use Psr\Http\Message\StreamInterface;
use Zend\Diactoros\Stream;
/**
* @implements \Psr\Http\Message\MessageInterface
* @extends \Symfony\Component\HttpFoundation\Response
*/
trait MessageTrait
{
/**
* Retrieves the HTTP protocol version as a string.
*
* The string MUST contain only the HTTP version number (e.g., "1.1", "1.0").
*
* @return string HTTP protocol version.
*/
public function getProtocolVersion()
{
return parent::getProtocolVersion();
}
/**
* Return an instance with the specified HTTP protocol version.
*
* The version string MUST contain only the HTTP version number (e.g.,
* "1.1", "1.0").
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* new protocol version.
*
* @param string $version HTTP protocol version
* @return static
*/
public function withProtocolVersion($version)
{
$new = clone $this;
if (method_exists($new, 'setProtocolVersion')) {
$new->setProtocolVersion($version);
} else {
$new->server->set('SERVER_PROTOCOL', $version);
}
return $new;
}
/**
* Retrieves all message header values.
*
* The keys represent the header name as it will be sent over the wire, and
* each value is an array of strings associated with the header.
*
* // Represent the headers as a string
* foreach ($message->getHeaders() as $name => $values) {
* echo $name . ": " . implode(", ", $values);
* }
*
* // Emit headers iteratively:
* foreach ($message->getHeaders() as $name => $values) {
* foreach ($values as $value) {
* header(sprintf('%s: %s', $name, $value), false);
* }
* }
*
* While header names are not case-sensitive, getHeaders() will preserve the
* exact case in which headers were originally specified.
*
* @return string[][] Returns an associative array of the message's headers. Each
* key MUST be a header name, and each value MUST be an array of strings
* for that header.
*/
public function getHeaders()
{
if (method_exists($this->headers, 'allPreserveCase')) {
return $this->headers->allPreserveCase();
}
return $this->headers->all();
}
/**
* Checks if a header exists by the given case-insensitive name.
*
* @param string $name Case-insensitive header field name.
* @return bool Returns true if any header names match the given header
* name using a case-insensitive string comparison. Returns false if
* no matching header name is found in the message.
*/
public function hasHeader($name)
{
return $this->headers->has($name);
}
/**
* Retrieves a message header value by the given case-insensitive name.
*
* This method returns an array of all the header values of the given
* case-insensitive header name.
*
* If the header does not appear in the message, this method MUST return an
* empty array.
*
* @param string $name Case-insensitive header field name.
* @return string[] An array of string values as provided for the given
* header. If the header does not appear in the message, this method MUST
* return an empty array.
*/
public function getHeader($name)
{
return $this->headers->get($name, null, false);
}
/**
* Retrieves a comma-separated string of the values for a single header.
*
* This method returns all of the header values of the given
* case-insensitive header name as a string concatenated together using
* a comma.
*
* NOTE: Not all header values may be appropriately represented using
* comma concatenation. For such headers, use getHeader() instead
* and supply your own delimiter when concatenating.
*
* If the header does not appear in the message, this method MUST return
* an empty string.
*
* @param string $name Case-insensitive header field name.
* @return string A string of values as provided for the given header
* concatenated together using a comma. If the header does not appear in
* the message, this method MUST return an empty string.
*/
public function getHeaderLine($name)
{
return implode(',', $this->getHeader($name));
}
/**
* Return an instance with the provided value replacing the specified header.
*
* While header names are case-insensitive, the casing of the header will
* be preserved by this function, and returned from getHeaders().
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* new and/or updated header and value.
*
* @param string $name Case-insensitive header field name.
* @param string|string[] $value Header value(s).
* @return static
* @throws \InvalidArgumentException for invalid header names or values.
*/
public function withHeader($name, $value)
{
$new = clone $this;
$new->headers->set($name, $value);
return $new;
}
/**
* Return an instance with the specified header appended with the given value.
*
* Existing values for the specified header will be maintained. The new
* value(s) will be appended to the existing list. If the header did not
* exist previously, it will be added.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* new header and/or value.
*
* @param string $name Case-insensitive header field name to add.
* @param string|string[] $value Header value(s).
* @return static
* @throws \InvalidArgumentException for invalid header names or values.
*/
public function withAddedHeader($name, $value)
{
$new = clone $this;
$new->headers->set($name, $value, false);
return $new;
}
/**
* Return an instance without the specified header.
*
* Header resolution MUST be done without case-sensitivity.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that removes
* the named header.
*
* @param string $name Case-insensitive header field name to remove.
* @return static
*/
public function withoutHeader($name)
{
$new = clone $this;
$new->headers->remove($name);
return $new;
}
/**
* Gets the body of the message.
*
* @return StreamInterface Returns the body as a stream.
*/
public function getBody()
{
$stream = new Stream('php://memory', 'wb+');
$stream->write($this->getContent());
$stream->rewind();
return $stream;
}
/**
* Return an instance with the specified message body.
*
* The body MUST be a StreamInterface object.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return a new instance that has the
* new body stream.
*
* @param StreamInterface $body Body.
* @return static
* @throws \InvalidArgumentException When the body is not valid.
*/
public function withBody(StreamInterface $body)
{
$new = clone $this;
if (method_exists($new, 'setContent')) {
$new->setContent($body);
} else {
$new->content = $body;
}
return $new;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Engelsystem\Http;
use Engelsystem\Container\ServiceProvider;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
class Psr7ServiceProvider extends ServiceProvider
{
public function register()
{
/** @var DiactorosFactory $psr7Factory */
$psr7Factory = $this->app->make(DiactorosFactory::class);
$this->app->instance('psr7.factory', $psr7Factory);
/** @var Request $request */
$request = $this->app->get('request');
$this->app->instance('psr7.request', $request);
$this->app->bind(ServerRequestInterface::class, 'psr7.request');
/** @var Response $response */
$response = $this->app->get('response');
$this->app->instance('psr7.response', $response);
$this->app->bind(ResponseInterface::class, 'psr7.response');
}
}

View File

@ -2,10 +2,18 @@
namespace Engelsystem\Http;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile as SymfonyFile;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
use Zend\Diactoros\UploadedFile;
use Zend\Diactoros\Uri;
class Request extends SymfonyRequest
class Request extends SymfonyRequest implements ServerRequestInterface
{
use MessageTrait;
/**
* Get POST input
*
@ -64,4 +72,441 @@ class Request extends SymfonyRequest
{
return rtrim(preg_replace('/\?.*/', '', $this->getUri()), '/');
}
/**
* Retrieves the message's request target.
*
*
* Retrieves the message's request-target either as it will appear (for
* clients), as it appeared at request (for servers), or as it was
* specified for the instance (see withRequestTarget()).
*
* In most cases, this will be the origin-form of the composed URI,
* unless a value was provided to the concrete implementation (see
* withRequestTarget() below).
*
* If no URI is available, and no request-target has been specifically
* provided, this method MUST return the string "/".
*
* @return string
*/
public function getRequestTarget()
{
$query = $this->getQueryString();
return '/' . $this->path() . (!empty($query) ? '?' . $query : '');
}
/**
* Return an instance with the specific request-target.
*
* If the request needs a non-origin-form request-target e.g., for
* specifying an absolute-form, authority-form, or asterisk-form
* this method may be used to create an instance with the specified
* request-target, verbatim.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* changed request target.
*
* @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
* request-target forms allowed in request messages)
* @param mixed $requestTarget
* @return static
*/
public function withRequestTarget($requestTarget)
{
return $this->create($requestTarget);
}
/**
* Return an instance with the provided HTTP method.
*
* While HTTP method names are typically all uppercase characters, HTTP
* method names are case-sensitive and thus implementations SHOULD NOT
* modify the given string.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* changed request method.
*
* @param string $method Case-sensitive method.
* @return static
* @throws \InvalidArgumentException for invalid HTTP methods.
*/
public function withMethod($method)
{
$new = clone $this;
$new->setMethod($method);
return $new;
}
/**
* Returns an instance with the provided URI.
*
* This method MUST update the Host header of the returned request by
* default if the URI contains a host component. If the URI does not
* contain a host component, any pre-existing Host header MUST be carried
* over to the returned request.
*
* You can opt-in to preserving the original state of the Host header by
* setting `$preserveHost` to `true`. When `$preserveHost` is set to
* `true`, this method interacts with the Host header in the following ways:
*
* - If the Host header is missing or empty, and the new URI contains
* a host component, this method MUST update the Host header in the returned
* request.
* - If the Host header is missing or empty, and the new URI does not contain a
* host component, this method MUST NOT update the Host header in the returned
* request.
* - If a Host header is present and non-empty, this method MUST NOT update
* the Host header in the returned request.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* new UriInterface instance.
*
* @link http://tools.ietf.org/html/rfc3986#section-4.3
* @param UriInterface $uri New request URI to use.
* @param bool $preserveHost Preserve the original state of the Host header.
* @return static
*/
public function withUri(UriInterface $uri, $preserveHost = false)
{
$new = $this->create($uri);
if ($preserveHost) {
$new->headers->set('HOST', $this->getHost());
}
return $new;
}
/**
* Retrieves the URI instance.
*
* This method MUST return a UriInterface instance.
*
* @link http://tools.ietf.org/html/rfc3986#section-4.3
* @return string|UriInterface Returns a UriInterface instance
* representing the URI of the request.
*/
public function getUri()
{
$uri = parent::getUri();
return new Uri($uri);
}
/**
* Retrieve server parameters.
*
* Retrieves data related to the incoming request environment,
* typically derived from PHP's $_SERVER superglobal. The data IS NOT
* REQUIRED to originate from $_SERVER.
*
* @return array
*/
public function getServerParams()
{
return $this->server->all();
}
/**
* Retrieve cookies.
*
* Retrieves cookies sent by the client to the server.
*
* The data MUST be compatible with the structure of the $_COOKIE
* superglobal.
*
* @return array
*/
public function getCookieParams()
{
return $this->cookies->all();
}
/**
* Return an instance with the specified cookies.
*
* The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST
* be compatible with the structure of $_COOKIE. Typically, this data will
* be injected at instantiation.
*
* This method MUST NOT update the related Cookie header of the request
* instance, nor related values in the server params.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* updated cookie values.
*
* @param array $cookies Array of key/value pairs representing cookies.
* @return static
*/
public function withCookieParams(array $cookies)
{
$new = clone $this;
$new->cookies = clone $this->cookies;
$new->cookies->replace($cookies);
return $new;
}
/**
* Retrieve query string arguments.
*
* Retrieves the deserialized query string arguments, if any.
*
* Note: the query params might not be in sync with the URI or server
* params. If you need to ensure you are only getting the original
* values, you may need to parse the query string from `getUri()->getQuery()`
* or from the `QUERY_STRING` server param.
*
* @return array
*/
public function getQueryParams()
{
return $this->query->all();
}
/**
* Return an instance with the specified query string arguments.
*
* These values SHOULD remain immutable over the course of the incoming
* request. They MAY be injected during instantiation, such as from PHP's
* $_GET superglobal, or MAY be derived from some other value such as the
* URI. In cases where the arguments are parsed from the URI, the data
* MUST be compatible with what PHP's parse_str() would return for
* purposes of how duplicate query parameters are handled, and how nested
* sets are handled.
*
* Setting query string arguments MUST NOT change the URI stored by the
* request, nor the values in the server params.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* updated query string arguments.
*
* @param array $query Array of query string arguments, typically from
* $_GET.
* @return static
*/
public function withQueryParams(array $query)
{
$new = clone $this;
$new->query = clone $this->query;
$new->query->replace($query);
return $new;
}
/**
* Retrieve normalized file upload data.
*
* This method returns upload metadata in a normalized tree, with each leaf
* an instance of Psr\Http\Message\UploadedFileInterface.
*
* These values MAY be prepared from $_FILES or the message body during
* instantiation, or MAY be injected via withUploadedFiles().
*
* @return array An array tree of UploadedFileInterface instances; an empty
* array MUST be returned if no data is present.
*/
public function getUploadedFiles()
{
$files = [];
foreach ($this->files as $file) {
/** @var SymfonyFile $file */
$files[] = new UploadedFile(
$file->getPath(),
$file->getSize(),
$file->getError(),
$file->getClientOriginalName(),
$file->getClientMimeType()
);
}
return $files;
}
/**
* Create a new instance with the specified uploaded files.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* updated body parameters.
*
* @param array $uploadedFiles An array tree of UploadedFileInterface instances.
* @return static
* @throws \InvalidArgumentException if an invalid structure is provided.
*/
public function withUploadedFiles(array $uploadedFiles)
{
$new = clone $this;
$new->files = clone $this->files;
$files = [];
foreach ($uploadedFiles as $file) {
/** @var UploadedFileInterface $file */
$filename = tempnam(sys_get_temp_dir(), 'upload');
$handle = fopen($filename, "w");
fwrite($handle, $file->getStream()->getContents());
fclose($handle);
$files[] = new SymfonyFile(
$filename,
$file->getClientFilename(),
$file->getClientMediaType(),
$file->getSize(),
$file->getError()
);
}
$new->files->add($files);
return $new;
}
/**
* Retrieve any parameters provided in the request body.
*
* If the request Content-Type is either application/x-www-form-urlencoded
* or multipart/form-data, and the request method is POST, this method MUST
* return the contents of $_POST.
*
* Otherwise, this method may return any results of deserializing
* the request body content; as parsing returns structured content, the
* potential types MUST be arrays or objects only. A null value indicates
* the absence of body content.
*
* @return null|array|object The deserialized body parameters, if any.
* These will typically be an array or object.
*/
public function getParsedBody()
{
return $this->request->all();
}
/**
* Return an instance with the specified body parameters.
*
* These MAY be injected during instantiation.
*
* If the request Content-Type is either application/x-www-form-urlencoded
* or multipart/form-data, and the request method is POST, use this method
* ONLY to inject the contents of $_POST.
*
* The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
* deserializing the request body content. Deserialization/parsing returns
* structured data, and, as such, this method ONLY accepts arrays or objects,
* or a null value if nothing was available to parse.
*
* As an example, if content negotiation determines that the request data
* is a JSON payload, this method could be used to create a request
* instance with the deserialized parameters.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* updated body parameters.
*
* @param null|array|object $data The deserialized body data. This will
* typically be in an array or object.
* @return static
* @throws \InvalidArgumentException if an unsupported argument type is
* provided.
*/
public function withParsedBody($data)
{
$new = clone $this;
$new->request = clone $this->request;
$new->request->replace($data);
return $new;
}
/**
* Retrieve attributes derived from the request.
*
* The request "attributes" may be used to allow injection of any
* parameters derived from the request: e.g., the results of path
* match operations; the results of decrypting cookies; the results of
* deserializing non-form-encoded message bodies; etc. Attributes
* will be application and request specific, and CAN be mutable.
*
* @return array Attributes derived from the request.
*/
public function getAttributes()
{
return $this->attributes->all();
}
/**
* Retrieve a single derived request attribute.
*
* Retrieves a single derived request attribute as described in
* getAttributes(). If the attribute has not been previously set, returns
* the default value as provided.
*
* This method obviates the need for a hasAttribute() method, as it allows
* specifying a default value to return if the attribute is not found.
*
* @see getAttributes()
* @param string $name The attribute name.
* @param mixed $default Default value to return if the attribute does not exist.
* @return mixed
*/
public function getAttribute($name, $default = null)
{
return $this->attributes->get($name, $default);
}
/**
* Return an instance with the specified derived request attribute.
*
* This method allows setting a single derived request attribute as
* described in getAttributes().
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* updated attribute.
*
* @see getAttributes()
* @param string $name The attribute name.
* @param mixed $value The value of the attribute.
* @return static
*/
public function withAttribute($name, $value)
{
$new = clone $this;
$new->attributes = clone $this->attributes;
$new->attributes->set($name, $value);
return $new;
}
/**
* Return an instance that removes the specified derived request attribute.
*
* This method allows removing a single derived request attribute as
* described in getAttributes().
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that removes
* the attribute.
*
* @see getAttributes()
* @param string $name The attribute name.
* @return static
*/
public function withoutAttribute($name)
{
$new = clone $this;
$new->attributes = clone $this->attributes;
$new->attributes->remove($name);
return $new;
}
}

75
src/Http/Response.php Normal file
View File

@ -0,0 +1,75 @@
<?php
namespace Engelsystem\Http;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
class Response extends SymfonyResponse implements ResponseInterface
{
use MessageTrait;
/**
* Return an instance with the specified status code and, optionally, reason phrase.
*
* If no reason phrase is specified, implementations MAY choose to default
* to the RFC 7231 or IANA recommended reason phrase for the response's
* status code.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* updated status and reason phrase.
*
* @link http://tools.ietf.org/html/rfc7231#section-6
* @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
* @param int $code The 3-digit integer result code to set.
* @param string $reasonPhrase The reason phrase to use with the
* provided status code; if none is provided, implementations MAY
* use the defaults as suggested in the HTTP specification.
* @return static
* @throws \InvalidArgumentException For invalid status code arguments.
*/
public function withStatus($code, $reasonPhrase = '')
{
$new = clone $this;
$new->setStatusCode($code, !empty($reasonPhrase) ? $reasonPhrase : null);
return $new;
}
/**
* Gets the response reason phrase associated with the status code.
*
* Because a reason phrase is not a required element in a response
* status line, the reason phrase value MAY be null. Implementations MAY
* choose to return the default RFC 7231 recommended reason phrase (or those
* listed in the IANA HTTP Status Code Registry) for the response's
* status code.
*
* @link http://tools.ietf.org/html/rfc7231#section-6
* @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
* @return string Reason phrase; must return an empty string if none present.
*/
public function getReasonPhrase()
{
return $this->statusText;
}
/**
* Return an instance with the specified content.
*
* This method MUST be implemented in such a way as to retain the
* immutability of the message, and MUST return an instance that has the
* updated status and reason phrase.
*
* @param mixed $content Content that can be cast to string
* @return static
*/
public function withContent($content)
{
$new = clone $this;
$new->setContent($content);
return $new;
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Engelsystem\Http;
use Engelsystem\Container\ServiceProvider;
class ResponseServiceProvider extends ServiceProvider
{
public function register()
{
$response = $this->app->make(Response::class);
$this->app->instance('response', $response);
}
}

View File

@ -0,0 +1,110 @@
<?php
namespace Engelsystem\Middleware;
use Engelsystem\Application;
use InvalidArgumentException;
use LogicException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class Dispatcher implements MiddlewareInterface, RequestHandlerInterface
{
/** @var MiddlewareInterface[]|string[] */
protected $stack;
/** @var Application */
protected $container;
/** @var RequestHandlerInterface */
protected $next;
/**
* @param MiddlewareInterface[]|string[] $stack
* @param Application|null $container
*/
public function __construct($stack = [], Application $container = null)
{
$this->stack = $stack;
$this->container = $container;
}
/**
* Process an incoming server request and return a response, optionally delegating
* response creation to a handler.
*
* Could be used to group middleware
*
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$this->next = $handler;
return $this->handle($request);
}
/**
* Handle the request and return a response.
*
* It calls all configured middleware and handles their response
*
* @param ServerRequestInterface $request
* @return ResponseInterface
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
$middleware = array_shift($this->stack);
if (!$middleware) {
if ($this->next) {
return $this->next->handle($request);
}
throw new LogicException('Middleware queue is empty');
}
if (is_string($middleware)) {
$middleware = $this->resolveMiddleware($middleware);
}
if (!$middleware instanceof MiddlewareInterface) {
throw new InvalidArgumentException('Middleware is no instance of ' . MiddlewareInterface::class);
}
return $middleware->process($request, $this);
}
/**
* Resolve the middleware with the container
*
* @param string $middleware
* @return MiddlewareInterface
*/
protected function resolveMiddleware($middleware)
{
if (!$this->container instanceof Application) {
throw new InvalidArgumentException('Unable to resolve middleware ' . $middleware);
}
if ($this->container->has($middleware)) {
return $this->container->get($middleware);
}
return $this->container->make($middleware);
}
/**
* @param Application $container
*/
public function setContainer(Application $container)
{
$this->container = $container;
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Engelsystem\Middleware;
use Engelsystem\Exceptions\Handler as ExceptionsHandler;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ExceptionHandler implements MiddlewareInterface
{
/** @var ContainerInterface */
protected $container;
/**
* @param ContainerInterface $container
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* Handles any exceptions that occurred inside other middleware while returning it to the default response handler
*
* Should be added at the beginning
*
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
try {
return $handler->handle($request);
} catch (\Throwable $e) {
/** @var ExceptionsHandler $handler */
$handler = $this->container->get('error.handler');
$content = $handler->exceptionHandler($e, true);
return response($content, 500);
}
}
}

View File

@ -0,0 +1,296 @@
<?php
namespace Engelsystem\Middleware;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class LegacyMiddleware implements MiddlewareInterface
{
protected $free_pages = [
'admin_event_config',
'angeltypes',
'api',
'atom',
'credits',
'ical',
'login',
'public_dashboard',
'rooms',
'shift_entries',
'shifts',
'shifts_json_export',
'shifts_json_export_all',
'stats',
'users',
'user_driver_licenses',
'user_password_recovery',
'user_worklog'
];
/** @var ContainerInterface */
protected $container;
/**
* @param ContainerInterface $container
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* Handle the request the old way
*
* Should be used before a 404 is send
*
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
global $user;
global $privileges;
/** @var Request $appRequest */
$appRequest = $this->container->get('request');
$page = $appRequest->query->get('p');
if (empty($page)) {
$page = $appRequest->path();
$page = str_replace('-', '_', $page);
}
if ($page == '/') {
$page = isset($user) ? 'news' : 'login';
}
$title = $content = '';
if (
preg_match('~^\w+$~i', $page)
&& (
in_array($page, $this->free_pages)
|| (isset($privileges) && in_array($page, $privileges))
)
) {
list($title, $content) = $this->loadPage($page);
}
if (empty($title) and empty($content)) {
return $handler->handle($request);
}
return $this->renderPage($page, $title, $content);
}
/**
* Get the legacy page content and title
*
* @param string $page
* @return array ['title', 'content']
* @codeCoverageIgnore
*/
protected function loadPage($page)
{
$title = ucfirst($page);
switch ($page) {
/** @noinspection PhpMissingBreakStatementInspection */
case 'api':
error('Api disabled temporarily.');
redirect(page_link_to());
/** @noinspection PhpMissingBreakStatementInspection */
case 'ical':
require_once realpath(__DIR__ . '/../../includes/pages/user_ical.php');
user_ical();
/** @noinspection PhpMissingBreakStatementInspection */
case 'atom':
require_once realpath(__DIR__ . '/../../includes/pages/user_atom.php');
user_atom();
/** @noinspection PhpMissingBreakStatementInspection */
case 'shifts_json_export':
require_once realpath(__DIR__ . '/../../includes/controller/shifts_controller.php');
shifts_json_export_controller();
/** @noinspection PhpMissingBreakStatementInspection */
case 'shifts_json_export_all':
require_once realpath(__DIR__ . '/../../includes/controller/shifts_controller.php');
shifts_json_export_all_controller();
/** @noinspection PhpMissingBreakStatementInspection */
case 'stats':
require_once realpath(__DIR__ . '/../../includes/pages/guest_stats.php');
guest_stats();
case 'user_password_recovery':
require_once realpath(__DIR__ . '/../../includes/controller/users_controller.php');
$title = user_password_recovery_title();
$content = user_password_recovery_controller();
return [$title, $content];
case 'public_dashboard':
return public_dashboard_controller();
case 'angeltypes':
return angeltypes_controller();
case 'shift_entries':
return shift_entries_controller();
case 'shifts':
return shifts_controller();
case 'users':
return users_controller();
case 'user_angeltypes':
return user_angeltypes_controller();
case 'user_driver_licenses':
return user_driver_licenses_controller();
case 'shifttypes':
list($title, $content) = shifttypes_controller();
return [$title, $content];
case 'admin_event_config':
list($title, $content) = event_config_edit_controller();
return [$title, $content];
case 'rooms':
return rooms_controller();
case 'news':
$title = news_title();
$content = user_news();
return [$title, $content];
case 'news_comments':
require_once realpath(__DIR__ . '/../../includes/pages/user_news.php');
$title = user_news_comments_title();
$content = user_news_comments();
return [$title, $content];
case 'user_meetings':
$title = meetings_title();
$content = user_meetings();
return [$title, $content];
case 'user_myshifts':
$title = myshifts_title();
$content = user_myshifts();
return [$title, $content];
case 'user_shifts':
$title = shifts_title();
$content = user_shifts();
return [$title, $content];
case 'user_worklog':
return user_worklog_controller();
case 'user_messages':
$title = messages_title();
$content = user_messages();
return [$title, $content];
case 'user_questions':
$title = questions_title();
$content = user_questions();
return [$title, $content];
case 'user_settings':
$title = settings_title();
$content = user_settings();
return [$title, $content];
case 'login':
$title = login_title();
$content = guest_login();
return [$title, $content];
case 'register':
$title = register_title();
$content = guest_register();
return [$title, $content];
case 'logout':
$title = logout_title();
$content = guest_logout();
return [$title, $content];
case 'admin_questions':
$title = admin_questions_title();
$content = admin_questions();
return [$title, $content];
case 'admin_user':
$title = admin_user_title();
$content = admin_user();
return [$title, $content];
case 'admin_arrive':
$title = admin_arrive_title();
$content = admin_arrive();
return [$title, $content];
case 'admin_active':
$title = admin_active_title();
$content = admin_active();
return [$title, $content];
case 'admin_free':
$title = admin_free_title();
$content = admin_free();
return [$title, $content];
case 'admin_news':
require_once realpath(__DIR__ . '/../../includes/pages/admin_news.php');
$content = admin_news();
return [$title, $content];
case 'admin_rooms':
$title = admin_rooms_title();
$content = admin_rooms();
return [$title, $content];
case 'admin_groups':
$title = admin_groups_title();
$content = admin_groups();
return [$title, $content];
case 'admin_import':
$title = admin_import_title();
$content = admin_import();
return [$title, $content];
case 'admin_shifts':
$title = admin_shifts_title();
$content = admin_shifts();
return [$title, $content];
case 'admin_log':
$title = admin_log_title();
$content = admin_log();
return [$title, $content];
case 'credits':
require_once realpath(__DIR__ . '/../../includes/pages/guest_credits.php');
$title = credits_title();
$content = guest_credits();
return [$title, $content];
}
require_once realpath(__DIR__ . '/../../includes/pages/guest_start.php');
$content = guest_start();
return [$title, $content];
}
/**
* Render the template
*
* @param string $page
* @param string $title
* @param string $content
* @return Response
* @codeCoverageIgnore
*/
protected function renderPage($page, $title, $content)
{
global $user;
$event_config = EventConfig();
$parameters = [
'key' => (isset($user) ? $user['api_key'] : ''),
];
if ($page == 'user_meetings') {
$parameters['meetings'] = 1;
}
return response(view(__DIR__ . '/../../templates/layout.html', [
'theme' => isset($user) ? $user['color'] : config('theme'),
'title' => $title,
'atom_link' => ($page == 'news' || $page == 'user_meetings')
? ' <link href="'
. page_link_to('atom', $parameters)
. '" type = "application/atom+xml" rel = "alternate" title = "Atom Feed">'
: '',
'start_page_url' => page_link_to('/'),
'credits_url' => page_link_to('credits'),
'menu' => make_menu(),
'content' => msg() . $content,
'header_toolbar' => header_toolbar(),
'faq_url' => config('faq_url'),
'contact_email' => config('contact_email'),
'locale' => locale(),
'event_info' => EventConfig_info($event_config) . ' <br />'
]));
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace Engelsystem\Middleware;
use Engelsystem\Http\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class NotFoundResponse implements MiddlewareInterface
{
/**
* Returns a 404: Page not found response
*
* Should be the last middleware
*
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$info = _('This page could not be found or you don\'t have permission to view it. You probably have to sign in or register in order to gain access!');
return $this->renderPage($info);
}
/**
* @param string $content
* @return Response
* @codeCoverageIgnore
*/
protected function renderPage($content)
{
global $user;
$event_config = EventConfig();
return response(view(__DIR__ . '/../../templates/layout.html', [
'theme' => isset($user) ? $user['color'] : config('theme'),
'title' => _('Page not found'),
'atom_link' => '',
'start_page_url' => page_link_to('/'),
'credits_url' => page_link_to('credits'),
'menu' => make_menu(),
'content' => msg() . info($content),
'header_toolbar' => header_toolbar(),
'faq_url' => config('faq_url'),
'contact_email' => config('contact_email'),
'locale' => locale(),
'event_info' => EventConfig_info($event_config) . ' <br />'
]), 404);
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace Engelsystem\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class SendResponseHandler implements MiddlewareInterface
{
/**
* Send the server response to the client
*
* This should be the first middleware
*
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$response = $handler->handle($request);
if (!$this->headersSent()) {
$this->sendHeader(sprintf(
'HTTP/%s %s %s',
$response->getProtocolVersion(),
$response->getStatusCode(),
$response->getReasonPhrase()
), true, $response->getStatusCode());
foreach ($response->getHeaders() as $name => $values) {
foreach ($values as $value) {
$this->sendHeader($name . ': ' . $value, false);
}
}
}
echo $response->getBody();
return $response;
}
/**
* Checks if headers have been sent
*
* @return bool
* @codeCoverageIgnore
*/
protected function headersSent()
{
return headers_sent();
}
/**
* Send a raw HTTP header
*
* @param string $content
* @param bool $replace
* @param int $code
* @codeCoverageIgnore
*/
protected function sendHeader($content, $replace = true, $code = null)
{
header($content, $replace, $code);
}
}

View File

@ -4,6 +4,7 @@
use Engelsystem\Application;
use Engelsystem\Config\Config;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Renderer\Renderer;
use Engelsystem\Routing\UrlGeneratorInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
@ -12,7 +13,7 @@ use Symfony\Component\HttpFoundation\Session\SessionInterface;
* Get the global app instance
*
* @param string $id
* @return mixed
* @return mixed|Application
*/
function app($instance_id = null)
{
@ -80,6 +81,27 @@ function request($key = null, $default = null)
return $request->input($key, $default);
}
/**
* @param string $content
* @param int $status
* @param array $headers
* @return Response
*/
function response($content = '', $status = 200, $headers = [])
{
/** @var Response $response */
$response = app('psr7.response');
$response = $response
->withContent($content)
->withStatus($status);
foreach ($headers as $key => $value) {
$response = $response->withAddedHeader($key, $value);
}
return $response;
}
/**
* @param string $key
* @param mixed $default

View File

@ -0,0 +1,14 @@
<?php
namespace Engelsystem\Test\Feature;
use PHPUnit\Framework\TestCase;
abstract class ApplicationFeatureTest extends TestCase
{
public static function setUpBeforeClass()
{
$_SERVER['HTTP_HOST'] = 'foo.bar';
require_once __DIR__ . '/../../includes/engelsystem.php';
}
}

View File

@ -3,18 +3,13 @@
namespace Engelsystem\Test\Feature\Logger;
use Engelsystem\Logger\EngelsystemLogger;
use PHPUnit\Framework\TestCase;
use Engelsystem\Test\Feature\ApplicationFeatureTest;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
class EngelsystemLoggerTest extends TestCase
class EngelsystemLoggerTest extends ApplicationFeatureTest
{
public static function setUpBeforeClass()
{
require_once __DIR__ . '/../../../includes/engelsystem.php';
}
/**
* @return LoggerInterface
*/

View File

@ -2,16 +2,11 @@
namespace Engelsystem\Test\Feature\Model;
use PHPUnit\Framework\TestCase;
use Engelsystem\Test\Feature\ApplicationFeatureTest;
use Psr\Log\LogLevel;
class LogEntriesModelTest extends TestCase
class LogEntriesModelTest extends ApplicationFeatureTest
{
public static function setUpBeforeClass()
{
require_once __DIR__ . '/../../../includes/engelsystem.php';
}
public function testCreateLogEntry()
{
LogEntries_clear_all();

View File

@ -8,11 +8,6 @@ class RoomModelTest extends TestCase
{
private $room_id = null;
public static function setUpBeforeClass()
{
require_once __DIR__ . '/../../../includes/engelsystem.php';
}
public function createRoom()
{
$this->room_id = Room_create('test', false, null, null);

View File

@ -9,6 +9,7 @@ use Engelsystem\Container\ServiceProvider;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject;
use Psr\Container\ContainerInterface;
use Psr\Http\Server\MiddlewareInterface;
use ReflectionClass;
class ApplicationTest extends TestCase
@ -118,6 +119,7 @@ class ApplicationTest extends TestCase
/**
* @covers \Engelsystem\Application::bootstrap
* @covers \Engelsystem\Application::isBooted
* @covers \Engelsystem\Application::getMiddleware
*/
public function testBootstrap()
{
@ -137,10 +139,11 @@ class ApplicationTest extends TestCase
$config = $this->getMockBuilder(Config::class)
->getMock();
$config->expects($this->once())
$middleware = [MiddlewareInterface::class];
$config->expects($this->exactly(2))
->method('get')
->with('providers')
->willReturn([$serviceProvider]);
->withConsecutive(['providers'], ['middleware'])
->willReturnOnConsecutiveCalls([$serviceProvider], $middleware);
$property = (new ReflectionClass($app))->getProperty('serviceProviders');
$property->setAccessible(true);
@ -149,6 +152,7 @@ class ApplicationTest extends TestCase
$app->bootstrap($config);
$this->assertTrue($app->isBooted());
$this->assertEquals($middleware, $app->getMiddleware());
// Run bootstrap another time to ensure that providers are registered only once
$app->bootstrap($config);

View File

@ -49,15 +49,19 @@ class HandlerTest extends TestCase
public function testExceptionHandler()
{
$exception = new Exception();
$errorMessage = 'Oh noes, an error!';
/** @var HandlerInterface|Mock $handlerMock */
$handlerMock = $this->getMockForAbstractClass(HandlerInterface::class);
$handlerMock->expects($this->once())
$handlerMock->expects($this->atLeastOnce())
->method('report')
->with($exception);
$handlerMock->expects($this->once())
$handlerMock->expects($this->atLeastOnce())
->method('render')
->with($this->isInstanceOf(Request::class), $exception);
->with($this->isInstanceOf(Request::class), $exception)
->willReturnCallback(function () use ($errorMessage) {
echo $errorMessage;
});
/** @var Handler|Mock $handler */
$handler = $this->getMockBuilder(Handler::class)
@ -68,7 +72,11 @@ class HandlerTest extends TestCase
$handler->setHandler(Handler::ENV_PRODUCTION, $handlerMock);
$this->expectOutputString($errorMessage);
$handler->exceptionHandler($exception);
$return = $handler->exceptionHandler($exception, true);
$this->assertEquals($errorMessage, $return);
}
/**

View File

@ -72,6 +72,14 @@ class WhoopsTest extends TestCase
[$prettyPageHandler],
[$jsonResponseHandler]
);
$whoopsRunner
->expects($this->once())
->method('writeToOutput')
->with(false);
$whoopsRunner
->expects($this->once())
->method('allowQuit')
->with(false);
$whoopsRunner
->expects($this->once())
->method('handleException')

View File

@ -6,6 +6,7 @@ use Engelsystem\Application;
use Engelsystem\Config\Config;
use Engelsystem\Container\Container;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Renderer\Renderer;
use Engelsystem\Routing\UrlGeneratorInterface;
use PHPUnit\Framework\TestCase;
@ -126,6 +127,33 @@ class HelpersTest extends TestCase
$this->assertEquals('requestValue', request('requestKey'));
}
/**
* @covers \response
*/
public function testResponse()
{
/** @var MockObject|Response $response */
$response = $this->getMockBuilder(Response::class)->getMock();
$this->getAppMock('psr7.response', $response);
$response->expects($this->once())
->method('withContent')
->with('Lorem Ipsum?')
->willReturn($response);
$response->expects($this->once())
->method('withStatus')
->with(501)
->willReturn($response);
$response->expects($this->exactly(2))
->method('withAddedHeader')
->withConsecutive(['lor', 'em'], ['foo', 'bar'])
->willReturn($response);
$this->assertEquals($response, response('Lorem Ipsum?', 501, ['lor' => 'em', 'foo' => 'bar',]));
}
/**
* @covers \session
*/

View File

@ -0,0 +1,50 @@
<?php
namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Test\Unit\Http\Stub\MessageTraitRequestImplementation;
use PHPUnit\Framework\TestCase;
use Zend\Diactoros\Stream;
class MessageTraitRequestTest extends TestCase
{
/**
* @covers \Engelsystem\Http\MessageTrait::withProtocolVersion
*/
public function testWithProtocolVersion()
{
$message = new MessageTraitRequestImplementation();
$newMessage = $message->withProtocolVersion('0.1');
$this->assertNotEquals($message, $newMessage);
$this->assertEquals('0.1', $newMessage->getProtocolVersion());
}
/**
* @covers \Engelsystem\Http\MessageTrait::getHeaders
*/
public function testGetHeaders()
{
$message = new MessageTraitRequestImplementation();
$newMessage = $message->withHeader('lorem', 'ipsum');
$this->assertNotEquals($message, $newMessage);
$this->assertArraySubset(['lorem' => ['ipsum']], $newMessage->getHeaders());
}
/**
* @covers \Engelsystem\Http\MessageTrait::withBody
*/
public function testWithBody()
{
/** @var Stream $stream */
$stream = new Stream('php://memory', 'wb+');
$stream->write('Test content');
$stream->rewind();
$message = new MessageTraitRequestImplementation();
$newMessage = $message->withBody($stream);
$this->assertNotEquals($message, $newMessage);
$this->assertEquals('Test content', $newMessage->getContent());
}
}

View File

@ -0,0 +1,159 @@
<?php
namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Test\Unit\Http\Stub\MessageTraitResponseImplementation;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\StreamInterface;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
use Zend\Diactoros\Stream;
class MessageTraitResponseTest extends TestCase
{
/**
* @covers \Engelsystem\Http\MessageTrait
*/
public function testCreate()
{
$message = new MessageTraitResponseImplementation();
$this->assertInstanceOf(MessageInterface::class, $message);
$this->assertInstanceOf(SymfonyResponse::class, $message);
}
/**
* @covers \Engelsystem\Http\MessageTrait::getProtocolVersion
* @covers \Engelsystem\Http\MessageTrait::withProtocolVersion
*/
public function testGetProtocolVersion()
{
$message = new MessageTraitResponseImplementation();
$newMessage = $message->withProtocolVersion('0.1');
$this->assertNotEquals($message, $newMessage);
$this->assertEquals('0.1', $newMessage->getProtocolVersion());
}
/**
* @covers \Engelsystem\Http\MessageTrait::getHeaders
*/
public function testGetHeaders()
{
$message = new MessageTraitResponseImplementation();
$newMessage = $message->withHeader('Foo', 'bar');
$this->assertNotEquals($message, $newMessage);
$this->assertArraySubset(['Foo' => ['bar']], $newMessage->getHeaders());
$newMessage = $message->withHeader('lorem', ['ipsum', 'dolor']);
$this->assertArraySubset(['lorem' => ['ipsum', 'dolor']], $newMessage->getHeaders());
}
/**
* @covers \Engelsystem\Http\MessageTrait::hasHeader
*/
public function testHasHeader()
{
$message = new MessageTraitResponseImplementation();
$this->assertFalse($message->hasHeader('test'));
$newMessage = $message->withHeader('test', '12345');
$this->assertTrue($newMessage->hasHeader('Test'));
$this->assertTrue($newMessage->hasHeader('test'));
}
/**
* @covers \Engelsystem\Http\MessageTrait::getHeader
*/
public function testGetHeader()
{
$message = new MessageTraitResponseImplementation();
$newMessage = $message->withHeader('foo', 'bar');
$this->assertEquals(['bar'], $newMessage->getHeader('Foo'));
$this->assertEquals([], $newMessage->getHeader('LoremIpsum'));
}
/**
* @covers \Engelsystem\Http\MessageTrait::getHeaderLine
*/
public function testGetHeaderLine()
{
$message = new MessageTraitResponseImplementation();
$newMessage = $message->withHeader('foo', ['bar', 'bla']);
$this->assertEquals('', $newMessage->getHeaderLine('Lorem-Ipsum'));
$this->assertEquals('bar,bla', $newMessage->getHeaderLine('Foo'));
}
/**
* @covers \Engelsystem\Http\MessageTrait::withHeader
*/
public function testWithHeader()
{
$message = new MessageTraitResponseImplementation();
$newMessage = $message->withHeader('foo', 'bar');
$this->assertNotEquals($message, $newMessage);
$this->assertArraySubset(['foo' => ['bar']], $newMessage->getHeaders());
$newMessage = $newMessage->withHeader('Foo', ['lorem', 'ipsum']);
$this->assertArraySubset(['Foo' => ['lorem', 'ipsum']], $newMessage->getHeaders());
}
/**
* @covers \Engelsystem\Http\MessageTrait::withAddedHeader
*/
public function testWithAddedHeader()
{
$message = new MessageTraitResponseImplementation();
$newMessage = $message->withHeader('foo', 'bar');
$this->assertNotEquals($message, $newMessage);
$this->assertArraySubset(['foo' => ['bar']], $newMessage->getHeaders());
$newMessage = $newMessage->withAddedHeader('Foo', ['lorem', 'ipsum']);
$this->assertArraySubset(['Foo' => ['bar', 'lorem', 'ipsum']], $newMessage->getHeaders());
}
/**
* @covers \Engelsystem\Http\MessageTrait::withoutHeader
*/
public function testWithoutHeader()
{
$message = (new MessageTraitResponseImplementation())->withHeader('foo', 'bar');
$this->assertTrue($message->hasHeader('foo'));
$newMessage = $message->withoutHeader('Foo');
$this->assertNotEquals($message, $newMessage);
$this->assertFalse($newMessage->hasHeader('foo'));
}
/**
* @covers \Engelsystem\Http\MessageTrait::getBody
*/
public function testGetBody()
{
$message = (new MessageTraitResponseImplementation())->setContent('Foo bar!');
$body = $message->getBody();
$this->assertInstanceOf(StreamInterface::class, $body);
$this->assertEquals('Foo bar!', $body->getContents());
}
/**
* @covers \Engelsystem\Http\MessageTrait::withBody
*/
public function testWithBody()
{
/** @var Stream $stream */
$stream = new Stream('php://memory', 'wb+');
$stream->write('Test content');
$stream->rewind();
$message = new MessageTraitResponseImplementation();
$newMessage = $message->withBody($stream);
$this->assertNotEquals($message, $newMessage);
$this->assertEquals('Test content', $newMessage->getContent());
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Http\Psr7ServiceProvider;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Test\Unit\ServiceProviderTest;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as RequestInterface;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
class Psr7ServiceProviderTest extends ServiceProviderTest
{
/**
* @covers \Engelsystem\Http\Psr7ServiceProvider::register()
*/
public function testRegister()
{
/** @var MockObject|DiactorosFactory $psr7Factory */
$psr7Factory = $this->createMock(DiactorosFactory::class);
/** @var MockObject|Request $request */
$request = $this->createMock(Request::class);
/** @var MockObject|Response $response */
$response = $this->createMock(Response::class);
/** @var MockObject|RequestInterface $psr7request */
$psr7request = $this->createMock(Request::class);
/** @var MockObject|ResponseInterface $psr7response */
$psr7response = $this->createMock(Response::class);
$app = $this->getApp(['make', 'instance', 'get', 'bind']);
$this->setExpects($app, 'make', [DiactorosFactory::class], $psr7Factory);
$app->expects($this->atLeastOnce())
->method('get')
->withConsecutive(['request'], ['response'])
->willReturnOnConsecutiveCalls($request, $response);
$app->expects($this->atLeastOnce())
->method('instance')
->withConsecutive(
['psr7.factory', $psr7Factory],
['psr7.request', $psr7request],
['psr7.response', $psr7response]
);
$app->expects($this->atLeastOnce())
->method('bind')
->withConsecutive(
[RequestInterface::class, 'psr7.request'],
[ResponseInterface::class, 'psr7.response']
);
$serviceProvider = new Psr7ServiceProvider($app);
$serviceProvider->register();
}
}

View File

@ -5,9 +5,24 @@ namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Http\Request;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile as SymfonyFile;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
class RequestTest extends TestCase
{
/**
* @covers \Engelsystem\Http\Request
*/
public function testCreate()
{
$response = new Request();
$this->assertInstanceOf(SymfonyRequest::class, $response);
$this->assertInstanceOf(RequestInterface::class, $response);
}
/**
* @covers \Engelsystem\Http\Request::postData
*/
@ -96,4 +111,272 @@ class RequestTest extends TestCase
$this->assertEquals('http://foo.bar/bla/foo', $request->url());
$this->assertEquals('https://lorem.ipsum/dolor/sit', $request->url());
}
/**
* @covers \Engelsystem\Http\Request::getRequestTarget
*/
public function testGetRequestTarget()
{
/** @var Request|MockObject $request */
$request = $this
->getMockBuilder(Request::class)
->setMethods(['getQueryString', 'path'])
->getMock();
$request->expects($this->exactly(2))
->method('getQueryString')
->willReturnOnConsecutiveCalls(null, 'foo=bar&lorem=ipsum');
$request->expects($this->exactly(2))
->method('path')
->willReturn('foo/bar');
$this->assertEquals('/foo/bar', $request->getRequestTarget());
$this->assertEquals('/foo/bar?foo=bar&lorem=ipsum', $request->getRequestTarget());
}
/**
* @covers \Engelsystem\Http\Request::withRequestTarget
*/
public function testWithRequestTarget()
{
$request = new Request();
foreach (
[
'*',
'/foo/bar',
'https://lorem.ipsum/test?lor=em'
] as $target
) {
$new = $request->withRequestTarget($target);
$this->assertNotEquals($request, $new);
}
}
/**
* @covers \Engelsystem\Http\Request::withMethod
*/
public function testWithMethod()
{
$request = new Request();
$new = $request->withMethod('PUT');
$this->assertNotEquals($request, $new);
$this->assertEquals('PUT', $new->getMethod());
}
/**
* @covers \Engelsystem\Http\Request::withUri
*/
public function testWithUri()
{
/** @var UriInterface|MockObject $uri */
$uri = $this->getMockForAbstractClass(UriInterface::class);
$uri->expects($this->atLeastOnce())
->method('__toString')
->willReturn('http://foo.bar/bla?foo=bar');
$request = Request::create('http://lor.em/');
$new = $request->withUri($uri);
$this->assertNotEquals($request, $new);
$this->assertEquals('http://foo.bar/bla?foo=bar', (string)$new->getUri());
$new = $request->withUri($uri, true);
$this->assertEquals('http://lor.em/bla?foo=bar', (string)$new->getUri());
}
/**
* @covers \Engelsystem\Http\Request::getUri
*/
public function testGetUri()
{
$request = Request::create('http://lor.em/test?bla=foo');
$uri = $request->getUri();
$this->assertInstanceOf(UriInterface::class, $uri);
$this->assertEquals('http://lor.em/test?bla=foo', (string)$uri);
}
/**
* @covers \Engelsystem\Http\Request::getServerParams
*/
public function testGetServerParams()
{
$server = ['foo' => 'bar'];
$request = new Request([], [], [], [], [], $server);
$this->assertEquals($server, $request->getServerParams());
}
/**
* @covers \Engelsystem\Http\Request::getCookieParams
*/
public function testGetCookieParams()
{
$cookies = ['session' => 'LoremIpsumDolorSit'];
$request = new Request([], [], [], $cookies);
$this->assertEquals($cookies, $request->getCookieParams());
}
/**
* @covers \Engelsystem\Http\Request::withCookieParams
*/
public function testWithCookieParams()
{
$cookies = ['lor' => 'em'];
$request = new Request();
$new = $request->withCookieParams($cookies);
$this->assertNotEquals($request, $new);
$this->assertEquals($cookies, $new->getCookieParams());
}
/**
* @covers \Engelsystem\Http\Request::getQueryParams
*/
public function testGetQueryParams()
{
$params = ['foo' => 'baz'];
$request = new Request($params);
$this->assertEquals($params, $request->getQueryParams());
}
/**
* @covers \Engelsystem\Http\Request::withQueryParams
*/
public function testWithQueryParams()
{
$params = ['test' => 'ing'];
$request = new Request();
$new = $request->withQueryParams($params);
$this->assertNotEquals($request, $new);
$this->assertEquals($params, $new->getQueryParams());
}
/**
* @covers \Engelsystem\Http\Request::getUploadedFiles
*/
public function testGetUploadedFiles()
{
$filename = tempnam(sys_get_temp_dir(), 'test');
file_put_contents($filename, 'LoremIpsum!');
$files = [new SymfonyFile($filename, 'foo.html', 'text/html', 11)];
$request = new Request([], [], [], [], $files);
$uploadedFiles = $request->getUploadedFiles();
$this->assertNotEmpty($uploadedFiles);
/** @var UploadedFileInterface $file */
$file = $uploadedFiles[0];
$this->assertInstanceOf(UploadedFileInterface::class, $file);
$this->assertEquals('foo.html', $file->getClientFilename());
$this->assertEquals('text/html', $file->getClientMediaType());
$this->assertEquals(11, $file->getSize());
}
/**
* @covers \Engelsystem\Http\Request::withUploadedFiles
*/
public function testWithUploadedFiles()
{
$filename = tempnam(sys_get_temp_dir(), 'test');
file_put_contents($filename, 'LoremIpsum!');
$file = new \Zend\Diactoros\UploadedFile($filename, 11, UPLOAD_ERR_OK, 'test.txt', 'text/plain');
$request = new Request();
$new = $request->withUploadedFiles([$file]);
$uploadedFiles = $new->getUploadedFiles();
$this->assertNotEquals($request, $new);
$this->assertNotEmpty($uploadedFiles);
/** @var UploadedFileInterface $file */
$file = $uploadedFiles[0];
$this->assertEquals('test.txt', $file->getClientFilename());
$this->assertEquals('text/plain', $file->getClientMediaType());
$this->assertEquals(11, $file->getSize());
}
/**
* @covers \Engelsystem\Http\Request::getParsedBody
*/
public function testGetParsedBody()
{
$body = ['foo' => 'lorem'];
$request = new Request();
$request->request->add($body);
$this->assertEquals($body, $request->getParsedBody());
}
/**
* @covers \Engelsystem\Http\Request::withParsedBody
*/
public function testWithParsedBody()
{
$data = ['test' => 'er'];
$request = new Request();
$new = $request->withParsedBody($data);
$this->assertNotEquals($request, $new);
$this->assertEquals($data, $new->getParsedBody());
}
/**
* @covers \Engelsystem\Http\Request::getAttributes
*/
public function testGetAttributes()
{
$attributes = ['foo' => 'lorem', 'ipsum' => 'dolor'];
$request = new Request([], [], $attributes);
$this->assertEquals($attributes, $request->getAttributes());
}
/**
* @covers \Engelsystem\Http\Request::getAttribute
*/
public function testGetAttribute()
{
$attributes = ['foo' => 'lorem', 'ipsum' => 'dolor'];
$request = new Request([], [], $attributes);
$this->assertEquals($attributes['ipsum'], $request->getAttribute('ipsum'));
$this->assertEquals(null, $request->getAttribute('dolor'));
$this->assertEquals(1234, $request->getAttribute('test', 1234));
}
/**
* @covers \Engelsystem\Http\Request::withAttribute
*/
public function testWithAttribute()
{
$request = new Request();
$new = $request->withAttribute('lorem', 'ipsum');
$this->assertNotEquals($request, $new);
$this->assertEquals('ipsum', $new->getAttribute('lorem'));
}
/**
* @covers \Engelsystem\Http\Request::withoutAttribute
*/
public function testWithoutAttribute()
{
$attributes = ['foo' => 'lorem', 'ipsum' => 'dolor'];
$request = new Request([], [], $attributes);
$new = $request->withoutAttribute('ipsum');
$this->assertNotEquals($request, $new);
$this->assertEquals(['foo' => 'lorem'], $new->getAttributes());
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Http\Response;
use Engelsystem\Http\ResponseServiceProvider;
use Engelsystem\Test\Unit\ServiceProviderTest;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
class ResponseServiceProviderTest extends ServiceProviderTest
{
/**
* @covers \Engelsystem\Http\ResponseServiceProvider::register()
*/
public function testRegister()
{
/** @var MockObject|Response $response */
$response = $this->getMockBuilder(Response::class)
->getMock();
$app = $this->getApp();
$this->setExpects($app, 'make', [Response::class], $response);
$this->setExpects($app, 'instance', ['response', $response]);
$serviceProvider = new ResponseServiceProvider($app);
$serviceProvider->register();
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Http\Response;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
class ResponseTest extends TestCase
{
/**
* @covers \Engelsystem\Http\Response
*/
public function testCreate()
{
$response = new Response();
$this->assertInstanceOf(SymfonyResponse::class, $response);
$this->assertInstanceOf(ResponseInterface::class, $response);
}
/**
* @covers \Engelsystem\Http\Response::withStatus
* @covers \Engelsystem\Http\Response::getReasonPhrase
*/
public function testWithStatus()
{
$response = new Response();
$newResponse = $response->withStatus(503);
$this->assertNotEquals($response, $newResponse);
$this->assertNotEquals('', $newResponse->getReasonPhrase());
$this->assertEquals(503, $newResponse->getStatusCode());
$newResponse = $response->withStatus(503, 'Foo');
$this->assertEquals('Foo', $newResponse->getReasonPhrase());
}
/**
* @covers \Engelsystem\Http\Response::withContent
*/
public function testWithContent()
{
$response = new Response();
$newResponse = $response->withContent('Lorem Ipsum?');
$this->assertNotEquals($response, $newResponse);
$this->assertEquals('Lorem Ipsum?', $newResponse->getContent());
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Engelsystem\Test\Unit\Http\Stub;
use Engelsystem\Http\MessageTrait;
use Psr\Http\Message\MessageInterface;
use Symfony\Component\HttpFoundation\Request;
class MessageTraitRequestImplementation extends Request implements MessageInterface
{
use MessageTrait;
}

View File

@ -0,0 +1,12 @@
<?php
namespace Engelsystem\Test\Unit\Http\Stub;
use Engelsystem\Http\MessageTrait;
use Psr\Http\Message\MessageInterface;
use Symfony\Component\HttpFoundation\Response;
class MessageTraitResponseImplementation extends Response implements MessageInterface
{
use MessageTrait;
}

View File

@ -0,0 +1,230 @@
<?php
namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Application;
use Engelsystem\Middleware\Dispatcher;
use Engelsystem\Test\Unit\Middleware\Stub\NotARealMiddleware;
use Engelsystem\Test\Unit\Middleware\Stub\ReturnResponseMiddleware;
use InvalidArgumentException;
use LogicException;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use ReflectionClass as Reflection;
class DispatcherTest extends TestCase
{
/**
* @covers \Engelsystem\Middleware\Dispatcher::__construct
*/
public function testInit()
{
/** @var Application|MockObject $container */
$container = $this->createMock(Application::class);
$dispatcher = new Dispatcher([], $container);
$this->assertInstanceOf(MiddlewareInterface::class, $dispatcher);
$this->assertInstanceOf(RequestHandlerInterface::class, $dispatcher);
$reflection = new Reflection(get_class($dispatcher));
$property = $reflection->getProperty('container');
$property->setAccessible(true);
$this->assertEquals($container, $property->getValue($dispatcher));
}
/**
* @covers \Engelsystem\Middleware\Dispatcher::process
*/
public function testProcess()
{
/** @var ServerRequestInterface|MockObject $request */
$request = $this->createMock(ServerRequestInterface::class);
/** @var ResponseInterface|MockObject $response */
$response = $this->createMock(ResponseInterface::class);
/** @var RequestHandlerInterface|MockObject $handler */
$handler = $this->createMock(RequestHandlerInterface::class);
/** @var Dispatcher|MockObject $dispatcher */
$dispatcher = $this->getMockBuilder(Dispatcher::class)
->setMethods(['handle'])
->getMock();
$dispatcher->expects($this->once())
->method('handle')
->willReturn($response);
$return = $dispatcher->process($request, $handler);
$this->assertEquals($response, $return);
$reflection = new Reflection(get_class($dispatcher));
$property = $reflection->getProperty('next');
$property->setAccessible(true);
$this->assertEquals($handler, $property->getValue($dispatcher));
}
/**
* @covers \Engelsystem\Middleware\Dispatcher::handle
*/
public function testHandle()
{
/** @var ServerRequestInterface|MockObject $request */
$request = $this->createMock(ServerRequestInterface::class);
/** @var ResponseInterface|MockObject $response */
$response = $this->createMock(ResponseInterface::class);
/** @var MiddlewareInterface|MockObject $middleware */
$middleware = $this->createMock(MiddlewareInterface::class);
$dispatcher = new Dispatcher([$middleware]);
$middleware->expects($this->once())
->method('process')
->with($request, $dispatcher)
->willReturn($response);
$return = $dispatcher->handle($request);
$this->assertEquals($response, $return);
}
/**
* @covers \Engelsystem\Middleware\Dispatcher::handle
*/
public function testHandleNext()
{
/** @var ServerRequestInterface|MockObject $request */
$request = $this->createMock(ServerRequestInterface::class);
/** @var ResponseInterface|MockObject $response */
$response = $this->createMock(ResponseInterface::class);
/** @var RequestHandlerInterface|MockObject $handler */
$handler = $this->createMock(RequestHandlerInterface::class);
$dispatcher = new Dispatcher();
$handler->expects($this->once())
->method('handle')
->with($request)
->willReturn($response);
$reflection = new Reflection(get_class($dispatcher));
$property = $reflection->getProperty('next');
$property->setAccessible(true);
$property->setValue($dispatcher, $handler);
$return = $dispatcher->handle($request);
$this->assertEquals($response, $return);
}
/**
* @covers \Engelsystem\Middleware\Dispatcher::handle
*/
public function testHandleNoMiddleware()
{
/** @var ServerRequestInterface|MockObject $request */
$request = $this->createMock(ServerRequestInterface::class);
$this->expectException(LogicException::class);
$dispatcher = new Dispatcher();
$dispatcher->handle($request);
}
/**
* @covers \Engelsystem\Middleware\Dispatcher::handle
*/
public function testHandleNoRealMiddleware()
{
/** @var ServerRequestInterface|MockObject $request */
$request = $this->createMock(ServerRequestInterface::class);
$this->expectException(InvalidArgumentException::class);
$dispatcher = new Dispatcher([new NotARealMiddleware()]);
$dispatcher->handle($request);
}
/**
* @covers \Engelsystem\Middleware\Dispatcher::handle
*/
public function testHandleCallResolve()
{
/** @var ServerRequestInterface|MockObject $request */
$request = $this->createMock(ServerRequestInterface::class);
/** @var ResponseInterface|MockObject $response */
$response = $this->createMock(ResponseInterface::class);
/** @var MiddlewareInterface|MockObject $middleware */
$middleware = $this->createMock(MiddlewareInterface::class);
/** @var Dispatcher|MockObject $dispatcher */
$dispatcher = $this->getMockBuilder(Dispatcher::class)
->setConstructorArgs([[MiddlewareInterface::class]])
->setMethods(['resolveMiddleware'])
->getMock();
$dispatcher->expects($this->once())
->method('resolveMiddleware')
->with(MiddlewareInterface::class)
->willReturn($middleware);
$middleware->expects($this->once())
->method('process')
->with($request, $dispatcher)
->willReturn($response);
$return = $dispatcher->handle($request);
$this->assertEquals($response, $return);
}
/**
* @covers \Engelsystem\Middleware\Dispatcher::resolveMiddleware
* @covers \Engelsystem\Middleware\Dispatcher::setContainer
*/
public function testResolveMiddleware()
{
/** @var Application|MockObject $container */
$container = $this->createMock(Application::class);
/** @var ServerRequestInterface|MockObject $request */
$request = $this->createMock(ServerRequestInterface::class);
/** @var ResponseInterface|MockObject $response */
$response = $this->createMock(ResponseInterface::class);
$returnResponseMiddleware = new ReturnResponseMiddleware($response);
$container->expects($this->exactly(2))
->method('has')
->withConsecutive([ReturnResponseMiddleware::class], ['middleware'])
->willReturnOnConsecutiveCalls(false, true);
$container->expects($this->once())
->method('make')
->with(ReturnResponseMiddleware::class)
->willReturn($returnResponseMiddleware);
$container->expects($this->once())
->method('get')
->with('middleware')
->willReturn($returnResponseMiddleware);
$dispatcher = new Dispatcher([ReturnResponseMiddleware::class]);
$dispatcher->setContainer($container);
$dispatcher->handle($request);
$dispatcher = new Dispatcher(['middleware'], $container);
$dispatcher->handle($request);
}
/**
* @covers \Engelsystem\Middleware\Dispatcher::resolveMiddleware
*/
public function testResolveMiddlewareNoContainer()
{
/** @var ServerRequestInterface|MockObject $request */
$request = $this->createMock(ServerRequestInterface::class);
$this->expectException(InvalidArgumentException::class);
$dispatcher = new Dispatcher([ReturnResponseMiddleware::class]);
$dispatcher->handle($request);
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Application;
use Engelsystem\Exceptions\Handler;
use Engelsystem\Http\Response;
use Engelsystem\Middleware\ExceptionHandler;
use Engelsystem\Test\Unit\Middleware\Stub\ExceptionMiddlewareHandler;
use Engelsystem\Test\Unit\Middleware\Stub\ReturnResponseMiddlewareHandler;
use Illuminate\Contracts\Container\Container as ContainerInterface;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class ExceptionHandlerTest extends TestCase
{
/**
* @covers \Engelsystem\Middleware\ExceptionHandler::__construct
* @covers \Engelsystem\Middleware\ExceptionHandler::process
*/
public function testRegister()
{
/** @var MockObject|ContainerInterface $container */
$container = $this->getMockForAbstractClass(ContainerInterface::class);
/** @var MockObject|ServerRequestInterface $request */
$request = $this->getMockBuilder(ServerRequestInterface::class)->getMock();
/** @var MockObject|ResponseInterface $response */
$response = $this->getMockBuilder(Response::class)->getMock();
/** @var MockObject|Handler $errorHandler */
$errorHandler = $this->getMockBuilder(Handler::class)->getMock();
$returnResponseHandler = new ReturnResponseMiddlewareHandler($response);
$throwExceptionHandler = new ExceptionMiddlewareHandler();
Application::setInstance($container);
$container->expects($this->exactly(2))
->method('get')
->withConsecutive(['error.handler'], ['psr7.response'])
->willReturnOnConsecutiveCalls($errorHandler, $response);
$response->expects($this->once())
->method('withContent')
->willReturn($response);
$response->expects($this->once())
->method('withStatus')
->with(500)
->willReturn($response);
$handler = new ExceptionHandler($container);
$return = $handler->process($request, $returnResponseHandler);
$this->assertEquals($response, $return);
$return = $handler->process($request, $throwExceptionHandler);
$this->assertEquals($response, $return);
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Http\Request;
use Engelsystem\Middleware\LegacyMiddleware;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Symfony\Component\HttpFoundation\ParameterBag;
class LegacyMiddlewareTest extends TestCase
{
/**
* @covers \Engelsystem\Middleware\LegacyMiddleware::__construct
* @covers \Engelsystem\Middleware\LegacyMiddleware::process
*/
public function testRegister()
{
/** @var ContainerInterface|MockObject $container */
$container = $this->getMockForAbstractClass(ContainerInterface::class);
/** @var LegacyMiddleware|MockObject $middleware */
$middleware = $this->getMockBuilder(LegacyMiddleware::class)
->setConstructorArgs([$container])
->setMethods(['loadPage', 'renderPage'])
->getMock();
/** @var Request|MockObject $defaultRequest */
$defaultRequest = $this->createMock(Request::class);
/** @var ParameterBag|MockObject $parameters */
$parameters = $this->createMock(ParameterBag::class);
/** @var ResponseInterface|MockObject $response */
$response = $this->getMockForAbstractClass(ResponseInterface::class);
/** @var RequestHandlerInterface|MockObject $handler */
$handler = $this->getMockForAbstractClass(RequestHandlerInterface::class);
/** @var ServerRequestInterface|MockObject $request */
$request = $this->getMockForAbstractClass(ServerRequestInterface::class);
$middleware->expects($this->exactly(2))
->method('loadPage')
->withConsecutive(['user_worklog'], ['login'])
->willReturnOnConsecutiveCalls(
['title', 'content'],
['title2', 'content2']
);
$middleware->expects($this->exactly(2))
->method('renderPage')
->withConsecutive(
['user_worklog', 'title', 'content'],
['login', 'title2', 'content2']
)
->willReturn($response);
$container->expects($this->atLeastOnce())
->method('get')
->with('request')
->willReturn($defaultRequest);
$defaultRequest->query = $parameters;
$defaultRequest->expects($this->once())
->method('path')
->willReturn('user-worklog');
$parameters->expects($this->exactly(3))
->method('get')
->with('p')
->willReturnOnConsecutiveCalls(
null,
'foo',
'/'
);
$handler->expects($this->once())
->method('handle')
->with($request)
->willReturn($response);
$middleware->process($request, $handler);
$middleware->process($request, $handler);
$middleware->process($request, $handler);
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Middleware\NotFoundResponse;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class NotFoundResponseTest extends TestCase
{
/**
* @covers \Engelsystem\Middleware\NotFoundResponse::process
*/
public function testRegister()
{
/** @var NotFoundResponse|MockObject $middleware */
$middleware = $this->getMockBuilder(NotFoundResponse::class)
->setMethods(['renderPage'])
->getMock();
/** @var ResponseInterface|MockObject $response */
$response = $this->getMockForAbstractClass(ResponseInterface::class);
/** @var RequestHandlerInterface|MockObject $handler */
$handler = $this->getMockForAbstractClass(RequestHandlerInterface::class);
/** @var ServerRequestInterface|MockObject $request */
$request = $this->getMockForAbstractClass(ServerRequestInterface::class);
$middleware->expects($this->once())
->method('renderPage')
->willReturn($response);
$handler->expects($this->never())
->method('handle');
$middleware->process($request, $handler);
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Middleware\SendResponseHandler;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class SendResponseHandlerTest extends TestCase
{
/**
* @covers \Engelsystem\Middleware\SendResponseHandler::process
*/
public function testRegister()
{
/** @var SendResponseHandler|MockObject $middleware */
$middleware = $this->getMockBuilder(SendResponseHandler::class)
->setMethods(['headersSent', 'sendHeader'])
->getMock();
/** @var ServerRequestInterface|MockObject $request */
$request = $this->getMockForAbstractClass(ServerRequestInterface::class);
/** @var ResponseInterface|MockObject $response */
$response = $this->getMockForAbstractClass(ResponseInterface::class);
/** @var RequestHandlerInterface|MockObject $handler */
$handler = $this->getMockForAbstractClass(RequestHandlerInterface::class);
$middleware->expects($this->atLeastOnce())
->method('headersSent')
->willReturnOnConsecutiveCalls(true, false);
$middleware->expects($this->exactly(4))
->method('sendHeader')
->withConsecutive(
['HTTP/0.7 505 Something went wrong!', true, 505],
['Foo: bar', false],
['lorem: ipsum', false],
['lorem: dolor', false]
);
$handler->expects($this->exactly(2))
->method('handle')
->with($request)
->willReturn($response);
$response->expects($this->exactly(2))
->method('getBody')
->willReturn('Lorem Ipsum!');
$response->expects($this->atLeastOnce())
->method('getProtocolVersion')
->willReturn('0.7');
$response->expects($this->atLeastOnce())
->method('getStatusCode')
->willReturn(505);
$response->expects($this->once())
->method('getReasonPhrase')
->willReturn('Something went wrong!');
$response->expects($this->once())
->method('getHeaders')
->willReturn(['Foo' => ['bar'], 'lorem' => ['ipsum', 'dolor']]);
$this->expectOutputString('Lorem Ipsum!Lorem Ipsum!');
$middleware->process($request, $handler);
$middleware->process($request, $handler);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Engelsystem\Test\Unit\Middleware\Stub;
use Exception;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ExceptionMiddlewareHandler implements RequestHandlerInterface
{
/**
* Throws an exception
*
* @param ServerRequestInterface $request
* @return ResponseInterface
* @throws Exception
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
throw new Exception('Boooom!');
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Engelsystem\Test\Unit\Middleware\Stub;
class NotARealMiddleware
{
// I'm not a middleware!
}

View File

@ -0,0 +1,36 @@
<?php
namespace Engelsystem\Test\Unit\Middleware\Stub;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ReturnResponseMiddleware implements MiddlewareInterface
{
/** @var ResponseInterface */
protected $response;
public function __construct(ResponseInterface $response)
{
$this->response = $response;
}
/**
* Process an incoming server request and return a response, optionally delegating
* response creation to a handler.
*
* Could be used to group middleware
*
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
return $this->response;
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Engelsystem\Test\Unit\Middleware\Stub;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ReturnResponseMiddlewareHandler implements RequestHandlerInterface
{
/** @var ResponseInterface */
protected $response;
public function __construct(ResponseInterface $response)
{
$this->response = $response;
}
/**
* Returns a given response
*
* @param ServerRequestInterface $request
* @return ResponseInterface
* @throws \Exception
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
return $this->response;
}
}