apitte / negotiation
Content-Negotiation support for Apitte stack
Fund package maintenance!
f3l1x
contributte.org/partners.html
Installs: 256 300
Dependents: 3
Suggesters: 0
Security: 0
Stars: 5
Watchers: 3
Forks: 2
Open Issues: 0
pkg:composer/apitte/negotiation
Requires
- php: >=7.3
- apitte/core: ~0.7 || ~0.8
Requires (Dev)
- nette/di: ^3.0.0
- ninjify/nunjuck: ^0.4
- ninjify/qa: ^0.12
- phpstan/phpstan: ^0.12
- phpstan/phpstan-deprecation-rules: ^0.12
- phpstan/phpstan-nette: ^0.12
- phpstan/phpstan-strict-rules: ^0.12
README
Website 🚀 contributte.org | Contact 👨🏻💻 f3l1x.io | Twitter 🐦 @contributte
Disclaimer
| ⚠️ | This project is no longer being maintained. Please use contributte/apitte. |
|---|---|
| Composer | apitte/negotiation |
| Version | |
| PHP | |
| License |
Usage
To install the latest version of apitte/negotiation use Composer.
composer require apitte/negotiation
Documentation
Content negotiation for Apitte.
Transform response entity into response with unified format in dependence on Accept header and uri path suffix /api/v1/users(.json|.xml)
Setup
First of all, setup core package and enable CoreDecoratorPlugin.
Install and register negotiation plugin
composer require apitte/negotiation
api: plugins: Apitte\Negotiation\DI\NegotiationPlugin:
Response
Instead of writing data into response body use $response->withEntity($entity) so transformers could handle transformation for you.
namespace App\Api\V1\Controllers; use Apitte\Core\Annotation\Controller\ControllerPath; use Apitte\Core\Annotation\Controller\Method; use Apitte\Core\Annotation\Controller\Path; use Apitte\Core\Http\ApiRequest; use Apitte\Core\Http\ApiResponse; use Apitte\Negotiation\Http\ArrayEntity; /** * @ControllerPath("/users") */ class UsersController extends BaseV1Controller { /** * @Path("/") * @Method("GET") */ public function index(ApiRequest $request, ApiResponse $response): ApiResponse { $entity = ArrayEntity::from([ [ 'id' => 1, 'firstName' => 'John', 'lastName' => 'Doe', 'emailAddress' => 'john@doe.com', ], [ 'id' => 2, 'firstName' => 'Elon', 'lastName' => 'Musk', 'emailAddress' => 'elon.musk@spacex.com', ], ]); return $response ->withStatus(ApiResponse::S200_OK) ->withEntity($entity); } }
Entities
Value objects which are used to create response
ArrayEntity- create from arrayObjectEntity- create from stdClassScalarEntity- create from raw data
Error handling
Negotiations are implemented through an IErrorDecorator, which have higher priority than internal ErrorHandler
so response is created from exception in an ITransformer and ErrorHandler only log that exception (if you use PsrLogErrorHandler)
Negotiators
Handle request and based on path suffix or request headers call appropriate transformer.
SuffixNegotiator
- used for request with path suffix like
/api/v1/users.json-> transformer forjsonsuffix is used
DefaultNegotiator
- called when none other transform
- require annotation
@Negotiation(default = true, suffix = "json")defined on endpoint - transformer for given suffix is looked for
FallbackNegotiator
- used last if no other negotiator transformed response
- uses json transformer by default
Transformers
Transformers convert entities and exceptions into response.
JsonTransformer
- transform into json
JsonUnifyTransformer
- transform into json with unified format
api: plugins: Apitte\Negotiation\DI\NegotiationPlugin: unification: true
CsvTransformer
- transform into csv
- known limitation: data need to be a flat structure
Implementing transformer
services: - factory: App\Api\Transformer\XmlTransformer tags: [apitte.negotiator.transformer: [suffix: xml, fallback: true]]
- register transformer for suffix
xml, used for uris like/api/v1/users.xml - if
fallback: trueis defined and none of transformers matched then use that transformer
namespace App\Api\Transformer; use Apitte\Core\Exception\ApiException; use Apitte\Core\Http\ApiRequest; use Apitte\Core\Http\ApiResponse; use Apitte\Core\Http\ResponseAttributes; use Apitte\Negotiation\Http\ArrayEntity; use Apitte\Negotiation\Transformer\AbstractTransformer; use Throwable; class XmlTransformer extends AbstractTransformer { /** * Encode given data for response * * @param mixed[] $context */ public function transform(ApiRequest $request, ApiResponse $response, array $context = []) : ApiResponse { if (isset($context['exception'])) { return $this->transformError($context['exception'], $request, $response); } return $this->transformResponse($request, $response); } protected function transformResponse(ApiRequest $request, ApiResponse $response): ApiResponse { $data = $this->getEntity($response)->getData(); $content = $this->dataToXmlString($data); $response->getBody()->write($content); return $response ->withHeader('Content-Type', 'application/xml'); } protected function transformError(Throwable $error, ApiRequest $request, ApiResponse $response): ApiResponse { if ($error instanceof ApiException) { $code = $error->getCode(); $message = $error->getMessage(); } else { $code = 500; $message = 'Application encountered an internal error. Please try again later.'; } return $response ->withStatus($code) ->withAttribute(ResponseAttributes::ATTR_ENTITY, ArrayEntity::from([ 'status' => 'error', 'message' => $message, ])); } }
Version
| State | Version | Branch | Nette | PHP |
|---|---|---|---|---|
| stable | ^0.8 |
master |
3.0+ | >=7.3 |
| stable | ^0.5 |
master |
2.4 | >=7.1 |
Development
This package was maintained by these authors.
Consider to support contributte development team. Also thank you for being used this package.