How to forward an HTTP request with Slim Framework - php

Is it possible to forward a request in Slim?
The meaning of "forward", like in JavaEE, is to internally redirect to another route without return the response to the client and maintaining the model.
For example:
$app->get('/logout',function () use ($app) {
//logout code
$app->view->set("logout",true);
$app->forward('login'); //no redirect to client please
})->name("logout");
$app->get('/login',function () use ($app) {
$app->render('login.html');
})->name("login");

In my opinion, the best way to do this would be by using Slim's internal router (Slim\Router) capabilities and dispatching (Slim\Route::dispatch()) the matched route (meaning: executing the callable from a matched route without any redirect). There are a couple of options that come to mind (depending on your setup):
1. calling a named route + callable doesn't take any arguments (your example)
$app->get('/logout',function () use ($app) {
$app->view->set("logout",true);
// here comes the magic:
// getting the named route
$route = $app->router()->getNamedRoute('login');
// dispatching the matched route
$route->dispatch();
})->name("logout");
This should definitely do the trick for you, but I still want to show the other scenarios ...
2. calling a named route + callable with arguments
The above example will fail ... because now we need to pass arguments to the callable
// getting the named route
$route = $app->router()->getNamedRoute('another_route');
// calling the function with an argument or array of arguments
call_user_func($route->getCallable(), 'argument');
Dispatching the route (with $route->dispatch()) will invoke all middleware, but here we are just calling the the callable directly ... so to get the full package we should consider the next option ...
3. calling any route
Without named routes we can get a route by finding the one matching the a http method and pattern. For this we use Router::getMatchedRoutes($httpMethod, $pattern, $reload) with reload set to TRUE.
// getting the matched route
$matched = $app->router()->getMatchedRoutes('GET','/classes/name', true);
// dispatching the (first) matched route
$matched[0]->dispatch();
Here you might want to add some checks and for example dispatch notFound in case no route is matched.
I hope you get the idea =)

There is redirect() method. However it sends an 302 Temporary Redirect response which you do not want.
$app->get("/foo", function () use ($app) {
$app->redirect("/bar");
});
Another possibility is pass() which tells application to continue to next matching route. When pass() is called Slim will immediately stop processing the current matching route and invoke the next matching route.
If no subsequent matching route is found, a 404 Not Found is sent to the client.
$app->get('/hello/foo', function () use ($app) {
echo "You won't see this...";
$app->pass();
});
$app->get('/hello/:name', function ($name) use ($app) {
echo "But you will see this!";
});

I think you have to redirect them. There is no forward in Slim. But you can set a status code for example in the redirect function. When you redirect to a route you should get that functionality you want.
// With route
$app->redirect('login');
// With path and status code
$app->redirect('/foo', 303);
here is an example from the documentation:
<?php
$authenticateForRole = function ( $role = 'member' ) {
return function () use ( $role ) {
$user = User::fetchFromDatabaseSomehow();
if ( $user->belongsToRole($role) === false ) {
$app = \Slim\Slim::getInstance();
$app->flash('error', 'Login required');
$app->redirect('/login');
}
};
};

Related

Slim 4 internal redirect, infinity loop

After upgrading to Slim v4 I'm trying to replace my simple $app->subRequest call with $app->handle as specified in the changelog. However there are no details on how to do this in either the changelog or upgrade guide and my best effort to fix it ends up creating an infinite loop:
$app->get("/foo", function (Request $req) use ($app) {
$uri = $req->getUri();
$newUri = $uri->withPath("/bar");
$barReq = $req->withUri($newUri);
// Here we get stuck in endless loop instead of ending up in the /bar route handler below
$app->handle($barReq);
});
$app->get("/bar", function (Request $req) use ($app) {
echo 'bar!';
die;
});
It's like even though $barReq is a new request object with a completely new uri (and path) the router does not resolve which route handler that should handle it, instead it's just handled by the same one again.
My previous simplified (v3) code looked like and worked fine to get the result of the /bar route when calling /foo:
$app->get("/foo", function (Request $req) use ($app) {
$app->subRequest('GET', '/bar');
});
I'm probably missing some central concept on how Slim 4 handles requests and routes internally and would appreciate some help!
Edit: Should perhaps add that what I mean with internal redirect is that client should not be aware that a redirect has been made. I.e. any regular redirect function returning something to client is not applicable here.
As #remy stated, use the ServerRequestFactory implementing ServerRequestFactoryInterface.
For slim/psr7 it is: Slim\Psr7\Factory\ServerRequestFactory
A silent redirect to another route is then as simple as:
use Slim\Psr7\Factory\ServerRequestFactory;
...
...
$app->get('/foo', function ($request, $response, $args)
{
global $app;
return $app
->handle((new ServerRequestFactory())->createServerRequest('GET', '/bar'));
});

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.
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;
});
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.
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.

How does Routing work in PHP laravel?

