Reputation: 6965
I have a generic route like below -
$app->map(['GET', 'POST'], "/", function (\Slim\Http\Request $request, \Slim\Http\Response $response) use ($app) {
// Some random version 1 processing here ...
}
And I have a v2 version of the same like below -
$app->map(['GET', 'POST'], "/api/v2", function (\Slim\Http\Request $request, \Slim\Http\Response $response) use ($app) {
// Some random version 2 processing here ...
}
I have a problem to solve -
The frontend always hits the v1 of the API (the generic route). I need to internally redirect to v2, based on the parameter appversion
's value.
How can I do this in slim framework routing without issuing 302 redirect headers back to the client? Basically, I need to redirect internally to the server itself.
Note: I am aware of Nginx and Apache rewrites. Let us keep them aside and limit the scope to slim framework routing alone.
Upvotes: 1
Views: 2449
Reputation: 3409
What you want to achieve is technically possible, but to me, it seems the reason behind your question is that you want to introduce a new version of your API, yet you want not to (or you can not) update the front end to call the new version, and instead, you want to handle this in the back end.
If you are going to decide which version of your API needs to be called based on appversion
but not the endpoint that was hit, then what is the benefit of defining v1
and v2
endpoints?
If someone calls your v1
endpoint, they want your v1
response, and if someone needs your v2
response, they must call your v2
endpoint. If you return the same response for both v1
and v2
endpoints, then you're basically updating your v1
endpoint behavior.
Anyway, you want to dispatch another route in another route callback, and here is a fully working example showing how it's done using a subRequest
:
<?php
require 'vendor/autoload.php';
$app = new \Slim\App;
$app->map(['GET', 'POST'], '/v1', function($request, $response) use ($app) {
// Get the appversion query parameter and make the decision based on its value
$appVersion = $request->getParam('appversion');
if($appVersion == '1.0') {
return $app->subRequest($request->getMethod(), '/v2',
http_build_query($request->getQueryParams()),
$request->getHeaders(),
$request->getCookieParams(),
$request->getBody()->getContents()
);
}
return 'API version: v1, appversion: ' . $appVersion;
});
$app->map(['GET', 'POST'], '/v2', function($request, $response) {
return 'API version: v2, request method: ' . $request->getMethod() .', appversion: '. $request->getParam('appversion') . ', body: <pre>' . print_r($request->getParsedBody(), 1);
});
$app->get('/form', function() {
return <<<form
<form method="POST" action="/v1?appversion=1.0">
<input type="text" name="foo" value="bar">
<button type="submit" value="submit">Submit</button>
</form>
form;
});
$app->run();
Now if you try to reach /v1?appversion=1.0
the response from /v2
callback will be returned. Trying to reach /v1
with appversion
equal to any other value (for example /v1?appversion=2.0
) causes the app to return the v1
response.
The subRequest
method is also capable of handling POST
requests. Please refer to method documentation in Slim code repository. The example provides a /form
URI to demonstrate that.
Upvotes: 1
Reputation: 3620
I'd do it using optional segments in a single route definition.
use Psr\Http\Message\{
ServerRequestInterface as Request,
ResponseInterface as Response
};
$app->map(['GET', 'POST'], '/[{v2:api/v2}]', function (Request $request, Response $response, array $args) {
if (isset($args['v2'])) { // You may also check $request->getAttribute('appversion')
// Version 2 processing here...
// return $response;
}
// Version 1 processing here...
// return $response;
});
Upvotes: 2
Reputation: 1223
You'd need to abstract your invocations and create controllers (i.e. your code won't be so slim).
Example:
function doV2() {
// ... do V2 things
}
function doV1() {
// ... do V1 things
}
$app->map(['GET', 'POST'], "/", function (\Slim\Http\Request $request, \Slim\Http\Response $response) use ($app) {
if($app->request()->params('appversion') === 'v2') return doV2();
doV1();
}
$app->map(['GET', 'POST'], "/api/v2", function (\Slim\Http\Request $request, \Slim\Http\Response $response) use ($app) {
doV2();
}
The reason you cannot 'redirect' internally is because your 'controllers' are anonymous closures so you have no way to name/reference/call them. Instead, if you abstract them out to functions (and yes, you'll probably need to pass in $request/$response too) then you have named methods you can invoke for the appropriate routes.
Instead of defining closures or functions, you could also define your controllers and use SlimPHP's Container Resolution within the router - there's a great example in the router docs.
Last up, you could get tricky with middleware to change what happens based on your appversion param depending on the complexity of what you're wanting to achieve.
Upvotes: 1