There is a small problem with redirecting user after a POST action: you usually want to display some kind of confirmation (or error) message once the user is redirected to the next page. It is a good practice to inform a user about success or failure of his action. Because of the redirect, it is not easy to pass a message along. There are few options what you can do:
/another-route?message=Successfully created.
– but the user can
play with value of the message, possible XSS vulnerability./another-route?m=1
– annoying, you have
to determine which message to display.$_SESSION
array to display a message on next
opportunity – this is actually the best solution so far, but do not try to implement it by yourself, there is a lot
of libraries for that.Let’s take a look at the problem again: displaying errors is often easy, as you generally re-render a form, you can easily pass error messages into a template and display them:
$app->get('/some-route', function(Request $request, Response $response, $args) {
//render the form without error message
return $this->view->render($response, 'some-route.latte');
})->setName('some');
$app->post('/some-route', function(Request $request, Response $response, $args) {
$data = $request->getQueryParam();
if(everyhing_is_fine($data)) {
try {
//insert record into DB or update DB record
//...
//redirect to GET route
return $response->withHeader('Location', $this->router->pathFor('anotherRoute'));
} catch(Exception $e) {
//set custom error message and log exception
$tplVars['error'] = 'Some DB exception.';
}
} else {
//set custom error message
$tplVars['error'] = 'You missed some input fields.';
}
//re-render the form here with message and form fields
$tplVars['form'] = $data;
return $this->view->render($response, 'some-route.latte', $tplVars);
});
$app->get('/another-route', function(Request $request, Response $response, $args) {
//we need to display a message in this route which confirms previous POST action somehow
})->setName('anotherRoute');
Template some-route.latte
:
<!-- display error only when the variable $error is present -->
{if isset($error)}
<p>{$error}</p>
{/if}
<form method="post">
<!-- some fields and submit button -->
</form>
This approach is not very systematic, but works fine. The problem lies in the return $response->withHeader('Location', $this->router->pathFor('anotherRoute'));
line, because in that case, the target route does not know where the visitor is coming from and does not know that
it should display any message.
You have to think about each route as if it is a completely separate piece of code. Each HTTP request is handled by only
one route implementation and only one script execution. It is therefore impossible to pass values between routes via
ordinary PHP variable. This is not possible, because PHP script is re-executed for each HTTP request and the state of
variable $a
in following example is forgotten in subsequent requests:
//a variable which can be accessed by both routes (via use() statement)
$a = 0;
//you either execute this route and the program ends and frees up all its memory...
$app->get('/first-route', function(Request $request, Response $response, $args) use ($a) {
$a = $a + 5; //$a is 5 now
return $response->withHeader('Location', $this->router->pathFor('anotherRoute'));
});
//...or this route and the program ends and frees up all its memory
$app->get('/second-route', function(Request $request, Response $response, $args) use ($a) {
echo $a; //$a is back to 0
})->setName('anotherRoute');
Sorry for that, you have to think about PHP scripts in a bit different way – the PHP is not an application which runs all the time on the server, it only runs for the given time when it is handling the HTTP request. When there are no requests, the PHP application is just a bunch of files on server’s hard drive. Applications written in Java, Python or JavaScript work differently – they are in fact active even when there are no incoming HTTP request. But they also have to implement their own HTTP server.
Each framework has an ecosystem of libraries which extends its functionality. It is a good idea to search for flash message implementation curated especially for Slim framework.
Go to documentation page of slim/flash
package and
install it using composer require
command. You have to have Composer installed on your PC.
composer require slim/flash
Next step is to register flash
as dependency of your application in src/dependencies.php
.
$container['flash'] = function () {
return new \Slim\Flash\Messages();
};
You should be able to reference the library in routes and middleware by using $this->flash
now.
You probably saw the example in the documentation of the library. You should have noticed addMessage()
and
getMessages()
methods. Their names are self-describing. Unfortunately the docs does not explain anything more about
principle of flash messages.
Messages are either “positive” or “negative”. They can also be in other category, but these two are definitely the
most general ones. You usually want to visually distinguish positive and negative messages (you can use
Bootstrap’s alert alert-danger
and alert alert-success
classes on a paragraph).
For this reason, a good flash message library uses so called message bags to differentiate between categories
of messages – that is the first argument of addMessage()
method. A message bag is just an array named by the
programmer.
The addMessage()
method actually stores the message into a $_SESSION under the given message bag name. You can check
it out using this commands:
$this->flash->addMessage('success', 'Successfully performed an action!');
print_r($_SESSION);
exit;
There should be a slimFlash
key in the printout of $_SESSION
variable and inside that is another key called
success
.
The getMessages()
method simply retrieves that array from session and erases it, so the user won’t be confused
after he navigates to another page by a forgotten flash message.
Pick some POST route which you already have defined in your application. You should select a POST route which redirects after a success. Add row which sets the flash message with appropriate message bag name.
$app->post('...', function(Request $request, Response $response, $args) use ($a) {
try {
//insert or modify record in database
//...
//set flash message
$this->flash->addMessage('success', 'Successfully performed an action!');
//redirect
return $response->withHeader('Location', $this->router->pathFor('...'));
} catch(Exception $e) {
$this->logger->error($e->getMessage());
$tplVars['error'] = $e->getException();
}
//re-render template
return $this->view->render($response, 'template.latte', $tplVars);
});
Following step is to display flash messages. You can either display flash messages in layout template globally which might be very convenient, because you do not have to worry about retrieving flash messages from their storage, or hand-pick routes which should display them. You can register a middleware to pass flash messages to each possible template.
Register middleware to pass flash messages into Latte templates in middleware.php
, use
addParam()
method described in latte-view documentation:
$app->get(function (Request $request, Response $response, callable $next) {
$this->view->addParam('flash', $this->flash);
return $next($request, $response);
});
Display messages in layout.latte
:
{var $fm = $flash->getMessages()}
{if !empty($fm['success'])}
{foreach $fm['success'] as $m}
<p>{$m}</p>
{/foreach}
{/if}
{if !empty($fm['error'])}
{foreach $fm['error'] as $m}
<p>{$m}</p>
{/foreach}
{/if}
The getMessages()
method always picks and erases flash messages from session storage. Notice that I used this method
in the template, not in the middleware – the middleware only passes the flash object into template scope. In the
middleware, you cannot be 100% sure, whether the actual route will or will not render a template based on given layout.
Because flash messages are stored in session and the getMessages()
method clears that storage, they are displayed only
once. You should observe that after redirect, the message or messages are present and after reload of the page, the
flash messages are gone.
Flash messages are a useful concept which you can use to display one-time confirmation or error messages. Before you try to implement flash messages by yourself, try to find library which plays along with your framework.