Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/larttyler/php-api-common
Common code shared by REST API projects
https://github.com/larttyler/php-api-common
Last synced: 4 days ago
JSON representation
Common code shared by REST API projects
- Host: GitHub
- URL: https://github.com/larttyler/php-api-common
- Owner: LartTyler
- License: gpl-3.0
- Created: 2019-11-26T19:13:24.000Z (almost 5 years ago)
- Default Branch: main
- Last Pushed: 2023-08-11T19:30:50.000Z (over 1 year ago)
- Last Synced: 2024-04-12T19:24:23.316Z (7 months ago)
- Language: PHP
- Size: 179 KB
- Stars: 2
- Watchers: 1
- Forks: 1
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
This package contains components that tend to be useful across different REST API projects.
This package is broken up into several parts:
- [Common](#common) code, files that are always useful.
- [Validation](#validation) code, files that are useful in tandem with the `symfony/validator` component.
- [Doctrine Query Document](#doctrine-query-document) code, files that are useful in tandem with the
`dbstudios/doctrine-query-document` library.
- [Lexik JWT](#lexik-jwt) code, files that are useful in tandem with the `lexik/jwt-authentication-bundle` bundle.
- [Payload](#payload) code, files that are useful in tandem with the `symfony/serializer` component's deserialize
functionality.# Common
Common files include the `ResponderInterface` and it's implementations, as well as the classes in the top level of the
[`src/Error/Errors`](src/Error/Errors) directory.Working with responders is pretty straightforward.
```php
createResponse(
'json',
[
'message' => 'Hello, world!',
]
);
```The above code returns a Response object containing the following JSON.
```json
{
"message": "Hello, world!"
}
```Responders can also be used to simplify error handling, and to normalize error response formats.
```php
createErrorResponse(
new \DaybreakStudios\RestApiCommon\Error\Errors\AccessDeniedError(),
'json'
);
```The above code returns a Response object containing the following JSON.
```json
{
"error": {
"code": "access_denied",
"message": "Access Denied"
}
}
```For Symfony projects, you can use `DaybreakStudios\RestApiCommon\ResponderService` instead, which takes a responder and
the request stack, allowing you to omit the format argument in calls to `::createResponse()` and
`createErrorResponse()`.# Validation
The validation component adds an extra API error class, which will normalize constraint violation errors from the
[symfony/validator](https://packagist.org/packages/symfony/validator) component, allowing them to nicely returned as an
API error.```php
createErrorResponse(
new \DaybreakStudios\RestApiCommon\Error\Errors\Validation\ValidationFailedError($violations),
'json'
);
```The above code returns a Response object containing approximately the following JSON.
```json
{
"error": {
"code": "validation_failed",
"message": "One or more fields did not pass validation",
"context": {
"failures": {
"path.to.field": "Symfony validator error message (e.g. 'This value should be 3 or less.')",
"some.other.field": "Error message"
}
}
}
}
```# Doctrine Query Document
The Doctrine query document component adds 4 new API error classes. Since they're relatively simple, please refer to
the individual [class documentation](src/Error/Errors/DoctrineQueryDocument).# Lexik JWT
The Lexik JWT component adds a special event subscriber that will transform the very generic error messages emitted by
the [lexik/jwt-authentication-bundle](https://packagist.org/packages/lexik/jwt-authentication-bundle) into messages that
can be displayed directly to the end-user.To register the subscriber in a Symfony application, be sure to add the
[`kernel.event_subscriber`](https://symfony.com/doc/current/reference/dic_tags.html#dic-tags-kernel-event-subscriber)
tag to the service!# Payload
The payload component adds a simple framework for parsing API payloads into Data Transfer Object (DTO) classes. While
useful by itself, giving you a more concrete set of fields on the objects your API consumes, it is most useful when also
paired with the `symfony/validator` package, giving you a clean way to validate input to your API. By default,
assertions tagged with the "create" group will only be run when `DecoderIntent::CREATE` is passed to the `parse()`
method, while assertions tagged with the "update" method will be run when `DecoderIntent::UPDATE` is passed. Assertions
in the "Default" group will _always_ run. For example:```php
'Tyler Lartonoix',
'email' => 'invalid email',
]);/**
* These objects would come from some other part of your application, e.g. a service container
* @var SerializerInterface $serializer
* @var ValidatorInterface $validator
*/$decoder = new SymfonyDeserializeDecoder($serializer, 'json', UserPayload::class, $validator);
$payload = $decoder->parse(DecoderIntent::CREATE, $input);
```In the above example, the final line's call to `SymfonyDeserializeDecoder::parse()` would result in an
`ApiErrorException`, whose `error` field would be a `ValidationFailedError`, since the input payload did not pass
validation (the email address was not a valid email address).If you're using PHP 8, you can use the `PayloadTrait` in your DTO class to gain access to the `exists()` and `unset()`
utility methods. Since PHP 8 introduces the `mixed` psuedo-type, you can use it as the actual type for the properties on
your DTO class. By doing so, you can use the `exists()` method, which uses `\ReflectionProperty::isInitialized()` to
determine if a property was actually part of the input payload, and not just defaulting to `null` because the key was
not included. This is useful when you have a property that might be part of the payload, but whose value could be `null`
(since `isset()` would still return `false` when used on such a property). Since `exists()` uses reflection, results
are cached to mitigate performance hits due to repeated calls for the same property. **Do not** call `unset()` directly
on any property of a DTO class that you might need to call `exists()` on; instead, use `PayloadTrait::unset()` to unset
the property and clear it from the `exists()` cache.