Skip to content

Commit

Permalink
Combine all apitte packages to contributte/apitte
Browse files Browse the repository at this point in the history
  • Loading branch information
f3l1x committed Nov 10, 2021
1 parent 0a2f43c commit fdb190d
Show file tree
Hide file tree
Showing 297 changed files with 19,864 additions and 100 deletions.
14 changes: 11 additions & 3 deletions .docs/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
# Apitte Core
# Apitte

Core library of Apitte API framework
An opinionated and enjoyable API framework based on Nette Framework. Supporting content negotiation, debugging, middlewares, attributes, annotations and loving openapi/swagger.

Need to start with Apitte
- [Setup](index.md)
- [Endpoints](endpoints.md)
- [Mapping](mapping.md)

Plugins
- [Console](console.md)
- [Debug](debug.md)
- [Middlewares](middlewares.md)
- [Negotiation](negotiation.md)
- [OpenApi](openapi.md)
- [Presenter](presenter.md)

This knowledge could make your life easier
- [Architecture](architecture.md)
- [Decorators](decorators.md)
Expand All @@ -17,5 +25,5 @@ This knowledge could make your life easier
- [Schema](schema.md)

Examples
- https://github.com/contributte/playground (playground)
- https://github.com/contributte/apitte-skeleton (skeleton project)
- https://contributte.org/examples.html (more examples)
30 changes: 30 additions & 0 deletions .docs/console.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Console

Console commands for Apitte.

## Content

- [Setup](#setup)
- [Commands](#commands)

## Setup

Install and register console plugin.

```neon
api:
plugins:
Apitte\Console\DI\ConsolePlugin:
```

You also need setup an integration of [symfony/console](https://symfony.com/doc/current/components/console.html), try [contributte/console](https://github.com/contributte/console/)

## Commands

### Route dump

List all endpoints and their details

```bash
php bin/console apitte:route:dump
```
37 changes: 37 additions & 0 deletions .docs/debug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Debug

Debug tools for Apitte, based on [Tracy debugger](https://github.com/nette/tracy).

## Setup

Install and register debug plugin.

```neon
api:
plugins:
Apitte\Debug\DI\DebugPlugin:
debug:
panel: %debugMode%
negotiation: %debugMode%
```

## Tracy

- bar panel - displays all router
- blue screen panel - displays endpoint with invalid schema

## Negotiation

If you have [negotiation](./negotiation.md) plugin installed then you will be able to use two new suffixes.

With these suffixes you will also be able to see **Tracy bar**

`.debug`

- dumps response
- `example.com/api/v1/users.debug`

`.debugdata`

- dumps response entity
- `example.com/api/v1/users.debugdata`
67 changes: 67 additions & 0 deletions .docs/middlewares.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Middlewares

Middlewares for Apitte.

Transform and validate request or early return response before it is handled by dispatcher.

## Setup

Install and register middlewares plugin.

```neon
api:
plugins:
Apitte\Middlewares\DI\MiddlewaresPlugin:
```

In `index.php` replace `Apitte\Core\Application\IApplication` with `Contributte\Middlewares\Application\IApplication`.

## Configuration

[TracyMiddleware](https://github.com/contributte/middlewares/blob/master/.docs/README.md#tracymiddleware) (with priority 100)
and [AutoBasePathMiddleware](https://github.com/contributte/middlewares/blob/master/.docs/README.md#autobasepathmiddleware) (with priority 200)
are registered by default, but you could disable them if you want.

```neon
api:
plugins:
Apitte\Middlewares\DI\MiddlewaresPlugin:
tracy: true
autobasepath: true
```

`Apitte\Middlewares\ApiMiddleware` which run whole Apitte application is registered with priority 500. Make sure there is no middleware with higher priority.

## Middlewares

If you want to add another middleware, just register a class with appropriate tags.

```neon
services:
m1:
factory: App\Api\Middleware\ExampleMiddleware
tags: [middleware: [priority: 10]]
```

```php
namespace App\Api\Middleware;

use Contributte\Middlewares\IMiddleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

class ExampleMiddleware implements IMiddleware
{

public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface
{
// Call next middleware in a row
$response = $next($request, $response);
// Return response
return $response;
}

}
```

See [contributte/middlewares](https://github.com/contributte/middlewares) documentation for more info and useful middlewares
Binary file added .docs/misc/tracy-panel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
189 changes: 189 additions & 0 deletions .docs/negotiation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# Negotiation

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

Install and register negotiation plugin.

```neon
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.

```php
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' => '[email protected]',
],
[
'id' => 2,
'firstName' => 'Elon',
'lastName' => 'Musk',
'emailAddress' => '[email protected]',
],
]);

return $response
->withStatus(ApiResponse::S200_OK)
->withEntity($entity);
}

}
```

## Entities

Value objects which are used to create response

- `ArrayEntity` - create from array
- `ObjectEntity` - create from stdClass
- `ScalarEntity` - 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 for `json` suffix 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

```neon
api:
plugins:
Apitte\Negotiation\DI\NegotiationPlugin:
unification: true
```

`CsvTransformer`

- transform into csv
- known limitation: data need to be a flat structure

#### Implementing transformer

```neon
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: true` is defined and none of transformers matched then use that transformer

```php
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,
]));
}

}
```
Loading

0 comments on commit fdb190d

Please sign in to comment.