ArunKolhapur
ArunKolhapur

Reputation: 6965

PHP Slim-3: How to redirect internally to another route

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

Answers (3)

Nima
Nima

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

N&#39;Bayramberdiyev
N&#39;Bayramberdiyev

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

developerjack
developerjack

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

Related Questions