I'm building a simple app based on the excellent Slim 4 Tutorial https://odan.github.io/2019/11/05/slim4-tutorial.html
The structure is still fairly similar to the tutorial.
Every time I call any of my endpoints using Postman it executes my Actions twice (ie: \App\Action\UserGetAction) for a Get. I can confirm this is happening in my logs.
if I comment out the AuthMiddleware file in config/middleware.php the duplicate action stops happening.
Any ideas?
I have created my own Auth middleware and added it in the config/middleware.php file:
use Selective\BasePath\BasePathMiddleware;
use Slim\App;
use Slim\Middleware\ErrorMiddleware;
use SlimSession\Helper;
use App\Factory\SessionFactory;
use App\Factory\AuthMiddleware;
return function (App $app) {
// Parse json, form data and xml
$app->addBodyParsingMiddleware();
// Add the Slim built-in routing middleware
$app->addRoutingMiddleware();
$app->add(BasePathMiddleware::class);
// Catch exceptions and errors
$app->add(ErrorMiddleware::class);
$app->add(AuthMiddleware::class); // <--- here
$loggerFactory = $app->getContainer()->get(\App\Factory\LoggerFactory::class);
$logger = $loggerFactory->addFileHandler('error.log')->createInstance('error');
$errorMiddleware = $app->addErrorMiddleware(true, true, true, $logger);
};
for simplicity I have stripped out pretty much everything in the AuthMiddleware:
namespace App\Factory;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Psr7\Response;
use GuzzleHttp\Client;
use Exception;
use App\Factory\LoggerFactory;
class AuthMiddleware
{
/**
* #var LoggerInterface
*/
private $logger;
public function __construct(LoggerFactory $logger)
{
$this->logger = $logger
->addFileHandler('user_edit.log')
->addConsoleHandler()
->createInstance('user_edit');
}
/**
* Example middleware invokable class
*
* #param ServerRequest $request PSR-7 request
* #param RequestHandler $handler PSR-15 request handler
*
* #return Response
*/
public function __invoke(Request $request, RequestHandler $handler): Response
{
$response = $handler->handle($request);
$headers = $request->getHeaders();
$eauth = $headers["Authorization"][0];
$this->logger->info($eauth);
return $handler->handle($request);
}
} //Class
here are my routes in config/routes.php:
$app->post('/users', \App\Action\UserCreateAction::class);
$app->map(['PUT', 'PATCH'],'/users/{id}[/{system}]', \App\Action\UserUpdateAction::class);
$app->delete('/users/{id}[/{system}]', \App\Action\UserDeleteAction::class);
$app->get('/users/{id}[/{system}]', \App\Action\UserGetAction::class);
$app->get('/extid/{id}[/{system}]', \App\Action\ExtidGetAction::class);
I have confirmed that I'm only running $app-run() once --- at the end of my index.php
require __DIR__ . '/../config/bootstrap.php';
$app->run();
There is error in __invoke method of your middleware.
You call handler queue twice:
public function __invoke(Request $request, RequestHandler $handler): Response
{
// here you call handler queue first time
// so you get response
$response = $handler->handle($request);
$headers = $request->getHeaders();
$eauth = $headers["Authorization"][0];
$this->logger->info($eauth);
// here you call handle queue for the second time
// so you get the second duplicated version of response
// this is the reason why you actions called twice
return $handler->handle($request);
}
One of the solutions is below:
public function __invoke(Request $request, RequestHandler $handler): Response
{
// here you call handler queue first time
// so you get response
$response = $handler->handle($request);
$headers = $request->getHeaders();
$eauth = $headers["Authorization"][0];
$this->logger->info($eauth);
// return response you already have
return $response;
}
Related
Slim 4 is already here and I am trying to move to Slim 4. Everything is great, but CSRF returns an error when i try to implement it. I tried the simplest setup, but I get this error:
Message: Argument 2 passed to Slim\Csrf\Guard::__invoke() must be an instance of Psr\Http\Message\ResponseInterface, instance of Slim\Routing\RouteRunner given, called in /Volumes/Web/slim/vendor/slim/slim/Slim/MiddlewareDispatcher.php on line 180
File: /Volumes/Web/slim/vendor/slim/csrf/src/Guard.php
Here is my code:
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
use Slim\Csrf\Guard;
require __DIR__ . '/../vendor/autoload.php';
/**
* Instantiate App
*
* In order for the factory to work you need to ensure you have installed
* a supported PSR-7 implementation of your choice e.g.: Slim PSR-7 and a supported
* ServerRequest creator (included with Slim PSR-7)
*/
$app = AppFactory::create();
$app->add(Guard::class);
// Add Routing Middleware
$app->addRoutingMiddleware();
/*
* Add Error Handling Middleware
*
* #param bool $displayErrorDetails -> Should be set to false in production
* #param bool $logErrors -> Parameter is passed to the default ErrorHandler
* #param bool $logErrorDetails -> Display error details in error log
* which can be replaced by a callable of your choice.
* Note: This middleware should be added last. It will not handle any exceptions/errors
* for middleware added after it.
*/
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
// Define app routes
$app->get('/', function (Request $request, Response $response, $args) {
$response->getBody()->write('Hello');
return $response;
});
// Run app
$app->run();
Any help is greatly appreciated! Thanks!
The package is not compatible with Slim4. I wrote a wrapper so you can use it.
`
<?php
declare(strict_types=1);
namespace App\Application\Middleware;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface as Middleware;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Csrf\Guard as Guard;
class CsrfMiddleware extends Guard implements Middleware
{
/**
* Process middleware
*
* #param ServerRequestInterface $request request object
* #param RequestHandlerInterface $handler handler object
*
* #return ResponseInterface response object
*/
public function process(Request $request, RequestHandler $handler): Response
{
$this->validateStorage();
// Validate POST, PUT, DELETE, PATCH requests
if (in_array($request->getMethod(), ['POST', 'PUT', 'DELETE', 'PATCH'])) {
$body = $request->getParsedBody();
$body = $body ? (array) $body : [];
$name = isset($body[$this->prefix . '_name']) ? $body[$this->prefix . '_name'] : false;
$value = isset($body[$this->prefix . '_value']) ? $body[$this->prefix . '_value'] : false;
if (!$name || !$value || !$this->validateToken($name, $value)) {
// Need to regenerate a new token, as the validateToken removed the current one.
$request = $this->generateNewToken($request);
$failureCallable = $this->getFailureCallable();
return $failureCallable($request, $handler);
}
}
// Generate new CSRF token if persistentTokenMode is false, or if a valid keyPair has not yet been stored
if (!$this->persistentTokenMode || !$this->loadLastKeyPair()) {
$request = $this->generateNewToken($request);
} elseif ($this->persistentTokenMode) {
$pair = $this->loadLastKeyPair() ? $this->keyPair : $this->generateToken();
$request = $this->attachRequestAttributes($request, $pair);
}
// Enforce the storage limit
$this->enforceStorageLimit();
return $handler->handle($request);
}
/**
* Getter for failureCallable
*
* #return callable|\Closure
*/
public function getFailureCallable()
{
if (is_null($this->failureCallable)) {
$this->failureCallable = function (Request $request, RequestHandler $handler): Response {
$response = $handler->handle($request);
$stream = $response->getBody();
$stream->write('CSRF fail');
return $response->withStatus(400);
};
}
return $this->failureCallable;
}
}
`
The relevant bit is:
$app->add(Guard::class);
The signature of middleware callbacks has changed. In Slim/3 it used to be like this:
public function __invoke(
ServerRequestInterface $request,
ResponseInterface $response,
callable $next
): ResponseInterface
... and then the method had to call $next like $next($request, $response).
In Slim/4 it's like this:
public function __invoke(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface
.. and the internal call to $handler is $handler->handle($request).
The library does not seem to have been updated for Slim/4. It declares Slim/3 as dev (?) dependency in composer.json and mentions in README.md. Perhaps it isn't very difficult to either fix the library or write a compatible wrapper on top of it but if you aren't familiar with the overall ecosystem it's probably easier to install a replacement.
I'm using a controller/middleware build with slim 3 and i want from the middleware attached to a group, to pass some data to the $args parameter in my controller - action.
Here's some code:
class MyController
{
protected $container;
public function __construct(ContainerInterface $container) {
$this->container = $container;
}
public function index(Request $request, Response $response, $args) {
return $this->container->get('renderer')->render($response, "index.html.twig", $args);
}
}
class MyMiddleware
{
public function __invoke(Request $request, Response $response, $next)
{
// do some stuff to inject further down to $args some data
return $next($request, $response);
}
}
$app->group('/group', function () use ($app){
//routes
})->add(new MyMiddleware());
My use case is to send stuff to all the views rendered by the actions of these controllers, so i'm also fine with other ways to do this :)
Thanks.
so you need just pass data from Middleware to Controller
what about
class MyMiddleware
{
public function __invoke(Request $request, Response $response, $next)
{
$request = $request->withAttribute('myMagicArgument', 42);
return $next($request, $response);
}
}
and then in controller
class MyController
{
//...
public function index(Request $request, Response $response) {
$yourAttributeFromMiddleware = $request->getAttribute('myMagicArgument');
//...
}
}
For completeness, I'm going to extend the excellent answer given #jDolba
Unfortunately, though it got me going in the right direction, it still took a little experimentation to get everything working.
Basically, as explained in the slim router docs
The route callback signature is determined by a route strategy. By
default, Slim expects route callbacks to accept the request, response,
and an array of route placeholder arguments. This is called the
RequestResponse strategy. However, you can change the expected route
callback signature by simply using a different strategy. As an
example, Slim provides an alternative strategy called
RequestResponseArgs that accepts request and response, plus each route
placeholder as a separate argument. Here is an example of using this
alternative strategy; simply replace the foundHandler dependency
provided by the default \Slim\Container:
$c = new \Slim\Container();
$c['foundHandler'] = function() {
return new \Slim\Handlers\Strategies\RequestResponseArgs();
};
$app = new \Slim\App($c);
$app->get('/hello/{name}', function ($request, $response, $name) {
return $response->write($name);
});
You can provide your own route strategy by implementing the
\Slim\Interfaces\InvocationStrategyInterface.
however, for the task of injecting some standardised data into the $args[] array, the default \Slim\Handlers\Strategies\RequestResponse class does everything it needs to minus injecting the data.
As such, I simply extended that class:
<?php
namespace MyProject\Handlers\Strategies;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use \Slim\Handlers\Strategies\RequestResponse;
class SomeNewInvocationStrategy extends RequestResponse
{
/**
* Invoke a route callable.
*
* #param callable $callable The callable to invoke using the strategy.
* #param ServerRequestInterface $request The request object.
* #param ResponseInterface $response The response object.
* #param array $routeArguments The route's placholder arguments
*
* #return ResponseInterface|string The response from the callable.
*/
public function __invoke( callable $callable, ServerRequestInterface $request, ResponseInterface $response, array $routeArguments)
{
$routeArguments['test'] = 'testing testing 123';
return parent::__invoke( $callable, $request, $response, $routeArguments );
}
}
My container declaration looks like this:
<?php
use Slim\App;
return function (App $app) {
$container = $app->getContainer();
$container['foundHandler'] = function() {
return new MyProject\Handlers\Strategies\SomeNewInvocationStrategy();
};
}
Then in all of my controller actions I have access to $args['test']. Further, this can be passed straight through to any Twig views.
This is useful for tasks like access control where by I always want to load the roles of users before processing the request but I'm sure there'll be many other use-cases for it.
I Hope this helps somebody.
I decided to implement my own small framework to implement such stuff like dependency injection etc.
Now I'm stucking at my middleware implementation. I can add middleware to a route but I im wondering how slim loops through the attached middleware.
I'd like to do it the slim way, so in every middleware I can return a request or a response or the next middleware. But how do I have too iterate over my attached middleware.
Here is my stack I want to proceed
class MiddlewareStack
{
private $stack;
public function addMiddleware(Middleware $middleware)
{
$this->stack[] = $middleware;
}
public function processMiddleware(Request $request, Response $response)
{
}
}
and thats the middleware interface
public function __invoke(Request $request, Response $response, $next);
I want to
return $next($request,$response);
in my middleware classes or just a response or a request.
Here's how to create middlware callable in slim.
http://www.slimframework.com/docs/concepts/middleware.html#invokable-class-middleware-example
Slim 3 first adds itself to the stack which is the Slim\App#__invoke() which executes the route.
Then, when you add a middleware it does the following: (before this slim wrapps the callable (annonymous function/invokable class) inside a DeferredCallable which helps to execute both the function and class equally (See Slim\App#add()).
protected function addMiddleware(callable $callable) // $callable is a DeferredCallable
{
$next = $this->stack->top(); // when it the first middleware this would be the route execution
$this->stack[] = function (ServerRequestInterface $req, ResponseInterface $res) use ($callable, $next) {
$result = call_user_func($callable, $req, $res, $next);
return $result;
};
}
(This is only the simple code, for full code see: Slim\MiddlewareAwareTrait#addMiddleware())
So the middleware which is on top of the stack executes the other middleware as well, because it is provided in the next method.
Then when you want to execute the middleware, get the middleware which is on top of the stack and execute it.
$start = $this->stack->top();
$resp = $start($req, $res);
// $resp is now the final response.
(see Slim\MiddlewareAwareTrait#callMiddlewareStack())
I am currently writing a REST API using Slim Framework 3 and implementing Middleware for basic authentication.
My routing goes like this:
$app->group('/api', function () use ($app, $pdo) {
$this->group('/v1', function () use ($app, $pdo) {
// Guest Routes
$this->group('', function() use ($app, $pdo) {
require_once '../app/api/v1/authentication.php';
});
// Authenticated Routes
$this->group('', function() use ($app, $pdo) {
require_once '../app/api/v1/test.php';
})->add(new \App\Middleware\AuthMiddleware($pdo));
});
});
In the AuthMiddleware class I am using the __invoke method in the following way:
namespace App\Middleware;
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
class AuthMiddleware extends Middleware {
/**
* #param Request $request
* #param Response $response
* #param $next
* #return Response
*/
public function __invoke(Request $request, Response $response, $next) {
$response = $next($response, $request);
return $response;
}
}
And I'm getting the following error:
Argument 1 passed to Slim\Route::__invoke() must implement interface Psr\Http\Message\ServerRequestInterface, instance of Slim\Http\Response given
on the following line:
$response = $next($response, $request);
What is happening? any ideas? I've been eating myself over this for 2 hours :(
Thanks a bunch!
Stupidly.. I noticed that on
$response = $next($response, $request);
I reversed the parameters.. should be
$response = $next($request, $response);
Blaahh... my head hurts.
I'm developing using PSR-7 (with Zend Expressive). I figured out the method
ServerRequestInterface::withAttribute()
and I was wondering why the object Response doesn't have one.
I'd like to pass metadata through middlewares after processing, on "response side".
Is there somehow to pass "attributes" on Response for post-processing? What's is the best way, following the architecture guidelines, to achieve that?
Best practise is using the request object to pass data between Middleware. The response is what is going out to the client and you want to keep this clean. The request lives only on the server and you can add (sensitive data) attributes to pass around. In case something goes wrong or you return a response early before removing the custom data, then it doesn't matter since your response is "clean".
Also if you need to pass data around: The Middleware is always executed in the order it gets from the config. This way you can make sure the request object in MiddlewareX contains the data set by MiddlewareY.
UPDATE: An example on how to pass data with the a request.
Middleware 2 sets an messenger object which Middleware 4 can use to set data which is required on the way out again.
<?php
namespace Middleware;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
class Middleware2
{
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
{
$messenger = new Messenger();
// Do something else before next middleware
if ($next) {
$response = $next($request->withAttribute(Messenger::class, $messenger), $response);
}
// Do something with the Response after it got back
// At this point the $messenger object contains the updated data from Middleware4
return $response->withHeader('Content-Language', $locale);
}
}
Middleware 4 grabs the messenger object and updates its values.
<?php
namespace Middleware;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
class Middleware4
{
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
{
$messenger = $request->getAttribute(Messenger::class);
$messenger->info('going in');
// Do something else before next middleware
if ($next) {
$response = $next($request->withAttribute(FlashMessenger::class, $messenger), $response);
}
// Do something with the Response after it got back
$messenger->info('going out');
return $response->withHeader('Content-Language', $locale);
}
}
The PSR-7 specification defines attributes only for server requests. They are mainly use to store metadata deduced from the incoming request so that they could be used later when you reach your domain layer.
On the other hand, a response is usually created in the domain layer and traverses back all the middleware stack before being actually sent to the client. So metadata added to a response would have no place where they could actually be used.
I guess that if you want to pass data from a inner middleware to an outer one, the best way is to use response headers.
Not sure if this is "best practice" but another possibility is to simply inject your data object into the middlewares.
Middleware 2 has a messenger object injected and sets some data on it:
<?php
namespace Middleware;
use Interop\Http\Server\MiddlewareInterface;
use Interop\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class Middleware2
{
private $messenger;
public function __construct(Messenger $messenger)
{
$this->messenger = $messenger;
}
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$this->messenger->foo = 'bar';
$response = $handler->handle($request);
if ($this->messenger->foo = 'baz') {
return $response->withHeader('Really-Important-Header', 'Baz');
}
return $response;
}
}
Middleware 4 changes the data:
<?php
namespace Middleware;
use Interop\Http\Server\MiddlewareInterface;
use Interop\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class Middleware4
{
private $messenger;
public function __construct(Messenger $messenger)
{
$this->messenger = $messenger;
}
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$this->messenger->foo = 'baz';
return $handler->handle($request);
}
}
You might even use one of the middlewares as the messenger.
Caveat: You have to make sure that both classes get constructed with the same messenger object. But that seems to be the case with most dependency injection containers.