I have just started playing with Laravel framework and I have seen this :
Route::get('foo', function () {
return 'Hello World';
});
Can some one please explain what is this ? I mean over all I know what is get . but why do we put 'foo' and then the closure we put ?
Also where am I really getting the information from ?
First we declare the Facade of the Route, think like a shortcut to use the Route class.
After that, we choose the method of the route, it could be:
Route::get($uri, $callback); //get
Route::post($uri, $callback); //post
Route::put($uri, $callback); //put
Route::patch($uri, $callback); //patch
Route::delete($uri, $callback); //delete
Now you choose the url of the page, for example:
If you digit in the browser:
www.foobar.com/user/profile
Laravel will search for the route with the user/profile parameter, like that:
Route::get('user/profile', function () {
return 'Hello World';
});
You can pass variables too,
Route::get('user/{id}', function () {
return 'Hello World';
});
After that, you choose the callback method, in other words, what is gonna happen when the laravel enter in the route.
In your example, you have the function example, just returning a simple "hello world".
The best pratice here is to create a controller
php artisan make:controller FoobarController --resource
And referece to any method of your controller
Route::get('user/profile', 'FoobarController#index');
Now, when the laravel find the route, it's going to redirect to the index method of the Foobar controller, and there, you define your logic
public function index() {
return view('welcome');
}
Firsty, read the documentation, it's super easy, even for the begginers.
Step by step:
get is the HTTP method you use on this particular route. The other most often used is POST, but there are more of them.
foo is the route, in that case will be: www.example.com\foo. You can put any name as you want and need.
As a second parameter to a Route facade you put closure/name of the controller/view you want to handle endpoint, e.g.
Route::get('foo', 'SomeController#method');
Route::get('foo', function(){
return view('some.view');
};
There are lot more options in routing and they are not difficult to understand, just have a look on documentation or some video tutorials.

Proper way to pass a hard-coded value from route to controller (Laravel)?

I have a PagesController with one action: view.
This action accepts a page argument.
What I want to achieve:
Have a routes example.com/about and example.com/foobar.
When one of this routes is triggered, pass a value predefined in routes file to PagesController#view.
In my routes file:
Route::get('about', function () {
return App::make('App\Http\Controllers\PagesController')->view('about');
})->name('aboutPage');
Route::get('foobar', function () {
return App::make('App\Http\Controllers\PagesController')->view('foobar');
})->name('foobarPage');
It works as expected, but I want to know is there a better and more proper way to achieve the same functionality?
Pass your pages as route parameter:
Route::get('{page}', 'PagesController#view');
//controller
public function view($page)
{
//$page is your value passed by route;
return view($page);
}
So you just want an argument to your action. You can use optional parameters if that argument can be empty. You can read more about it here.
Route::get('{argument?}', 'PagesController#view')->name('page');
And in your PagesController:
public function view($argument = 'default') {
// Your logic
}
The accepted answer is what you want based on what you are doing.
If you really wanted a hardcoded value you can use the 'actions' array part of the route if you wanted.
Route::get('something', ['uses' => 'Controller#page', 'page' => 'something']);
public function page(Request $request)
{
$page = $request->route()->getAction()['page'];
...
}
asklagbox - blog - random tips and tricks
If you don't need the names of the routes like in your example
->name('foobarPage');
you can use something like this
Route::get('{page_name}','PagesController#view')->where('page_name', '(about)|(foobar)');
This will accept only the values passed in the regular expression for the page_name parameter. Other routes will throw a 404 error. I should mention that this technique seems to be valid for applications with one level of url nesting only and should NOT be used as a pattern.
From what I can see above if all you are doing is showing the correct view I would go for
Route::get('{page}', function($page)
{
if (view()->exists($page)) {
return view($page);
}
return abort(404);
});
This prevents you even needing a method in your controller.

Change target controller/action from middleware

I have two resources /test and /blabla.
For the /test I have registered middleware. In the middleware, based on certain condition, I would like to redirect current call to the controller/action which serve /blabla resource, transparently for user (no any extra client request, no 302 status response code etc..). How can I achieve it ?
It seems like your solution might be better suited in the routes file. You are suggesting serving a different route given a certain condition.
So in your routes.php file:
Route::get('test', function(){
if($condition){
return App::make('App\Http\Controllers\TestController')->index();
} else {
return App::make('App\Http\Controllers\BlaBlaController')->index();
}
});
If you still want to handle it in the middleware you should be able to do the same thing as above:
return App::make($controller)->index(); // or whatever controller method you want to call.
If you need both sets of middlewares to be called, then inside the constructor (after calling your middlewares) check your condition and call the other controller like above.
If you want to change the users url, I don't think there's any way other than returning a redirect. Most users don't notice the redirect so it will probably seem "transparent."
In that case, your middleware function looks like:
public function handle($request, Closure $next)
{
if (something == thingy) {
return redirect('/blabla');
}
return $next($request);
}

Categories