Skip to content

Commit

Permalink
Merge pull request #1102 from cultuurnet/III-4896-psr-router
Browse files Browse the repository at this point in the history
III-4896 Add PSR router
  • Loading branch information
bertramakers committed Aug 23, 2022
2 parents e8b32ca + 4d1d2fe commit 7fda250
Show file tree
Hide file tree
Showing 15 changed files with 672 additions and 120 deletions.
81 changes: 81 additions & 0 deletions app/CatchAllRouteServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

namespace CultuurNet\UDB3\Silex;

use CultuurNet\UDB3\Http\LegacyPathRewriter;
use League\Route\Router;
use Silex\Application;
use Silex\ServiceProviderInterface;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;

final class CatchAllRouteServiceProvider implements ServiceProviderInterface
{
public function register(Application $app): void
{
}

public function boot(Application $app): void
{
$pathHasBeenRewrittenForSilex = false;
$originalRequest = null;

// NOTE: THIS CATCH-ALL ROUTE HAS TO BE REGISTERED INSIDE boot() SO THAT (DYNAMICALLY GENERATED) OPTIONS ROUTES
// FOR CORS GET REGISTERED FIRST BEFORE THIS ONE.
// Matches any path that does not match a registered route, and rewrites it using a set of predefined pattern
// replacements and sends an internal sub-request to try and match an existing route.
// This makes it possible to support old endpoint names without having to register controllers/request handlers
// twice.
// If the sub-request does not return a response either, it will be converted to a PSR request and dispatched on
// a new PSR router.
$app->match(
'/{path}',
function (Request $request, string $path) use ($app, &$pathHasBeenRewrittenForSilex, &$originalRequest) {
if (!$pathHasBeenRewrittenForSilex) {
// If the path has not been rewritten before, rewrite it and dispatch the request again to the Silex
// router.
$rewrittenPath = (new LegacyPathRewriter())->rewritePath($path);
$pathHasBeenRewrittenForSilex = true;
$originalRequest = $request;

// Create a new Request object with the rewritten path, because it's basically impossible to overwrite
// the path of an existing Request object even with initialize() or duplicate(). Approach copied from
// https://github.com/graze/silex-trailing-slash-handler/blob/1.x/src/TrailingSlashControllerProvider.php
$rewrittenRequest = Request::create(
$rewrittenPath,
$request->getMethod(),
[],
$request->cookies->all(),
$request->files->all(),
$request->server->all(),
$request->getContent()
);
$rewrittenRequest = $rewrittenRequest->duplicate(
$request->query->all(),
$request->request->all()
);
$rewrittenRequest->headers->replace($app['request']->headers->all());

// Handle the request with the rewritten path.
// If the Silex app still cannot match the new path to a route, this catch-all route will be matched
// again and that time we will dispatch it to the new PSR router because
// $pathHasBeenRewrittenForSilex will be set to true.
return $app->handle($rewrittenRequest, HttpKernelInterface::SUB_REQUEST);
}

// If the request is still not matched with a route after it has been rewritten and dispatched on the
// Silex router a second time, convert it to a PSR request and dispatch it on the PSR router instead.
/** @var Router $psrRouter */
$psrRouter = $app[Router::class];
$psrRequest = (new DiactorosFactory())->createRequest($originalRequest);
$psrRequest = (new LegacyPathRewriter())->rewriteRequest($psrRequest);
$psrResponse = $psrRouter->handle($psrRequest);
return (new HttpFoundationFactory())->createResponse($psrResponse);
}
)->assert('path', '^.+$');
}
}
14 changes: 14 additions & 0 deletions app/Error/WebErrorHandlerProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
use CultuurNet\UDB3\Security\CommandAuthorizationException;
use Error;
use Exception;
use League\Route\Http\Exception\MethodNotAllowedException;
use League\Route\Http\Exception\NotFoundException;
use Respect\Validation\Exceptions\GroupedValidationException;
use Silex\Application;
use Silex\ServiceProviderInterface;
Expand Down Expand Up @@ -89,6 +91,18 @@ private static function convertThrowableToApiProblem(Request $request, Throwable
)
);

case $e instanceof NotFoundException:
return ApiProblem::urlNotFound();

case $e instanceof MethodNotAllowedException:
$details = null;
$headers = $e->getHeaders();
$allowed = $headers['Allow'] ?? null;
if ($allowed !== null) {
$details = 'Allowed: ' . $allowed;
}
return ApiProblem::methodNotAllowed($details);

// Do a best effort to convert "not found" exceptions into an ApiProblem with preferably a detail mentioning
// what kind of resource and with what id could not be found. Since the exceptions themselves do not contain
// enough info to detect this, we need to get this info from the current request. However this is not
Expand Down
32 changes: 32 additions & 0 deletions app/Http/PsrRouterServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace CultuurNet\UDB3\Silex\Http;

use CultuurNet\UDB3\Http\CustomLeagueRouterStrategy;
use CultuurNet\UDB3\Http\Offer\GetDetailRequestHandler;
use League\Route\Router;
use Silex\Application;
use Silex\ServiceProviderInterface;

final class PsrRouterServiceProvider implements ServiceProviderInterface
{
public function register(Application $app): void
{
$app[Router::class] = $app::share(
function (Application $app) {
$router = new Router();
$router->setStrategy(new CustomLeagueRouterStrategy());

$router->get('/{offerType:events|places}/{offerId}/', [$app[GetDetailRequestHandler::class], 'handle']);

return $router;
}
);
}

public function boot(Application $app): void
{
}
}
90 changes: 0 additions & 90 deletions app/LegacyRoutesServiceProvider.php

This file was deleted.

1 change: 0 additions & 1 deletion app/Offer/OfferControllerProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ public function connect(Application $app): ControllerCollection
/** @var ControllerCollection $controllers */
$controllers = $app['controllers_factory'];

$controllers->get('/{offerType}/{offerId}/', GetDetailRequestHandler::class);
$controllers->delete('/{offerType}/{offerId}/', DeleteRequestHandler::class);

$controllers->put('/{offerType}/{offerId}/name/{language}/', UpdateTitleRequestHandler::class);
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"league/flysystem": "^2.2.3",
"league/flysystem-aws-s3-v3": "^2.1",
"league/period": "^3.3",
"league/route": "^5.1",
"league/uri": "^6.3",
"league/uri-components": "^2.4",
"marvin_b8/psr-7-service-provider": "^1.0",
Expand Down
Loading

0 comments on commit 7fda250

Please sign in to comment.