Skip to content

Commit

Permalink
Issue #314: Implemented mezzio-problem-details and handler delegators.
Browse files Browse the repository at this point in the history
Signed-off-by: alexmerlin <[email protected]>
  • Loading branch information
alexmerlin committed Oct 21, 2024
1 parent 655ec19 commit 5a97d6a
Show file tree
Hide file tree
Showing 65 changed files with 931 additions and 856 deletions.
60 changes: 40 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# DotKernel API
# Dotkernel API

Based on Enrico Zimuel's [Zend Expressive API - Skeleton example](https://github.com/ezimuel/zend-expressive-api), DotKernel API runs on [Laminas](https://github.com/laminas) and [Mezzio](https://github.com/mezzio) components and implements standards like PSR-3, PSR-4, PSR-7, PSR-11 and PSR-15.
Based on Enrico Zimuel's [Zend Expressive API - Skeleton example](https://github.com/ezimuel/zend-expressive-api), Dotkernel API runs on [Laminas](https://github.com/laminas) and [Mezzio](https://github.com/mezzio) components and implements standards like PSR-3, PSR-4, PSR-7, PSR-11 and PSR-15.

![OSS Lifecycle](https://img.shields.io/osslifecycle/dotkernel/api)
![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/api/5.0.x-dev)
![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/api/5.0.0)

[![GitHub issues](https://img.shields.io/github/issues/dotkernel/api)](https://github.com/dotkernel/api/issues)
[![GitHub forks](https://img.shields.io/github/forks/dotkernel/api)](https://github.com/dotkernel/api/network)
Expand All @@ -14,33 +14,41 @@ Based on Enrico Zimuel's [Zend Expressive API - Skeleton example](https://github
[![codecov](https://codecov.io/gh/dotkernel/api/graph/badge.svg?token=53FN78G5CK)](https://codecov.io/gh/dotkernel/api)
[![Qodana](https://github.com/dotkernel/api/actions/workflows/qodana_code_quality.yml/badge.svg?branch=5.0)](https://github.com/dotkernel/api/actions/workflows/qodana_code_quality.yml)

[![SymfonyInsight](https://insight.symfony.com/projects/7f9143cc-5e3c-4cfc-992c-377a001fde3e/big.svg)](https://insight.symfony.com/projects/7f9143cc-5e3c-4cfc-992c-377a001fde3e)

## Getting Started

## Step 1: Clone the project

Using your terminal, navigate inside the directory you want to download the project files into. Make sure that the directory is empty before proceeding to the download process. Once there, run the following command:

git clone https://github.com/dotkernel/api.git .
```shell
git clone https://github.com/dotkernel/api.git .
```

## Step 2: Install project's dependencies

composer install
```shell
composer install
```

## Step 3: Development mode

If you're installing the project for development, make sure you have development mode enabled, by running:

composer development-enable
```shell
composer development-enable
```

You can disable development mode by running:

composer development-disable
```shell
composer development-disable
```

You can check if you have development mode enabled by running:

composer development-status
```shell
composer development-status
```

## Step 4: Prepare config files

Expand All @@ -57,11 +65,13 @@ You can check if you have development mode enabled by running:
* fill out the database connection params in `config/autoload/local.php` under `$databases['default']`
* run the database migrations by using the following command:

php vendor/bin/doctrine-migrations migrate
```shell
php vendor/bin/doctrine-migrations migrate
```

This command will prompt you to confirm that you want to run it:

WARNING! You are about to execute a migration in database "..." that could result in schema changes and data loss. Are you sure you wish to continue? (yes/no) [yes]:
> WARNING! You are about to execute a migration in database "..." that could result in schema changes and data loss. Are you sure you wish to continue? (yes/no) [yes]:
Hit `Enter` to confirm the operation.

Expand All @@ -71,33 +81,43 @@ Hit `Enter` to confirm the operation.

To list all the fixtures, run:

php bin/doctrine fixtures:list
```shell
php bin/doctrine fixtures:list
```

This will output all the fixtures in the order of execution.

To execute all fixtures, run:

php bin/doctrine fixtures:execute
```shell
php bin/doctrine fixtures:execute
```

To execute a specific fixture, run:

php bin/doctrine fixtures:execute --class=FixtureClassName
```shell
php bin/doctrine fixtures:execute --class=FixtureClassName
```

More details on how fixtures work can be found here: https://github.com/dotkernel/dot-data-fixtures#creating-fixtures

## Step 6: Test the installation

php -S 0.0.0.0:8080 -t public
```shell
php -S 0.0.0.0:8080 -t public
```

Sending a GET request to the [home page](http://0.0.0.0:8080/) should output the following message:

{
"message": "DotKernel API version 5"
}
```json
{
"message": "Dotkernel API version 5"
}
```

## Documentation

In order to access DotKernel API documentation, check the provided [readme file](documentation/README.md).
In order to access Dotkernel API documentation, check the provided [readme file](documentation/README.md).

Additionally, each CLI command available has it's own documentation:

Expand Down
8 changes: 4 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
"sort-packages": true,
"allow-plugins": {
"dotkernel/*": true,
"laminas/laminas-component-installer": true,
"composer/package-versions-deprecated": true,
"dealerdirect/phpcodesniffer-composer-installer": true
"dealerdirect/phpcodesniffer-composer-installer": true,
"laminas/laminas-component-installer": true
}
},
"extra": {
Expand Down Expand Up @@ -58,7 +58,7 @@
"dotkernel/dot-errorhandler": "^4.0.0",
"dotkernel/dot-mail": "^4.1.1",
"dotkernel/dot-response-header": "^3.2.3",
"laminas/laminas-component-installer": "^3.4.0",
"laminas/laminas-component-installer": "^3.4",
"laminas/laminas-config": "^3.9.0",
"laminas/laminas-config-aggregator": "^1.14.0",
"laminas/laminas-http": "^2.19.0",
Expand All @@ -74,7 +74,7 @@
"mezzio/mezzio-cors": "^1.11.1",
"mezzio/mezzio-fastroute": "^3.11.0",
"mezzio/mezzio-hal": "^2.9",
"mezzio/mezzio-problem-details": "^1.13.1",
"mezzio/mezzio-problem-details": "^1.14",
"mezzio/mezzio-twigrenderer": "^2.15.0",
"ramsey/uuid-doctrine": "^2.1.0",
"roave/psr-container-doctrine": "^5.2.1",
Expand Down
21 changes: 9 additions & 12 deletions config/autoload/local.php.dist
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

$baseUrl = 'http://localhost:8080';
$baseUrl = 'http://api.dotkernel.localhost';

$databases = [
'default' => [
Expand All @@ -19,14 +19,11 @@ $databases = [
];

return [
'application' => [
'name' => 'DotKernel API',
'url' => $baseUrl,
'versioning' => [
'documentation_url' => 'https://docs.dotkernel.org/api-documentation/v5/core-features/versioning',
],
'application' => [
'name' => 'DotKernel API',
'url' => $baseUrl,
],
'authentication' => [
'authentication' => [
'private_key' => [
'key_or_path' => getcwd() . '/data/oauth/private.key',
'key_permissions_check' => false,
Expand All @@ -45,15 +42,15 @@ return [
'message' => 'Invalid credentials.',
],
],
'databases' => $databases,
'doctrine' => [
'connection' => [
'databases' => $databases,
'doctrine' => [
'connection' => [
'orm_default' => [
'params' => $databases['default'],
],
],
],
'uploads' => [
'uploads' => [
'user' => [
'url' => $baseUrl . '/uploads/user',
'path' => realpath(__DIR__ . '/../../public/uploads/user'),
Expand Down
43 changes: 43 additions & 0 deletions config/autoload/problem-details.global.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

use Api\App\Service\ErrorReportServiceInterface;
use Fig\Http\Message\StatusCodeInterface;

return [
/**
* Unless specified when throwing the exception,
* mezzio-problem-details will set type based on the status code specified in the exception
*/
'problem-details' => [
'default_types_map' => [
StatusCodeInterface::STATUS_BAD_REQUEST => 'https://example.com/error/bad-request',
StatusCodeInterface::STATUS_UNAUTHORIZED => 'https://example.com/error/unauthorized',
StatusCodeInterface::STATUS_FORBIDDEN => 'https://example.com/error/forbidden',
StatusCodeInterface::STATUS_NOT_FOUND => 'https://example.com/error/not-found',
StatusCodeInterface::STATUS_METHOD_NOT_ALLOWED => 'https://example.com/error/method-not-allowed',
StatusCodeInterface::STATUS_NOT_ACCEPTABLE => 'https://example.com/error/method-not-acceptable',
StatusCodeInterface::STATUS_CONFLICT => 'https://example.com/error/conflict',
StatusCodeInterface::STATUS_GONE => 'https://example.com/error/gone',
StatusCodeInterface::STATUS_UNSUPPORTED_MEDIA_TYPE => 'https://example.com/error/unsupported-media-type',
StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR => 'https://example.com/error/internal-server-error',
],
],

/**
* Misc documentation URLs used application-wide
*/
'application' => [
'versioning' => [
'documentation_url' => 'https://docs.dotkernel.org/api-documentation/v5/core-features/versioning',
],
],

/**
* Error-reporting specific documentation URLs
*/
ErrorReportServiceInterface::class => [
'documentation_url' => 'https://example.com/error/not-authorized/error-reporting',
],
];
4 changes: 4 additions & 0 deletions config/pipeline.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use Mezzio\Helper\BodyParams\BodyParamsMiddleware;
use Mezzio\Helper\ServerUrlMiddleware;
use Mezzio\Helper\UrlHelperMiddleware;
use Mezzio\ProblemDetails\ProblemDetailsMiddleware;
use Mezzio\ProblemDetails\ProblemDetailsNotFoundHandler;
use Mezzio\Router\Middleware\DispatchMiddleware;
use Mezzio\Router\Middleware\ImplicitHeadMiddleware;
use Mezzio\Router\Middleware\ImplicitOptionsMiddleware;
Expand All @@ -24,6 +26,7 @@
// The error handler should be the first (most outer) middleware to catch
// all Exceptions.
$app->pipe(ErrorHandlerInterface::class);
$app->pipe(ProblemDetailsMiddleware::class);

$app->pipe(BodyParamsMiddleware::class);
$app->pipe(ServerUrlMiddleware::class);
Expand Down Expand Up @@ -84,5 +87,6 @@
// At this point, if no Response is returned by any middleware, the
// NotFoundHandler kicks in; alternately, you can provide other fallback
// middleware to execute.
$app->pipe(ProblemDetailsNotFoundHandler::class);
$app->pipe(NotFoundHandler::class);
};
16 changes: 11 additions & 5 deletions src/Admin/src/Command/AdminCreateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
use Api\Admin\InputFilter\CreateAdminInputFilter;
use Api\Admin\Service\AdminRoleService;
use Api\Admin\Service\AdminService;
use Api\App\Exception\BadRequestException;
use Api\App\Exception\ConflictException;
use Api\App\Exception\NotFoundException;
use Api\App\Message;
Expand All @@ -17,6 +16,7 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

use function implode;
use function sprintf;
Expand Down Expand Up @@ -54,12 +54,12 @@ protected function configure(): void
}

/**
* @throws BadRequestException
* @throws ConflictException
* @throws NotFoundException
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);

$inputFilter = (new CreateAdminInputFilter())->setData($this->getData($input));
if (! $inputFilter->isValid()) {
$messages = [];
Expand All @@ -69,10 +69,16 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
}

throw new BadRequestException(implode(PHP_EOL, $messages));
$io->error(implode(PHP_EOL, $messages));
return Command::FAILURE;
}

$this->adminService->createAdmin($inputFilter->getValues());
try {
$this->adminService->createAdmin($inputFilter->getValues());
} catch (ConflictException | NotFoundException $e) {
$io->error($e->getDetail());
return Command::FAILURE;
}

$output->writeln(Message::ADMIN_CREATED);

Expand Down
10 changes: 7 additions & 3 deletions src/Admin/src/ConfigProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Api\Admin\Service\AdminService;
use Api\Admin\Service\AdminServiceInterface;
use Api\App\ConfigProvider as AppConfigProvider;
use Api\App\Factory\HandlerDelegatorFactory;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Dot\DependencyInjection\Factory\AttributedRepositoryFactory;
use Dot\DependencyInjection\Factory\AttributedServiceFactory;
Expand All @@ -43,9 +44,12 @@ public function getDependencies(): array
{
return [
'delegators' => [
Application::class => [
RoutesDelegator::class,
],
Application::class => [RoutesDelegator::class],
AdminHandler::class => [HandlerDelegatorFactory::class],
AdminCollectionHandler::class => [HandlerDelegatorFactory::class],
AdminAccountHandler::class => [HandlerDelegatorFactory::class],
AdminRoleHandler::class => [HandlerDelegatorFactory::class],
AdminRoleCollectionHandler::class => [HandlerDelegatorFactory::class],
],
'factories' => [
AdminHandler::class => AttributedServiceFactory::class,
Expand Down
28 changes: 10 additions & 18 deletions src/Admin/src/Handler/AdminAccountHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,17 @@
use Api\App\Exception\BadRequestException;
use Api\App\Exception\ConflictException;
use Api\App\Exception\NotFoundException;
use Api\App\Handler\HandlerTrait;
use Api\App\Handler\AbstractHandler;
use Api\App\Message;
use Dot\DependencyInjection\Attribute\Inject;
use Mezzio\Hal\HalResponseFactory;
use Mezzio\Hal\ResourceGenerator;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

class AdminAccountHandler implements RequestHandlerInterface
class AdminAccountHandler extends AbstractHandler
{
use HandlerTrait;

#[Inject(
HalResponseFactory::class,
ResourceGenerator::class,
AdminServiceInterface::class,
)]
public function __construct(
protected HalResponseFactory $responseFactory,
protected ResourceGenerator $resourceGenerator,
protected AdminServiceInterface $adminService,
) {
#[Inject(AdminServiceInterface::class)]
public function __construct(protected AdminServiceInterface $adminService)
{
}

public function get(ServerRequestInterface $request): ResponseInterface
Expand All @@ -48,7 +37,10 @@ public function patch(ServerRequestInterface $request): ResponseInterface
{
$inputFilter = (new UpdateAdminInputFilter())->setData((array) $request->getParsedBody());
if (! $inputFilter->isValid()) {
throw (new BadRequestException())->setMessages($inputFilter->getMessages());
throw BadRequestException::create(
detail: Message::VALIDATOR_FIX_ERRORS,
additional: ['errors' => $inputFilter->getMessages()],
);
}

$admin = $this->adminService->updateAdmin($request->getAttribute(Admin::class), $inputFilter->getValues());
Expand Down
Loading

0 comments on commit 5a97d6a

Please sign in to comment.