According to http://www.slimframework.com/docs/concepts/middleware.html, route middleware is added as follows.
<?php
$app = new \Slim\App();
$mw = function ($request, $response, $next) {
// How is $arg accessed?
$response->getBody()->write('BEFORE');
$response = $next($request, $response);
$response->getBody()->write('AFTER');
return $response;
};
$app->get('/ticket/{id}', function ($request, $response, $args) {
$response->getBody()->write(' Hello ');
// $arg will be ['id'=>123]
return $response;
})->add($mw);
$app->run();
$arg will be the parameters array. How can this be accessed in the middleware?
http://help.slimframework.com/discussions/questions/31-how-pass-route-pram-to-middleware shows an approach to do so, however, appears to be an earlier release of Slim, and errors Fatal error: Call to undefined method Slim\\Route::getParams().
Route Params can be accessed through the routeInfo Attribute on Request, as such
$app->get('/hello/{name}', \HelloWorld::class . ':execute')
->add(function ($request, $response, $next) {
var_dump($request->getAttribute('routeInfo')[2]['name']);exit();
return $next($request, $response);
});
This is noted in this Github issue
Related
I have a Slim Framework application with a custom errorHandler, and a small middleware stack. My middleware adds attributes to the Request object which I would like to have access to from my error handler in the event of an exception. For example:
$app->get('/endpoint', function($request $response, $args) {
$myAttribute = $request->getAttribute('myAttribute'); //returns 'myValue'
throw new \Exception(); //any code that throws an error
})->add(function($request, $response, $next) {
$request = $request->withAttribute('myAttribute', 'myValue');
$response = $next($request, $response);
return $response;
});
$app->getContainer['errorHandler'] = function($c) {
return function($request, $response, $exception) {
$myAttribute = $request->getAttribute('myAttribute'); //returns null
return $response;
}
};
The attribute does not exist within the Request object inside the error handler because the cloned Request from inside the route has not been returned after traversing through the middleware stack. Is it possible to access the Request and Response objects as they exist, at the time (in the location) the exception is thrown? I can't explicitly pass them (for example, SlimException) because I'm trying to handle unexpected errors as well.
I've created two, somewhat hacky, solutions to capturing Request and Response state when exceptions are thrown. Both attempt to insert a try/catch as close to the center of the middleware stack as possible (without repeating code), and involve wrapping the original exception in a new exception class together with the modified endpoint parameters.
Middleware
This works as long as attention is paid to the order that middleware is added. Unfortunately it does require the innermost middleware is added to each route, or at the very least, "before" the middleware modifying the Request and/or Response objects. This won't catch any changes to Request/Response made inside, nor after the route.
class WrappedException extends \Exception {
public $exception;
public $request;
public $response;
public function __construct($exception, $request, $response) {
$this->exception = $exception;
$this->request = $request;
$this->response = $response;
}
}
$app->get('/endpoint', function($request $response, $args) {
throw new \Exception(); //any code that throws an error
})->add(function($request, $response, $next) {
try {
$response = $next($request, $response);
} catch (\Exception $exception) {
throw new WrappedException($exception, $request, $response);
}
return $response;
});
$app->add(function($request, $response, $next) {
$request = $request->withAttribute('myAttribute', 'myValue');
$response = $next($request, $response);
return $response;
});
$app->getContainer['errorHandler'] = function($c) {
return function($request, $response, $exception) {
if ($exception instanceof WrappedException) {
//returns 'myValue'
$myAttribute = $exception->request->getAttribute('myAttribute');
}
return $response;
}
};
Route Class
This wraps all routes in a try block, and makes for a bit cleaner route code, but you must be sure to extend all routes from the RouteBase class.
class WrappedException extends \Exception {
public $exception;
public $request;
public $response;
public function __construct($exception, $request = null, $response = null) {
$this->exception = $exception;
$this->request = $request;
$this->response = $response;
}
}
class RouteBase {
public function __call($method, $arguments) {
if (method_exists($this, $method)) {
try {
$this::$method(...$arguments);
} catch (\Exception $e) {
throw new WrappedException($e, ...$arguments);
}
} else {
throw new \Exception('Route method not found.');
}
}
}
class RouteClass extends RouteBase {
//PROTECTED -- must be callable by parent class, but not outside class
protected function get(Request $request, Response $response, $args) {
throw new \Exception('A new exception!');
$response->write('hey');
return $response;
}
}
$app->get('/endpoint', 'RouteClass:get');
$app->add(function($request, $response, $next) {
$request = $request->withAttribute('myAttribute', 'myValue');
$response = $next($request, $response);
return $response;
});
$app->getContainer['errorHandler'] = function($c) {
return function($request, $response, $exception) {
if ($exception instanceof WrappedException) {
//returns 'myValue'
$myAttribute = $exception->request->getAttribute('myAttribute');
}
return $response;
}
};
Upgrade to Slim 4 (if you can)
Worth noting now that Slim 4 fixes this issue. Personally, it was kind of a pain to upgrade as a lot changed, especially with the Dependency Injection containers (I went from Slim v3.12 => 4.8).
In Slim 4 they moved both the routing and the error handling into (separate) middlewares.
I'm using postman to test my API, but right now I have a problem with the put routes.
So this is a put route I wrote:
$app->put('/setting/{id}/settingvalue', function (ServerRequestInterface $request, Response
Interface $response, $args) {
try {
$user = new \Riecken\PBS\controller\SettingsValueController();
$result = $user->updateOneSettingsValueinDetail( $args['id'], $request->getParsedBody());
$response = $response->withJson($result);
$response = $response->withStatus(200);
return $response;
}catch(Exception $e) {
$response->getBody()->write($e->getMessage());
return $response->withStatus($e->getCode());
}
});
And this is the function which you see above (updateOneSettingsValueinDetail):
public function updateOneSettingsValueinDetail ($settingsvalueIdToUpdate, $body) {
try {
return $this->SettingsValueDao->update($settingsvalueIdToUpdate, $body);
}catch(DAOException $e) {
throw new \Exception($e->returnErrorMessage(), $e->returnHttpCode());
}catch(\Exception $e) {
throw new \Exception("System Error", 500);
}
}
The problem is that Postman tells me that the Method is not allowed, only POST and GET is allowed:
enter image description here
Does anybody know what type of problem that is and what the solution could be?
This response is come from Slim's NotAllowedHandler. And it is not only POST and GET as default. This response is not related to your mentioned above code.
Are you sure you don't customize "NotAllowedHandler" and you don't bind to app as middleware?
I wrote this code that containing that would create the same situation:
<?php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\App;
require __DIR__ . '/../vendor/autoload.php';
$app = new App([]);
$container = $app->getContainer();
$app->add(function ($request, $response, $next) {
$allowed = ['GET', 'POST'];
if (!in_array($request->getMethod(), $allowed)) {
$notAllowed = new \Slim\Handlers\NotAllowed();
return $notAllowed($request, $response, $allowed);
}
$next($request, $response);
});
$app->put('/setting/{id}/settingvalue', function (ServerRequestInterface $request, ResponseInterface $response, $args) {
die("Expected Context via PUT");
});
$app->get('/setting/{id}/settingvalue', function (ServerRequestInterface $request, ResponseInterface $response, $args) {
die("Expected Other Context via GET");
});
$app->run();
I hope to it help you.
I have created a simple rest api which handles some POST data. I would like to add some functions to sanitize the data before sending a response.
I am new to Slim and PHP so unsure if this is possible / i am solving the problem using the "correct" approach.
Here is my attempt so far (which does not work!) The middleware is called, but the process function always returns NULL
<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
require __DIR__ . '/../vendor/autoload.php';
$app = new \Slim\App();
// add function to $app
$app->process = function ($request, $response, $next) use($app) {
return 'process';
};
$process = $app->process;
// middleware
$mw = function ($request, $response, $next) {
$response = $next($request, $response);
// should return string from above function
$variable = $process
$data = array('name' => $name, 'process' => $variable);
$newResp = $response->withJson($data);
return $newResp;
};
$app->post('/api/name', function (Request $request, Response $response, array $args) {
$parsed= $request->getParsedBody();
$response = $response->withStatus(200);
})->add($mw);
$app->run();
There were several issues:
Added function to $app which results in a BadMethodCallException
$appalready has a method called process
Missed ; after $variable = $process
This code gives your desired result:
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
require __DIR__ . '/../vendor/autoload.php';
// turn error logging on!
$app = new \Slim\App(['settings' => ['displayErrorDetails' => true]]);
// your function
$sanitize = function(){
return 'process';
};
// middleware + pass function
$mw = function ($request, $response, $next) use($sanitize) {
$response = $next($request, $response);
$variable = $sanitize(); // execute function
$data = array('name' => $name, 'process' => $variable);
$newResp = $response->withJson($data);
return $newResp;
};
$app->get('/api/name', function (Request $request, Response $response, array $args) {
$parsed= $request->getParsedBody();
$response = $response->withStatus(200);
})->add($mw);
$app->run();
When you don't need to use the functions in other middleware, you could simply define the functions in your middleware, like this:
// middleware
$mw = function ($request, $response, $next) use($sanitize) {
$response = $next($request, $response);
// your function
function sanitize(){
return 'process';
}
$data = array('name' => $name, 'process' => sanitize());
$newResp = $response->withJson($data);
return $newResp;
};
I am creating an API and need to implement the following functionality. Some roots need to be accessible to users that are:
List item
not loged in
logged in
logged in and are premium users
logged in and are premium users with x amount of points.
For this reason I chose to use slim groups. Mind you, I'm not sure that this is the proper usage for group middle ware, but this is what I did.
<?php
$ifa = function($request, $response, $next){
$a=true;
if ($a) {
$request = $request->withAttribute('foo', 'bar');
$response = $next($request, $response);
}
return $response;
};
$ifb = function($request, $response, $next){
$b=true;
if ($b) {
$response = $next($request, $response);
}
return $response;
};
$ifc = function($request, $response, $next){
$c=true;
if ($c) {
$response = $next($request, $response);
}
return $response;
};
$app->group('',function() use ($app, $ifb,$ifc){
$app->get('/home', 'AuthController:home')->setName('home');
$app->group('',function() use ($app,$ifc){
$app->get('/school', 'AuthController:school');
$app->group('',function() use ($app){
$app->get('/farm', 'AuthController:farm');
})->add($ifc);
})->add($ifb);
})->add($ifa);
I would like to separate my middleware code (named functions or classes) from my routes. Even though this set up currently works, I would prefer to work with classes, what would be a cleaner way of writing my code? I would appreciate any suggestions.
First $ifa can be simplified as
$ifa = function($request, $response, $next){
$request = $request->withAttribute('foo', 'bar');
return $next($request, $response);
};
$ifb and $ifc are useless and can be omitted and removed from route as they are essentially doing nothing (CMIIW).
To make it $ifa as class instead of anonymous function, you can declare it like this
<?php
class Ifa
{
public function __invoke($request, $response, $next)
{
$request = $request->withAttribute('foo', 'bar');
return $next($request, $response);
};
}
and in routes declaration
$app->group('/', function() {
//do something here
})->add(Ifa::class);
In Slim Framework v2 I use simple authentication function as a hook to check if a route needs login.
This is authentication code:
$authenticate = function ( $app ) {
return function () use ( $app ) {
if ( !isset( $_SESSION['userid'] ) ) {
$_SESSION['urlRedirect'] = $app->request()->getPathInfo();
$app->flash('danger', 'You need to login');
$app->redirect('/login');
}
};
};
this is how I use in Slim v2:
$app->get("/user/change-password", $authenticate($app), function () use ( $app ) {
$userStuff->changePassowrd($_SESSION['userid'], $newPassword, $oldPassword);
});
I can implement this to Slim v3 without a problem but I can't seem to understand how I supposed to do this with middleware(to learn and use the functionally)
I have tried this: this is my middleware class;
<?php
namespace entity;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
class Auth
{
/**
* Example middleware invokable class
*
* #param \Psr\Http\Message\ServerRequestInterface $request PSR7 request
* #param \Psr\Http\Message\ResponseInterface $response PSR7 response
* #param callable $next Next middleware
*
* #return \Psr\Http\Message\ResponseInterface
*/
public function __invoke($request, $response, $next)
{
if ($this->authenticate()) {
$response = $next($request, $response);
} else {
echo 'You need to login';
}
return $response;
}
public function authenticate() {
if ( !isset( $_SESSION['userid'] ) ) {
return true;
}
return false;
}
}
?>
Registered it:
$app->add( new entity\Auth() );
and I don't know how to use this on a route as I did in Slim v2, where do I add this in the route to check if that route needs an authentication?
Thanks.
What you are looking for is actually excluding a route for the Auth middleware.
You do want that check for all other routes but not for changePassword.
In your __invoke you need to get the current route and only do authenticate if the route is not changePassword (if that's the name of your route).
public function __invoke($request, $response, $next)
{
$route = $request->getAttribute('route');
if (in_array($route->getName(), ['changePassword']) || $this->authenticate()) {
$response = $next($request, $response);
} else {
echo 'You need to login';
}
return $response;
}
Well the basic example is next
<?php
require 'vendor/autoload.php';
session_start();
class Auth {
public function __invoke($request, $response, $next) {
if ($this->authenticate()) {
$response = $next($request, $response);
} else {
echo 'You need to login';
}
return $response;
}
public function authenticate() {
if (isset($_SESSION['userid'])) {
return true;
}
return false;
}
}
$app = new \Slim\App();
$app->get('/open', function($request, $response, $args){
echo 'HAPPINESS FOR EVERYBODY, FREE, AND LET NO ONE BE LEFT BEHIND!';
});
$app->get('/closed', function($request, $response, $args){
echo 'You are authenticated!';
})->add(new Auth());
$app->get('/login', function($request, $response, $args){
$_SESSION['userid'] = 1;
});
$app->run();
I think there was a mistake in as authenticate function as !isset means there is no $_SESSION['userid'] and probably user is not logged in and your function tells that he is logged.
But anyway you can add middleware to single route or group of routes with add() method.