Skip to content

Commit

Permalink
Merge pull request #33 from wizardstechnologies/feat/rest-view
Browse files Browse the repository at this point in the history
feat: add restview
  • Loading branch information
BigZ authored Jan 17, 2021
2 parents 7e5c88c + 906e7b3 commit 5bd6ddb
Show file tree
Hide file tree
Showing 12 changed files with 124 additions and 99 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
composer.lock
vendor
.phpunit.result.cache
17 changes: 5 additions & 12 deletions Controller/JsonControllerTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

namespace Wizards\RestBundle\Controller;

use InvalidArgumentException;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\ConstraintViolation;
use Wizards\RestBundle\Exception\MultiPartHttpException;
use WizardsRest\Exception\HttpException;

/**
* A trait that helps in building restful json controllers.
Expand All @@ -20,21 +20,18 @@ protected function throwRestErrorFromForm(FormInterface $form)
}

/**
* Transform a json request body to a valid symfony form and submits it.
*
* @param FormInterface $form
* @param Request $request
* Transform a json request body to a valid symfony form and submit it.
*/
protected function handleJsonForm(FormInterface $form, Request $request)
{
$body = $this->decode($request, $form);

if (empty($body)) {
throw new InvalidArgumentException('invalid, empty or not json/jsonapi body provided');
throw new HttpException(400, 'invalid, empty or not json/jsonapi body provided');
}

if (empty($body[$form->getName()])) {
throw new InvalidArgumentException(sprintf('json should contain a %s key', $form->getName()));
throw new HttpException(400, sprintf('json should contain a %s key', $form->getName()));
}

$form->submit($body[$form->getName()], $request->getMethod() !== 'PATCH');
Expand Down Expand Up @@ -76,12 +73,8 @@ private function decodeJsonApi(string $content, FormInterface $form): array

/**
* Transform form errors in a simple array.
*
* @param FormInterface $form
*
* @return array
*/
private function convertFormErrorsToArray(FormInterface $form)
private function convertFormErrorsToArray(FormInterface $form): array
{
$errors = [];

Expand Down
3 changes: 0 additions & 3 deletions DependencyInjection/WizardsRestExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@
*/
class WizardsRestExtension extends Extension
{
/**
* {@inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container): void
{
$configuration = new Configuration();
Expand Down
6 changes: 0 additions & 6 deletions Exception/MultiPartHttpException.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@ class MultiPartHttpException extends \WizardsRest\Exception\HttpException
*/
private $messageList;

/**
* MultiPartHttpException constructor.
*
* @param int $statusCode
* @param array $messageList
*/
public function __construct(int $statusCode = 500, array $messageList = ['Internal Server Error.'])
{
$this->messageList = $messageList;
Expand Down
6 changes: 0 additions & 6 deletions ParamConverter/Psr7ParamConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
*/
class Psr7ParamConverter implements ParamConverterInterface
{
/**
* @inheritdoc
*/
public function apply(Request $request, ParamConverter $configuration)
{
$psr17Factory = new Psr17Factory();
Expand All @@ -27,9 +24,6 @@ public function apply(Request $request, ParamConverter $configuration)
return true;
}

/**
* @inheritdoc
*/
public function supports(ParamConverter $configuration)
{
if ($configuration->getClass() == ServerRequestInterface::class) {
Expand Down
64 changes: 58 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@ Helps you create a REST API in an expressive and streamlined way.
It will help you conceive mature and discoverable APIs, thanks to the jsonapi specification.
Regardless of the output serialization, the request format will be the same.
Have a look at http://github.com/wizardstechnologies/php-rest-api for further documentation and explanations.
You can find an example project on https://github.com/BigZ/promoteapi

# Requirements
```
symfony >= 4.3
symfony >= 4.4
php >= 7.3
```

# Install
# Installation
```
composer require wizards/rest-bundle
```

# Configure
# Configuration
Create a configuration file with the following values:

```
Expand All @@ -39,8 +41,58 @@ This bundle ease the use of wizard's php rest api, and provide some extra goodie
- [a multi-part exception](https://github.com/wizardstechnologies/rest-api-bundle/blob/master/ParamConverter/Psr7ParamConverter.php) to easily serialize multiple errors (such as the one from forms)
- [a controller trait](https://github.com/wizardstechnologies/rest-api-bundle/blob/master/Controller/JsonControllerTrait.php) that helps on serializing input data from json and jsonapi

# Documentation

- Configure your data source
- Expose your endpoints
If you use symfony flex, those services will be automatically registered.


To serialize a single resource, just return the object from a controller:
```
public function getArtistAction(string $id, EntityManagerInterface $entityManager)
{
try {
$artist = $entityManager->find(Artist::class, $id);
} catch (\Exception $exception) {
throw new NotFoundHttpException('Artist not found.');
}
return $artist;
}
```
Note that we don't use the param injector for the entity as we want to be able to dispatch an error ourselves
so it is properly formatted.


To Serialize a collection, use the collectionManager
```
public function getArtistsAction(CollectionManager $collectionManager, ServerRequestInterface $request)
{
return $collectionManager->getPaginatedCollection(Artist::class, $request);
}
```


To deserialize input, use the json trait
```
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Wizards\RestBundle\Controller\JsonControllerTrait;
class ArtistController extends AbstractController
{
use JsonControllerTrait;
public function postArtistAction(Request $request, EntityManagerInterface $entityManager)
{
$artist = new Artist();
$form = $this->createForm(ArtistType::class, $artist);
$this->handleJsonForm($form, $request);
if (!$form->isValid()) {
$this->throwRestErrorFromForm($form);
}
$entityManager->persist($artist);
$entityManager->flush();
return $artist;
}
}
```
18 changes: 1 addition & 17 deletions Services/ResourceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,6 @@ public function __construct(
* Transforms a entity or a collection to a Fractal resource.
* If it is a collection, paginate it.
*
* @param mixed $result
* @param ServerRequestInterface $request
*
* @return ResourceAbstract
*
* @throws \ReflectionException
*/
public function getResource($result, ServerRequestInterface $request): ResourceAbstract
Expand All @@ -81,9 +76,6 @@ public function getResource($result, ServerRequestInterface $request): ResourceA
return $resource;
}

/**
* @param array $controller
*/
public function setController(array $controller): void
{
$this->controller = $controller;
Expand All @@ -92,8 +84,6 @@ public function setController(array $controller): void
/**
* Try to get the resource name/type by annotation, first on the method then on the class of the controller.
*
* @return string|null
*
* @throws \ReflectionException
*/
private function getResourceName(): ?string
Expand Down Expand Up @@ -138,8 +128,6 @@ private function getResourceName(): ?string
* @TODO: it might not feel really natural that the entity transformer is the default one.
* We might want a simpler default, or no default at all. Discussion is open !
*
* @param mixed $result
*
* @return \Closure|null
*/
private function getTransformer($result)
Expand All @@ -159,11 +147,7 @@ private function getTransformer($result)


/**
* Is the given resource an collection of resources ?
*
* @param mixed $resource
*
* @return bool
* Is the given resource a collection of resources ?
*/
private function isCollection($resource): bool
{
Expand Down
17 changes: 6 additions & 11 deletions Subscriber/ExceptionSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,28 +61,23 @@ public function onKernelException(ExceptionEvent $event): void
}
}

if ($exception instanceof \InvalidArgumentException) {
$response->setStatusCode(400);
$response->setContent($this->getErrorResponseContent($exception));
}
$response->headers->replace($this->formatOptions->getFormatSpecificHeaders());

$event->setResponse($response);
}

/**
* @param HttpExceptionInterface|HttpException $exception
*
* @return null|string
* @param HttpException|HttpExceptionInterface $exception
*/
private function getErrorResponseContent(\Throwable $exception): ?string
private function getErrorResponseContent($exception): ?string
{
$errorMessages = $this->getErrorMessages($exception);
// @TODO: This is an ugly fix for phpmd, until 2.9 release
$statusTexts = Response::$statusTexts;

$statusTexts = Response::$statusTexts;
// If the error has no specific text, use the common text for this code.
if (empty($errorMessages) && isset($statusTexts[$exception->getStatusCode()])) {
if ((empty($errorMessages) || !$errorMessages[0])
&& isset($statusTexts[$exception->getStatusCode()])
) {
$errorMessages = [$statusTexts[$exception->getStatusCode()]];
}

Expand Down
9 changes: 8 additions & 1 deletion Subscriber/SerializationSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Symfony\Component\HttpKernel\KernelEvents;
use Wizards\RestBundle\Services\FormatOptions;
use Wizards\RestBundle\Services\ResourceProvider;
use WizardsRest\RestView;
use WizardsRest\Serializer;

/**
Expand Down Expand Up @@ -75,9 +76,11 @@ public function onKernelView(ViewEvent $event): void
$serializedResponse = print_r($serializedResponse, true);
}

$result = $event->getControllerResult();

$event->setResponse(new Response(
$serializedResponse,
200,
$result instanceof RestView ? $result->getCode() : 200,
$this->optionsFormatter->getFormatSpecificHeaders()
));
}
Expand All @@ -103,6 +106,10 @@ private function getResource(ViewEvent $event): ResourceAbstract
$request = $this->psrFactory->createRequest($event->getRequest());
$result = $event->getControllerResult();

if ($result instanceof RestView) {
$result = $result->getContent();
}

return $this->resourceProvider->getResource($result, $request);
}
}
13 changes: 7 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,20 @@
"ext-json": "*",
"nyholm/psr7": "^1.1",
"pagerfanta/pagerfanta": "^2.0",
"symfony/http-kernel": "^4.1|^5.0",
"symfony/psr-http-message-bridge": "^2.0.0",
"symfony/http-kernel": "^4.4|^5.0",
"symfony/psr-http-message-bridge": "^2.0",
"symfony/validator": "^4.4|^5.0",
"sensio/framework-extra-bundle": "^5.2",
"wizards/rest-api": "0.8.2"
"wizards/rest-api": "0.9.0"
},
"require-dev": {
"symfony/form": "^4.1",
"symfony/form": "^4.4|^5.0",
"phpstan/phpstan": "^0.12",
"squizlabs/php_codesniffer": "^3.5",
"phpmd/phpmd": "^2.9",
"phpunit/phpunit": "^9.2",
"symfony/phpunit-bridge": "^4.2",
"symfony/var-dumper": "^4.3|^5.1"
"symfony/phpunit-bridge": "^4.4|^5.0",
"symfony/var-dumper": "^4.4|^5.0"
},
"autoload": {
"psr-0": { "Wizards\\RestBundle": "" }
Expand Down
41 changes: 22 additions & 19 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
bootstrap="vendor/autoload.php"
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
bootstrap="vendor/autoload.php"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
>
<testsuites>
<testsuite name="Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src/</directory>
</whitelist>
</filter>
<coverage>
<include>
<directory>./src/</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
</phpunit>
Loading

0 comments on commit 5bd6ddb

Please sign in to comment.