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.
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 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'm trying to pass an array with data to middleware and format it based on Accept HTTP header.
The controller gets the data from db and should pass it to the response object. Response object write() method accepts only strings:
public function getData(Request $request, Response $response): Response {
return $response->write($this->getUsers());
# This line of code should be fixed
}
The middleware should get the response and format it correctly:
public function __invoke(Request $request, Response $response, callable $next) {
$response = $next($request, $response);
$body = $response->getBody();
switch ($request->getHeader('Accept')) {
case 'application/json':
return $response->withJson($body);
break;
case 'application/xml':
# Building an XML with the data
$newResponse = new \Slim\Http\Response();
return $newResponse->write($xml)->withHeader('Content-type', 'application/xml');
break;
case 'text/html':
# Building a HTML list with the data
$newResponse = new \Slim\Http\Response();
return $newResponse->write($list)->withHeader('Content-type', 'text/html;charset=utf-8');
break;
}
}
I have a few routes behaves similarly:
$app->get('/api/users', 'UsersController:getUsers')->add($formatDataMiddleware);
$app->get('/api/products', 'UsersController:getProducts')->add($formatDataMiddleware);
By using middlewares I can add such functionality in a declarative way, keeping my controller thin.
How can I pass the original data array to response and implementing this pattern?
The Response-Object doesn't provide this functionallity nor has some extensions to do that. So you need to adjust the Response-Class
class MyResponse extends \Slim\Http\Response {
private $data;
public function getData() {
return $this->data;
}
public function withData($data) {
$clone = clone $this;
$clone->data = $data;
return $clone;
}
}
Then you need to add the new Response to the Container
$container = $app->getContainer();
$container['response'] = function($container) { // this stuff is the default from slim
$headers = new Headers(['Content-Type' => 'text/html; charset=UTF-8']);
$response = new MyResponse(200, $headers); // <-- adjust that to the new class
return $response->withProtocolVersion($container->get('settings')['httpVersion']);
}
Now change the Response type to MyResponse and use the withData method
public function getData(Request $request, \MyResponse $response): Response {
return $response->withData($this->getUsers());
}
At the end you can use the getData method and use its value and process it inside the middleware.
public function __invoke(Request $request, \MyResponse $response, callable $next) {
$response = $next($request, $response);
$data = $response->getData();
// [..]
}
That would be the answer to your question. A better solution in my opinion, would be a helper class which does what your middleware does, then you could something like this:
public function getData(Request $request, Response $response): Response {
$data = $this->getUsers();
return $this->helper->formatOutput($request, $response, $data);
}
For that there is already a lib for: rka-content-type-renderer
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
I am trying to handle errors in slim framework custom middle ware but dont know how to do this like in my code i am doing that if the request is not of four specific types then through an error that i want to handel in errorHandler with appropriate message and status but currently this code is just returning the status in postman with a blank screen in response body. I am not very much familiar with SLIM. Thanks in advance
<?php
require 'vendor/autoload.php';
use \Slim\App;
$c = new \Slim\Container();
$c['notAllowedHandler'] = function ($c) {
return function ($request, $response, $methods) use ($c) {
return $c['response']
->withStatus(405)
->withHeader('Allow', implode(', ', $methods))
->withHeader('Content-type', 'application/json')
->write(json_encode(array("c_status"=>"405","message"=>"Bad Request")));
};
};
$c['notFoundHandler'] = function ($c) {
return function ($request, $response) use ($c) {
return $c['response']
->withStatus(404)
->withHeader('Content-type', 'application/json')
->write(json_encode(array("c_status"=>"404","message"=>"Not Found")));
};
};
$c['errorHandler'] = function ($c) {
return function ($request, $response, $exception) use ($c) {
$data = [
'c_status' => $response->getStatus(),
'message' => $exception->getMessage()
];
return $container->get('response')->withStatus($response->getStatus())
->withHeader('Content-Type', 'application/json')
->write(json_encode($data));
};
};
$app = new App($c);
$app->add(function ($request, $response, $next) {
if($request->isGet() || $request->isPost() || $request->isPut() || $request->isDelete()){
$response = $next($request, $response);
}else{
$response = $response->withStatus(403);
}
return $response;
});
require 'API/routes.php';
$app->run();
Throw an exception:
$app->add(function ($request, $response, $next) {
if($request->isGet() || $request->isPost() || $request->isPut() || $request->isDelete()){
$response = $next($request, $response);
}else{
throw new \Exception("Forbidden", 403);
}
return $response;
})