Skip to content
Snippets Groups Projects
Commit 9b6bd7e7 authored by Marcus Eibrink-Lunzenauer's avatar Marcus Eibrink-Lunzenauer
Browse files

Update der JSONAPI-Bibliotheken, fixes #80

parent 8edcc69d
No related branches found
No related tags found
No related merge requests found
Showing
with 1567 additions and 524 deletions
...@@ -9,17 +9,16 @@ ...@@ -9,17 +9,16 @@
"camspiers/json-pretty": "~1.0.2", "camspiers/json-pretty": "~1.0.2",
"monolog/monolog": "~1.21.0", "monolog/monolog": "~1.21.0",
"php-http/curl-client": "~1.7.0", "php-http/curl-client": "~1.7.0",
"woohoolabs/yang": "~0.9.0", "woohoolabs/yang": "2.3.2",
"codeception/codeception": "~4.1.21", "codeception/codeception": "~4.1.21",
"codeception/module-asserts": "^1.3" "codeception/module-asserts": "^1.3"
}, },
"require": { "require": {
"php": "^7.2", "php": "^7.2",
"guzzlehttp/psr7": "~1.4.2", "guzzlehttp/psr7": "~1.4.2",
"neomerx/json-api": "~1.0.9", "neomerx/json-api": "4.0.1",
"slim/slim": "~3.12.3",
"spomky-labs/otphp": "^8.3.3", "spomky-labs/otphp": "^8.3.3",
"tuupola/cors-middleware": "~0.5.2", "tuupola/cors-middleware": "1.2.1",
"tecnickcom/tcpdf": "^6.3", "tecnickcom/tcpdf": "^6.3",
"scssphp/scssphp": "^1.4", "scssphp/scssphp": "^1.4",
"symfony/yaml": "^3.4", "symfony/yaml": "^3.4",
...@@ -43,6 +42,9 @@ ...@@ -43,6 +42,9 @@
"ext-pcre": "*", "ext-pcre": "*",
"ext-pdo": "*", "ext-pdo": "*",
"ext-mbstring": "*", "ext-mbstring": "*",
"opis/json-schema": "^1.0" "opis/json-schema": "^1.0",
"slim/psr7": "1.4",
"slim/slim": "4.7.1",
"php-di/php-di": "6.3.4"
} }
} }
This diff is collapsed.
<?php
namespace JsonApi;
use Slim\App;
use StudipPlugin;
use JsonApi\Middlewares\RemoveTrailingSlashes;
/**
* Diese Klasse erstellt eine neue Slim-Applikation und konfiguriert
* diese rudimentär vor.
*
* Dabei werden im `Dependency Container` der Slim-Applikation unter
* dem Schlüssel `plugin` das Stud.IP-Plugin vermerkt und außerdem
* eingestellt, dass Fehler des Slim-Frameworks detailliert angezeigt
* werden sollen, wenn sich Stud.IP im Modus `development` befindet.
*
* Darüber hinaus wird eine Middleware installiert, die alle Requests umleitet,
* die mit einem Schrägstrich enden (und zwar jeweils auf das Pendant
* ohne Schrägstrich).
*
* @see http://www.slimframework.com/
* @see \Studip\ENV
* @see \JsonApi\Middlewares\RemoveTrailingSlashes
*/
class AppFactory
{
/**
* Diese Factory-Methode erstellt die Slim-Applikation und
* konfiguriert diese wie oben angegeben.
*
* @return \Slim\App die erstellte Slim-Applikation
*/
public function makeApp()
{
$app = new App();
$app = $this->configureContainer($app);
$app->add(new RemoveTrailingSlashes());
return $app;
}
// hier wird der Container konfiguriert
private function configureContainer($app)
{
$container = $app->getContainer();
$container['settings']['displayErrorDetails'] = defined('\\Studip\\ENV') && \Studip\ENV === 'development';
$container->register(new Providers\StudipConfig());
$container->register(new Providers\StudipServices());
return $app;
}
}
...@@ -28,6 +28,8 @@ interface JsonApiPlugin ...@@ -28,6 +28,8 @@ interface JsonApiPlugin
* *
* @param \Slim\App $app die Slim-Applikation, in der das Plugin * @param \Slim\App $app die Slim-Applikation, in der das Plugin
* Routen eintragen möchte * Routen eintragen möchte
*
* @return void
*/ */
public function registerAuthenticatedRoutes(\Slim\App $app); public function registerAuthenticatedRoutes(\Slim\App $app);
...@@ -51,6 +53,8 @@ interface JsonApiPlugin ...@@ -51,6 +53,8 @@ interface JsonApiPlugin
* *
* @param \Slim\App $app die Slim-Applikation, in der das Plugin * @param \Slim\App $app die Slim-Applikation, in der das Plugin
* Routen eintragen möchte * Routen eintragen möchte
*
* @return void
*/ */
public function registerUnauthenticatedRoutes(\Slim\App $app); public function registerUnauthenticatedRoutes(\Slim\App $app);
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
namespace JsonApi\Errors; namespace JsonApi\Errors;
use Neomerx\JsonApi\Document\Error; use Neomerx\JsonApi\Schema\Error;
use Neomerx\JsonApi\Exceptions\JsonApiException; use Neomerx\JsonApi\Exceptions\JsonApiException;
/** /**
...@@ -15,7 +15,7 @@ class AuthorizationFailedException extends JsonApiException ...@@ -15,7 +15,7 @@ class AuthorizationFailedException extends JsonApiException
*/ */
public function __construct() public function __construct()
{ {
$error = new Error('Forbidden', null, 403); $error = new Error(null, null, null, '403', null, 'Forbidden');
parent::__construct($error, 403); parent::__construct($error, 403);
} }
} }
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
namespace JsonApi\Errors; namespace JsonApi\Errors;
use Neomerx\JsonApi\Document\Error; use Neomerx\JsonApi\Schema\Error;
use Neomerx\JsonApi\Exceptions\JsonApiException; use Neomerx\JsonApi\Exceptions\JsonApiException;
/** /**
...@@ -15,7 +15,7 @@ class BadRequestException extends JsonApiException ...@@ -15,7 +15,7 @@ class BadRequestException extends JsonApiException
*/ */
public function __construct($detail = null, array $source = null) public function __construct($detail = null, array $source = null)
{ {
$error = new Error('Bad Request', null, 400, null, null, $detail, $source); $error = new Error(null, null, null, 400, null, 'Bad Request', $detail, $source);
parent::__construct($error, 400); parent::__construct($error, 400);
} }
} }
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
namespace JsonApi\Errors; namespace JsonApi\Errors;
use Neomerx\JsonApi\Document\Error; use Neomerx\JsonApi\Schema\Error;
use Neomerx\JsonApi\Exceptions\JsonApiException; use Neomerx\JsonApi\Exceptions\JsonApiException;
/** /**
...@@ -15,7 +15,7 @@ class ConflictException extends JsonApiException ...@@ -15,7 +15,7 @@ class ConflictException extends JsonApiException
*/ */
public function __construct($error = null) public function __construct($error = null)
{ {
$error = new Error($error ?: 'Conflict', null, 409); $errorObject = new Error(null, null, null, 409, null, 'Conflict', $error);
parent::__construct($error, 409); parent::__construct($errorObject, 409);
} }
} }
<?php
namespace JsonApi\Errors;
use Neomerx\JsonApi\Exceptions\JsonApiException;
use Neomerx\JsonApi\Schema\Error;
use Neomerx\JsonApi\Schema\ErrorCollection;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface;
use Slim\App;
use Slim\Exception\HttpException;
use Throwable;
class ErrorHandler
{
/** @var \Slim\App */
private $app;
public function __construct(App $app)
{
$this->app = $app;
}
public function __invoke(
ServerRequestInterface $request,
Throwable $exception,
bool $displayErrorDetails,
bool $logErrors,
bool $logErrorDetails,
?LoggerInterface $logger = null
): ResponseInterface {
if ($logger) {
$logger->error($exception->getMessage());
}
$response = $this->app->getResponseFactory()->createResponse();
$response->getBody()->write($this->determinePayload($exception, $displayErrorDetails));
$code = $this->determineStatusCode($exception, $request->getMethod());
return $response->withStatus($code);
}
protected function determineStatusCode(Throwable $exception, string $method): int
{
if ('OPTIONS' === $method) {
return 200;
}
if ($exception instanceof HttpException) {
return $exception->getCode();
}
if ($exception instanceof JsonApiException) {
return $exception->getHttpCode();
}
return 500;
}
protected function determinePayload(Throwable $exception, bool $displayErrorDetails): string
{
$message = '';
if ($exception instanceof JsonApiException) {
$httpCode = $exception->getHttpCode();
$errors = new ErrorCollection();
foreach ($exception->getErrors() as $error) {
$errors[] = $this->copyError($error, $displayErrorDetails, $exception);
}
} elseif ($exception instanceof HttpException) {
$errors = $this->createErrorCollection(
$exception->getCode(),
$exception->getMessage(),
$exception->getDescription(),
$exception,
$displayErrorDetails
);
} else {
$errors = $this->createErrorCollection(
'500',
$exception->getMessage(),
null,
$exception,
$displayErrorDetails
);
}
if (sizeof($errors)) {
$encoder = $this->app->getContainer()->get('json-api-error-encoder');
return $encoder->encodeErrors($errors);
}
return '';
}
private function createErrorCollection(
string $httpCode,
string $message,
?string $details,
Throwable $exception,
bool $displayErrorDetails
): ErrorCollection {
/** @var \Exception $exception */
$errors = new ErrorCollection();
$errors->add(
new Error(
null,
null,
null,
$httpCode,
null,
$message,
$details,
$displayErrorDetails ? ['backtrace' => explode("\n", $exception->getTraceAsString())] : null
)
);
return $errors;
}
private function copyError(Error $error, bool $displayErrorDetails, JsonApiException $exception): Error
{
$newError = new Error(
$error->getId(),
$error->getLinks(),
$error->getTypeLinks(),
$error->getStatus(),
$error->getCode(),
$error->getTitle(),
$error->getDetail(),
$displayErrorDetails ? ['backtrace' => explode("\n", $exception->getTraceAsString())] : null,
false,
$error->getMeta()
);
return $newError;
}
}
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
namespace JsonApi\Errors; namespace JsonApi\Errors;
use Neomerx\JsonApi\Document\Error; use Neomerx\JsonApi\Schema\Error;
use Neomerx\JsonApi\Exceptions\JsonApiException; use Neomerx\JsonApi\Exceptions\JsonApiException;
/** /**
...@@ -15,7 +15,7 @@ class HttpRangeException extends JsonApiException ...@@ -15,7 +15,7 @@ class HttpRangeException extends JsonApiException
*/ */
public function __construct($error = null) public function __construct($error = null)
{ {
$error = new Error($error ?: 'Requested Range Not Satisfiable.', null, 416); $errorObject = new Error(null, null, null, 416, null, 'Requested Range Not Satisfiable.', $error);
parent::__construct($error, 416); parent::__construct($errorObject, 416);
} }
} }
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
namespace JsonApi\Errors; namespace JsonApi\Errors;
use Neomerx\JsonApi\Document\Error; use Neomerx\JsonApi\Schema\Error;
use Neomerx\JsonApi\Exceptions\JsonApiException; use Neomerx\JsonApi\Exceptions\JsonApiException;
/** /**
...@@ -15,7 +15,7 @@ class InternalServerError extends JsonApiException ...@@ -15,7 +15,7 @@ class InternalServerError extends JsonApiException
*/ */
public function __construct($detail = null, array $source = null) public function __construct($detail = null, array $source = null)
{ {
$error = new Error('Internal Server Error', null, 500, null, null, $detail, $source); $error = new Error(null, null, null, 500, null, 'Internal Server Error', $detail, $source);
parent::__construct($error, 500); parent::__construct($error, 500);
} }
} }
<?php
namespace JsonApi\Errors;
use JsonApi\Providers\JsonApiConfig as C;
use Neomerx\JsonApi\Contracts\Factories\FactoryInterface;
use Neomerx\JsonApi\Contracts\Http\Headers\MediaTypeInterface;
use Neomerx\JsonApi\Contracts\Schema\ContainerInterface as SC;
use Neomerx\JsonApi\Document\Error;
use Neomerx\JsonApi\Encoder\EncoderOptions;
use Neomerx\JsonApi\Exceptions\ErrorCollection;
use Neomerx\JsonApi\Exceptions\JsonApiException;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
/**
* Dieser spezielle Exception Handler wird in der Slim-Applikation
* für alle JSON-API-Routen installiert und sorgt dafür, dass auch
* evtl. Fehler JSON-API-kompatibel geliefert werden.
*/
class JsonApiExceptionHandler
{
private $previous;
private $container;
/**
* Der Konstruktor...
*
* @param ContainerInterface $container der Dependency Container,
* der in der Slim-Applikation verwendet wird
* @param callable $previous der zuvor installierte `Error
* Handler` als Fallback
*/
public function __construct(ContainerInterface $container, $previous = null)
{
$this->previous = $previous;
$this->container = $container;
}
/**
* Diese Methode wird aufgerufen, sobald es zu einer Exception
* kam, und generiert eine entsprechende JSON-API-spezifische Response.
*
* @param Request $request der eingehende Request
* @param Response $response die vorbereitete ausgehende Response
* @param \Exception $exception die aufgetretene Exception
*
* @return Response die JSON-API-kompatible Response
*/
public function __invoke(Request $request, Response $response, \Exception $exception)
{
if ($exception instanceof JsonApiException) {
$httpCode = $exception->getHttpCode();
$errors = $exception->getErrors();
} else {
$httpCode = 500;
$details = null;
$debugEnabled = \Studip\ENV === 'development';
if ($debugEnabled === true) {
$message = $exception->getMessage();
$details = (string) $exception;
}
$errors = new ErrorCollection();
$errors->add(new Error(null, null, $httpCode, null, $message, $details));
}
if (sizeof($errors)) {
$encoder = $this->createEncoder();
$response = $response
->withHeader(
'Content-Type',
sprintf('%s/%s',
MediaTypeInterface::JSON_API_TYPE,
MediaTypeInterface::JSON_API_SUB_TYPE
)
)
->write($encoder->encodeErrors($errors));
}
return $response->withStatus($httpCode);
}
private function createEncoder()
{
$factory = $this->container[FactoryInterface::class];
$schemaContainer = $this->container[SC::class];
$urlPrefix = $this->container[C::JSON_URL_PREFIX];
$encoderOptions = new EncoderOptions(0, $urlPrefix);
return $factory->createEncoder($schemaContainer, $encoderOptions);
}
}
<?php
namespace JsonApi\Errors;
use Neomerx\JsonApi\Schema\Error;
use Neomerx\JsonApi\Exceptions\JsonApiException;
class NotAcceptableException extends JsonApiException
{
public function __construct()
{
$error = new Error(null, null, null, '406', null, 'Not Acceptable Error');
parent::__construct($error, 406);
}
}
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
namespace JsonApi\Errors; namespace JsonApi\Errors;
use Neomerx\JsonApi\Document\Error; use Neomerx\JsonApi\Schema\Error;
use Neomerx\JsonApi\Exceptions\JsonApiException; use Neomerx\JsonApi\Exceptions\JsonApiException;
/** /**
...@@ -15,7 +15,7 @@ class NotImplementedException extends JsonApiException ...@@ -15,7 +15,7 @@ class NotImplementedException extends JsonApiException
*/ */
public function __construct($detail = null, array $source = null) public function __construct($detail = null, array $source = null)
{ {
$error = new Error('Not Implemented Error', null, 501, null, null, $detail, $source); $error = new Error(null, null, null, 501, null, 'Not Implemented Error', $detail, $source);
parent::__construct($error, 501); parent::__construct($error, 501);
} }
} }
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
namespace JsonApi\Errors; namespace JsonApi\Errors;
use Neomerx\JsonApi\Document\Error; use Neomerx\JsonApi\Schema\Error;
use Neomerx\JsonApi\Exceptions\JsonApiException; use Neomerx\JsonApi\Exceptions\JsonApiException;
/** /**
...@@ -15,7 +15,7 @@ class RecordNotFoundException extends JsonApiException ...@@ -15,7 +15,7 @@ class RecordNotFoundException extends JsonApiException
*/ */
public function __construct($error = null) public function __construct($error = null)
{ {
$error = new Error($error ?: 'Not Found', null, 404); $errorObject = new Error(null, null, null, 404, null, 'Not Found', $error);
parent::__construct($error, 404); parent::__construct($errorObject, 404);
} }
} }
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
namespace JsonApi\Errors; namespace JsonApi\Errors;
use Neomerx\JsonApi\Document\Error; use Neomerx\JsonApi\Schema\Error;
use Neomerx\JsonApi\Exceptions\JsonApiException; use Neomerx\JsonApi\Exceptions\JsonApiException;
/** /**
...@@ -15,7 +15,7 @@ class UnprocessableEntityException extends JsonApiException ...@@ -15,7 +15,7 @@ class UnprocessableEntityException extends JsonApiException
*/ */
public function __construct($detail = null, array $source = null) public function __construct($detail = null, array $source = null)
{ {
$error = new Error('Unprocesssable Entity', null, 422, null, null, $detail, $source); $error = new Error(null, null, null, 422, null, 'Unprocesssable Entity', $detail, $source);
parent::__construct($error, 422); parent::__construct($error, 422);
} }
} }
<?php
namespace JsonApi\Errors;
use Neomerx\JsonApi\Schema\Error;
use Neomerx\JsonApi\Exceptions\JsonApiException;
class UnsupportedMediaTypeException extends JsonApiException
{
public function __construct()
{
$error = new Error(null, null, null, '415', null, 'Unsupported Media Type Error');
parent::__construct($error, 415);
}
}
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
namespace JsonApi\Errors; namespace JsonApi\Errors;
use Neomerx\JsonApi\Document\Error; use Neomerx\JsonApi\Schema\Error;
use Neomerx\JsonApi\Exceptions\JsonApiException; use Neomerx\JsonApi\Exceptions\JsonApiException;
/** /**
...@@ -15,7 +15,7 @@ class UnsupportedRequestError extends JsonApiException ...@@ -15,7 +15,7 @@ class UnsupportedRequestError extends JsonApiException
*/ */
public function __construct($detail = null, array $source = null) public function __construct($detail = null, array $source = null)
{ {
$error = new Error('Unsupported request.', null, 403, null, null, $detail, $source); $error = new Error(null, null, null, 403, null, 'Unsupported request.', $detail, $source);
parent::__construct($error, 403); parent::__construct($error, 403);
} }
} }
...@@ -3,10 +3,20 @@ ...@@ -3,10 +3,20 @@
namespace JsonApi; namespace JsonApi;
use JsonApi\JsonApiIntegration\JsonApiTrait; use JsonApi\JsonApiIntegration\JsonApiTrait;
use JsonApi\JsonApiIntegration\QueryParserInterface;
use JsonApi\Middlewares\Authentication;
use Neomerx\JsonApi\Contracts\Encoder\EncoderInterface;
use Neomerx\JsonApi\Contracts\Factories\FactoryInterface;
use Neomerx\JsonApi\Contracts\Http\Headers\HeaderParametersParserInterface;
use Neomerx\JsonApi\Contracts\Http\Headers\MediaTypeInterface;
use Neomerx\JsonApi\Contracts\Http\ResponsesInterface;
use Neomerx\JsonApi\Contracts\Schema\SchemaContainerInterface;
use Neomerx\JsonApi\Contracts\Schema\SchemaInterface;
use Neomerx\JsonApi\Http\Headers\MediaType;
use Neomerx\JsonApi\Schema\Link;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Neomerx\JsonApi\Contracts\Http\Headers\HeadersCheckerInterface; use Psr\Http\Message\ServerRequestInterface as Request;
use Neomerx\JsonApi\Contracts\Http\Headers\HeaderParametersInterface;
/** /**
* Ein JsonApiController ist die einfachste Möglichkeit, eine eigene * Ein JsonApiController ist die einfachste Möglichkeit, eine eigene
...@@ -28,19 +38,426 @@ use Neomerx\JsonApi\Contracts\Http\Headers\HeaderParametersInterface; ...@@ -28,19 +38,426 @@ use Neomerx\JsonApi\Contracts\Http\Headers\HeaderParametersInterface;
*/ */
class JsonApiController class JsonApiController
{ {
use JsonApiTrait; /**
* @var \Slim\App
*/
protected $app;
/**
* @var ContainerInterface;
*/
protected $container;
/**
* @var FactoryInterface
*/
protected $factory;
/**
* @var EncoderInterface
*/
protected $encoder;
/**
* @var SchemaContainerInterface
*/
protected $schemaContainer;
/**
* @var QueryParserInterface
*/
protected $queryParser;
/** /**
* Der Konstruktor. * Der Konstruktor.
*/
public function __construct(
\Slim\App $app,
ContainerInterface $container,
FactoryInterface $factory,
EncoderInterface $encoder,
SchemaContainerInterface $schemaContainer,
QueryParserInterface $queryParser,
HeaderParametersParserInterface $headerParametersParser
) {
$this->app = $app;
$this->container = $container;
$this->factory = $factory;
$this->encoder = $encoder;
$this->schemaContainer = $schemaContainer;
$this->queryParser = $queryParser;
$queryChecker = new JsonApiIntegration\QueryChecker(
$this->allowUnrecognizedParams,
$this->allowedIncludePaths,
$this->allowedFieldSetTypes,
$this->allowedSortFields,
$this->allowedPagingParameters,
$this->allowedFilteringParameters
);
$queryChecker->checkQuery($queryParser);
$this->checkAcceptHeader($headerParametersParser);
$this->checkContentTypeHeader($headerParametersParser);
}
/**
* If unrecognized parameters should be allowed in input parameters.
* *
* @param ContainerInterface $container der Dependency Container * @var bool
*/ */
public function __construct(ContainerInterface $container) protected $allowUnrecognizedParams = false;
/**
* A list of allowed include paths in input parameters.
*
* Empty array [] means clients are not allowed to specify include paths and 'null' means all paths are allowed.
*
* @var string[]|null
*/
protected $allowedIncludePaths = [];
/**
* A list of JSON API types which clients can sent field sets to.
*
* Possible values
*
* $allowedFieldSetTypes = null; // <-- for all types all fields are allowed
*
* $allowedFieldSetTypes = []; // <-- non of the types and fields are allowed
*
* $allowedFieldSetTypes = [
* 'people' => null, // <-- all fields for 'people' are allowed
* 'comments' => [], // <-- no fields for 'comments' are allowed (all denied)
* 'posts' => ['title', 'body'], // <-- only 'title' and 'body' fields are allowed for 'posts'
* ];
*
* @var string[]|null
*/
protected $allowedFieldSetTypes = null;
/**
* A list of allowed sort field names in input parameters.
*
* Empty array [] means clients are not allowed to specify sort fields and 'null' means all fields are allowed.
*
* @var string[]|null
*/
protected $allowedSortFields = [];
/**
* A list of allowed pagination input parameters (e.g 'number', 'size', 'offset' and etc).
*
* Empty array [] means clients are not allowed to specify paging and 'null' means all parameters are allowed.
*
* @var string[]|null
*/
protected $allowedPagingParameters = [];
/**
* A list of allowed filtering input parameters.
*
* Empty array [] means clients are not allowed to specify filtering and 'null' means all parameters are allowed.
*
* @var string[]|null
*/
protected $allowedFilteringParameters = [];
// ***** RESPONSE GENERATORS *****
/**
* Get response with HTTP code only.
*/
protected function getCodeResponse(int $statusCode, array $headers = []): Response
{ {
$this->container = $container; $responses = $this->getResponses();
$this->initJsonApiSupport($container);
return $responses->getCodeResponse($statusCode, $headers);
}
/**
* Get response with meta information only.
*
* @param array|object $meta Meta information
* @param int $statusCode
*/
protected function getMetaResponse($meta, $statusCode = ResponsesInterface::HTTP_OK, array $headers = []): Response
{
$responses = $this->getResponses();
return $responses->getMetaResponse($meta, $statusCode, $headers);
}
/**
* Get response with regular JSON API Document in body.
*
* @param object|array $data
* @param int $statusCode
* @param array|null $links
* @param mixed $meta
*/
protected function getContentResponse(
$data,
$statusCode = ResponsesInterface::HTTP_OK,
$links = [],
$meta = [],
array $headers = []
): Response {
$responses = $this->getResponses($links, $meta);
return $responses->getContentResponse($data, $statusCode, $headers);
}
/**
* Get response with only resource identifiers.
*
* @param object|array $data
* @param array|null $links
* @param mixed $meta
*/
protected function getIdentifiersResponse($data, $links = [], $meta = [], array $headers = []): Response
{
$responses = $this->getResponses($links, $meta);
$statusCode = ResponsesInterface::HTTP_OK;
return $responses->getIdentifiersResponse($data, $statusCode, $headers);
}
/**
* Get response with paginated resource identifiers.
*
* @param object|array $data
* @param ?int $total
* @param array|null $links
* @param mixed $meta
*/
protected function getPaginatedIdentifiersResponse(
$data,
$total,
$links = [],
$meta = [],
array $headers = []
): Response {
list($offset, $limit) = $this->getOffsetAndLimit();
$meta['page'] = [
'offset' => (int) $offset,
'limit' => (int) $limit,
];
if (isset($total)) {
$meta['page']['total'] = (int) $total;
}
$paginator = new JsonApiIntegration\Paginator($total, $offset, $limit);
foreach (words('first last prev next') as $rel) {
if (list($off, $lim) = $paginator->{'get'.ucfirst($rel).'PageOffsetAndLimit'}()) {
$links[$rel] = $this->createLink($off, $lim);
}
}
$responses = $this->getResponses($links, $meta);
$statusCode = ResponsesInterface::HTTP_OK;
return $responses->getIdentifiersResponse($data, $statusCode, $headers);
}
/**
* @param object $resource
* @param array|null $links
* @param mixed $meta
*/
protected function getCreatedResponse($resource, $links = [], $meta = [], array $headers = []): Response
{
$responses = $this->getResponses($links, $meta);
$urlPrefix = $this->container->get('json-api-integration-urlPrefix');
$url = $this->schemaContainer
->getSchema($resource)
->getSelfLink($resource)
->getStringRepresentation($urlPrefix);
return $responses->getCreatedResponse($resource, $url, $headers);
}
/**
* @param object|array $data
* @param ?int $total
* @param int $statusCode
* @param array|null $links
* @param mixed $meta
*/
protected function getPaginatedContentResponse(
$data,
$total,
$statusCode = ResponsesInterface::HTTP_OK,
$links = [],
$meta = [],
array $headers = []
): Response {
list($offset, $limit) = $this->getOffsetAndLimit();
$meta['page'] = [
'offset' => (int) $offset,
'limit' => (int) $limit,
];
if (isset($total)) {
$meta['page']['total'] = (int) $total;
}
$paginator = new JsonApiIntegration\Paginator($total, $offset, $limit);
foreach (words('first last prev next') as $rel) {
if (list($off, $lim) = $paginator->{'get'.ucfirst($rel).'PageOffsetAndLimit'}()) {
$links[$rel] = $this->createLink($off, $lim);
}
}
$responses = $this->getResponses($links, $meta);
return $responses->getContentResponse($data, $statusCode, $headers);
}
protected function getQueryParameters(): QueryParserInterface
{
return $this->queryParser;
}
/**
* Liefert Offset und Limit aus den Request-Parametern zurück.
*
* @param int $offsetDefault optional; gibt den Standard-Offset
* an, falls dieser Wert nicht im Request gesetzt ist
* @param int $limitDefault optional; gibt das Standard-Limit an,
* falls dieser Wert nicht im Request gesetzt ist
*
* @return array<int> {
*
* @var int $offset der im Request gesetzte Offset oder
* ansonsten der Default-Wert 0
* @var int $limit das im Request gesetzte Limit oder
* ansonsten der Default-Wert 30
* }
*/
protected function getOffsetAndLimit($offsetDefault = 0, $limitDefault = 30): array
{
$params = iterator_to_array($this->queryParser->getPagination());
return [
$params && array_key_exists('offset', $params) ? (int) $params['offset'] : $offsetDefault,
$params && array_key_exists('limit', $params) ? (int) $params['limit'] : $limitDefault,
];
}
// Hier wird der aktuelle Link zusätzlich noch mit Paginierung ausgestattet
private function createLink(int $offset, int $limit): Link
{
$request = $this->container->get('request');
$queryParams = $request->getQueryParams();
$queryParams['page']['offset'] = $offset;
$queryParams['page']['limit'] = $limit;
$uri = $request->getUri()->withQuery(http_build_query($queryParams));
$path = $uri->getPath();
$query = $uri->getQuery();
$fragment = $uri->getFragment();
$uriString = $path.($query ? '?'.$query : '').($fragment ? '#'.$fragment : '');
return new Link(false, $uriString, false);
}
/**
* Gibt null oder das User-Objekt des "eingeloggten" Nutzers zurück.
*
* @param Request $request Request der eingehende Request
*
* @return mixed entweder null oder das User-Objekt des
* "eingeloggten" Nutzers
*/
public function getUser(Request $request)
{
return $request->getAttribute(Authentication::USER_KEY, null);
}
/**
* Gibt das Schema zu einer beliebigen Ressource zurück.
*
* @param mixed $resource die Ressource, zu der das Schema geliefert werden soll
*
* @return SchemaInterface das Schema zur Ressource
*/
protected function getSchema($resource): SchemaInterface
{
return $this->schemaContainer->getSchema($resource);
}
protected function getResponses(array $links = [], array $meta = []): ResponsesInterface
{
$paths = $this->queryParser->getIncludePaths();
$fieldSets = iterator_to_array($this->queryParser->getFields());
$encoder = $this->encoder
->withIncludedPaths($paths)
->withFieldSets($fieldSets)
->withLinks($links);
if (count($meta)) {
$encoder = $encoder->withMeta($meta);
}
$mediaType = new MediaType(MediaTypeInterface::JSON_API_TYPE, MediaTypeInterface::JSON_API_SUB_TYPE);
return new JsonApiIntegration\Responses($encoder, $mediaType);
}
private function checkAcceptHeader(HeaderParametersParserInterface $headerParametersParser): void
{
$request = $this->container->get('request');
$accept = $request->getHeader(HeaderParametersParserInterface::HEADER_ACCEPT);
if (count($accept)) {
$mediaType = $this->factory->createMediaType(
MediaTypeInterface::JSON_API_TYPE,
MediaTypeInterface::JSON_API_SUB_TYPE
);
foreach ($headerParametersParser->parseAcceptHeader($accept[0]) as $acceptMediaType) {
if ($mediaType->matchesTo($acceptMediaType)) {
return;
}
}
}
throw new Errors\NotAcceptableException();
}
private function checkContentTypeHeader(HeaderParametersParserInterface $headerParametersParser): void
{
$request = $this->container->get('request');
if ($this->doesRequestHaveBody($request)) {
$contentType = $request->getHeader(HeaderParametersParserInterface::HEADER_CONTENT_TYPE);
if (count($contentType)) {
$mediaType = $this->factory->createMediaType(
MediaTypeInterface::JSON_API_TYPE,
MediaTypeInterface::JSON_API_SUB_TYPE
);
$parsedContentType = $headerParametersParser->parseContentTypeHeader($contentType[0]);
if ($mediaType->matchesTo($parsedContentType)) {
return;
}
}
throw new Errors\UnsupportedMediaTypeException();
}
}
private function doesRequestHaveBody(Request $request): bool
{
if (count($request->getHeader('Transfer-Encoding'))) {
return true;
}
$contentLength = $request->getHeader('Content-Length');
$headerChecker = $this->container[HeadersCheckerInterface::class]; return count($contentLength) && $contentLength[0] > 0;
$headerChecker->checkHeaders($this->container[HeaderParametersInterface::class]);
} }
} }
<?php
namespace JsonApi\JsonApiIntegration;
class Container extends \Neomerx\JsonApi\Schema\Container
{
/**
* Überprüft nicht nur, ob es ein mapping zum $type gibt, sondern
* sucht sonst auch nach mappings der Oberklassen bzw. Interfaces.
*
* @param string $type
*
* @return bool
*/
protected function hasProviderMapping($type)
{
if (parent::hasProviderMapping($type)) {
return true;
}
foreach ($this->getParentClassesAndInterfaces($type) as $class) {
if (parent::hasProviderMapping($class)) {
return true;
}
}
return false;
}
/**
* Liefert nicht nur direkte Treffer aus den provider mappings
* sondern schaut falls nötig auch nach Oberklassen bzw. Interfaces.
*
* @param string $type
*
* @return mixed
*/
protected function getProviderMapping($type)
{
$providerMapping = $this->getProviderMappings();
if (isset($providerMapping[$type])) {
return $providerMapping[$type];
}
foreach ($this->getParentClassesAndInterfaces($type) as $class) {
if (isset($providerMapping[$class])) {
return $providerMapping[$class];
}
}
return null;
}
private function getParentClassesAndInterfaces($type)
{
return class_exists($type) ? @class_parents($type) + @class_implements($type) : [];
}
/**
* @deprecated use `createSchemaFromCallable` method instead
*
* @param Closure $closure
*
* @return SchemaProviderInterface
*/
protected function createSchemaFromClosure(\Closure $closure)
{
$schema = $closure($this->getFactory(), $this);
return $schema;
}
/**
* @param callable $callable
*
* @return SchemaProviderInterface
*/
protected function createSchemaFromCallable(callable $callable)
{
$schema = $callable instanceof \Closure ?
$this->createSchemaFromClosure($callable) : call_user_func($callable, $this->getFactory(), $this);
return $schema;
}
/**
* @param string $className
*
* @return SchemaProviderInterface
*/
protected function createSchemaFromClassName($className)
{
$schema = new $className($this->getFactory(), $this);
return $schema;
}
}
...@@ -2,9 +2,12 @@ ...@@ -2,9 +2,12 @@
namespace JsonApi\JsonApiIntegration; namespace JsonApi\JsonApiIntegration;
use Neomerx\JsonApi\Contracts\Parser\EditableContextInterface;
use Neomerx\JsonApi\Contracts\Parser\ParserInterface;
use Neomerx\JsonApi\Contracts\Schema\LinkInterface;
use Neomerx\JsonApi\Contracts\Schema\SchemaContainerInterface;
use Neomerx\JsonApi\Factories\Factory as NeomerxFactory; use Neomerx\JsonApi\Factories\Factory as NeomerxFactory;
use Neomerx\JsonApi\Contracts\Schema\ContainerInterface; use Neomerx\JsonApi\Schema\Link;
use Neomerx\JsonApi\Contracts\Encoder\Parser\ParserManagerInterface;
/** /**
* Die "normale" \Neomerx\JsonApi\Factories\Factory stellt in * Die "normale" \Neomerx\JsonApi\Factories\Factory stellt in
...@@ -19,38 +22,21 @@ use Neomerx\JsonApi\Contracts\Encoder\Parser\ParserManagerInterface; ...@@ -19,38 +22,21 @@ use Neomerx\JsonApi\Contracts\Encoder\Parser\ParserManagerInterface;
*/ */
class Factory extends NeomerxFactory class Factory extends NeomerxFactory
{ {
private $diContainer;
public function setDependencyInjectionContainer(\Psr\Container\ContainerInterface $diContainer)
{
$this->diContainer = $diContainer;
}
public function getDependencyInjectionContainer()
{
return $this->diContainer;
}
/** /**
* {@inheritdoc} * @inheritdoc
*/ */
public function createContainer(array $providers = []) public function createParser(
{ SchemaContainerInterface $container,
$container = new Container($this, $providers); EditableContextInterface $context
): ParserInterface {
$container->setLogger($this->logger); return new Parser($this, $container, $context);
return $container;
} }
/** /**
* {@inheritdoc} * @inheritdoc
*/ */
public function createParser(ContainerInterface $container, ParserManagerInterface $manager) public function createSchemaContainer(iterable $schemas): SchemaContainerInterface
{ {
$parser = new EncoderParser($this, $this, $this, $container, $manager); return new SchemaContainer($this, $schemas);
$parser->setLogger($this->logger);
return $parser;
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment