What I need is : Use a custom class to receive a HTTP Request and deal with it.
What I have so far:
$app->group('/user', function () use ($app) {
$app->post('/login', Auth::class . ':login');
}
And in my Auth Class:
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
// also tried: use Slim\Http\Request; use Slim\Http\Response;
class Auth {
protected $container;
public function __construct(&$c) {
$this->container = $c;
}
public function login(Request $request, Response $response, $args) {
// NEED TO GET GET/POST/PUT Params here
// When I Try to print $request, $response or $args, it returns empty
}
My code shows in the function login what I need : get the http params, and the request/response/args params. ( I want to implement many functions in one class, I mean, I don't want to use __invoke())
Another thing I don't understand is, if I do something like
return $response->withJson($somearraydata,200);
the variable $response actually works. Why?
Thanks for any help!
I think I have figured it out,
$args are never set (unless it is a get),but request and response are.
and, to get params, I could do:
$request->getParsedBody()['attribute']
Hope this helps someone. :)
Related
I've been testing the new Slim 4 framework and redirects work fine for me in normal classes, but I cannot seem to get them working in middleware, where a response is dynamically generated (apparently?) by the Request Handler. When I try to redirect with a Location header, it simply fails to redirect, and my route continues to the original location.
Here’s a basic version of my authentication middleware for testing:
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
class AuthMiddleware extends Middleware {
public function __invoke(Request $request, RequestHandler $handler): Response {
$response = $handler->handle($request);
$loggedInTest = false;
if ($loggedInTest) {
echo "User authorized.";
return $response;
} else {
echo "User NOT authorized.";
return $response->withHeader('Location', '/users/login')->withStatus(302);
}
}
}
Has anybody got this to work? And if so, how did you accomplish it? Thanks in advance.
I think I see the problem with this code.
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
class AuthMiddleware extends Middleware {
public function __invoke(Request $request, RequestHandler $handler): Response {
$response = $handler->handle($request);
$loggedInTest = false;
if ($loggedInTest) {
echo "User authorized.";
return $response;
} else {
echo "User NOT authorized.";
return $response->withHeader('Location', '/users/login')->withStatus(302);
}
}
}
When you call $handler->handle($request), that processes the request normally and calls whatever closure is supposed to handle the route. The response hasn't been completed yet, you can still append stuff to it, but the headers are already set, so you can't do a redirect, because the headers are done.
Maybe try this:
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Psr7\Response;
class AuthMiddleware extends Middleware {
public function __invoke(Request $request, RequestHandler $handler): ResponseInterface {
$loggedInTest = false;
if ($loggedInTest) {
$response = $handler->handle($request);
echo "User authorized.";
return $response;
} else {
$response = new Response();
// echo "User NOT authorized.";
return $response->withHeader('Location', '/users/login')->withStatus(302);
}
}
}
If the login test fails, we never call $handler->handle(), so the normal response doesn't get generated. Meanwhile, we create a new response.
Note that the ResponseInterface and Response can't both be called Response in the same file, so I had to remove that alias, and just call the ResponseInterface by its true name. You could give it a different alias, but I think that would only create more confusion.
Also, I commented out the echo before the redirect. I think this echo will force headers to be sent automatically, which will break the redirect. Unless Slim 4 is doing output buffering, in which case you're still not going to see it, because the redirect will immediately send you to a different page. Anyway, I commented it out to give the code the best chance of working but left it in place for reference.
Anyway, I think if you make that little change, everything will work. Of course, this post is almost a year old, so you've probably solved this on your own, switched to F3, or abandoned the project by now. But hopefully, this will be helpful to someone else. That's the whole point of StackOverflow, right?
eimajenthat is right, except that you cannot create an instance of interface.
Try this instead:
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Psr7\Response;
class AuthMiddleware extends Middleware {
public function __invoke(Request $request, RequestHandler $handler): Response {
global $app; // Assuming $app is your global object
$loggedInTest = false;
if ($loggedInTest) {
$response = $handler->handle($request);
echo "User authorized.";
return $response;
} else {
$response = $app->getResponseFactory()->createResponse();
// echo "User NOT authorized.";
return $response->withHeader('Location', '/users/login')->withStatus(302);
}
}
}
I was growing so frustrated by Slim 4 and redirect issues that I took a look at FatFreeFramework and had the exact same problem. So I knew it was something I was doing. My code was putting the app into a never-ending redirect loop. I can make it work by validating the redirect URL like so in FatFreeFramework:
class Controller {
protected $f3;
public function __construct() {
$isLoggedIn = false;
$this->f3 = Base::instance();
if ($isLoggedIn == false && $_SERVER['REQUEST_URI'] != '/login') {
$this->f3->reroute('/login');
exit();
}
}
}
Therefore, although I haven't actually taken the time to test it, I'm assuming I could fix it in Slim 4 by doing something like:
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
class AuthMiddleware extends Middleware {
public function __invoke(Request $request, RequestHandler $handler): Response {
$response = $handler->handle($request);
$loggedInTest = false;
if (!$loggedInTest && $_SERVER['REQUEST_URI'] != '/user/login') {
return return $response->withHeader('Location', '/users/login')->withStatus(302);
} else {
return $response;
}
}
}
Does anybody have another idea for how to break a continuous redirect loop? Or is the $_SERVER variable the best option?
Thanks in advance.
Use 2 response
namespace App\middleware;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Psr7\Response as Response7;
use Psr\Http\Message\ResponseInterface as Response;
final class OtorisasiAdmin {
public function __invoke(Request $request, RequestHandler $handler): Response {
$session = new \Classes\session();
$session->start();
$isAdmin=($session->has("login","admin"))?true:false;
if(!$isAdmin){
$response = new Response7();
$error = file_get_contents(__dir__."/../../src/error/404.html");
$response->getBody()->write($error);
return $response->withStatus(404);
}
$response=$handler->handle($request);
return $response;
}
}
So this is what i had first:
$app->get('/object/{id:[0-9]+}', function ($request, $response, $args) {
$id = (int)$args['id'];
$this->logger->addInfo('Get Object', array('id' => $id));
$mapper = new ObjectMapper($this->db);
$object = $mapper->getObjectById($id);
return $response->withJson((array)$object);
});
It worked well and outputted the whole DB Object as a nice JSON String.
Now i reorganized everything a little on MVC basis and this is whats left:
$app->get('/object/{id:[0-9]+}', ObjectController::class . ':show')->setName('object.show');
It also works, but i don't get any Output. If i put a var_dump before the DB Object is there, but how do i get a JSON String from that again?
Here comes the Controller
<?php
namespace Mycomp\Controllers\Object;
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
use Interop\Container\ContainerInterface;
use Mycomp\Models\Object;
class ObjectController
{
protected $validator;
protected $db;
protected $auth;
protected $fractal;
public function __construct(ContainerInterface $container)
{
$this->db = $container->get('db');
$this->logger = $container->get('logger');
}
public function show(Request $request, Response $response, array $args)
{
$id = (int)$args['id'];
$this->logger->addInfo('Get Object', array('id' => $id));
$object = new Object($this->db);
return $object->getObjectById($id);
}
}
As Nima said in comment, you need to return Response object
public function show(Request $request, Response $response, array $args)
...
return $response->withJson($object->getObjectById($id));
}
In order for Slim to send HTTP response to client, route callback must return some data that Slim understands. That type of data, according to Slim documentation is a PSR 7 Response object.
This is important, because what the route callback returns will not necessarily be sent to client exactly as is . It might be used by middlewares to teak the response before sending it to the client.
the $response object, injected by Slim into your route callbacks is used for that purpose. Slim also provides some helper methods like 'withJson` to generate a proper (PSR 7) JSON response with proper HTTP headers.
So as I said in comment, you need to return response object
public function show(Request $request, Response $response, array $args)
// Prepare what you want to return and
// Encode output data as JSON and return a proper response using withJson method
return $response->withJson($object->getObjectById($id));
}
I'm attempting to rebind what $request->user() returns, and having poked through the built in authentication code, I found a service using app->rebinding to request->setUserResolver is how it's done? I tried it myself, with no luck. I created a service (well, coopted AuthServiceProvider, and changed the register to:
public function register()
{
$this->app->rebinding('request', function ($app, $request) {
$request->setUserResolver(function () use ($app) {
$token = $this->request->bearerToken();
dd($token);
// error_log($token);
return array('user' => 1);
});
});
}
Ignoring the dd, which is there to test, how can I find where I'm going wrong? I even found a SO answer that seems to indicate this is the way to go but nothing gets dumped, nothing gets logged (when error log isn't commented out) and dumping $request->user() in my controller just returns null.
I know I can use the built in auth/guard setup, but I figured since I'm not using most of what the auth/guard setup has, why not try to learn and set it up myself? Of course, so far I've gotten nowhere. I'm going to fall back to using the built-in stuff, but I'd like to learn and improve.
As I realized it may make a difference, I'm running Lumen 5.4.
In Lumen, your App\Providers\AuthServiceProvider class comes by default with
public function boot()
{
// Here you may define how you wish users to be authenticated for your Lumen
// application. The callback which receives the incoming request instance
// should return either a User instance or null. You're free to obtain
// the User instance via an API token or any other method necessary.
$this->app['auth']->viaRequest('api', function ($request) {
if ($request->input('api_token')) {
return User::where('api_token', $request->input('api_token'))->first();
}
});
}
This is the place to define the user resolution logic. The rebinding you were registering in the register method was being supeseded by this one.
Just uncomment the $app->register(App\Providers\AuthServiceProvider::class); line in bootstrap/app.php to register your provider; don't modify the code in the vendor folder (if I understood correctly you were doing that).
Update
I now see what you mean, although I'm not sure it is really too much "load" for the auth/guard method.
However, in the interest of creating a minimal implementation, I think the solution would be overriding the prepareRequest method of the Application class.
In bootstrap/app.php replace
$app = new Laravel\Lumen\Application(
realpath(__DIR__.'/../')
);
with
$app = new class (realpath(__DIR__.'/../')) extends Laravel\Lumen\Application {
protected function prepareRequest(\Symfony\Component\HttpFoundation\Request $request)
{
if (! $request instanceof Illuminate\Http\Request) {
$request = Illuminate\Http\Request::createFromBase($request);
}
$request->setUserResolver(function () use ($request) {
return $request->bearerToken();
})->setRouteResolver(function () {
return $this->currentRoute;
});
return $request;
}
};
This way you can have the simple resolution logic for getting the bearer token (don't include the AuthServiceProvider then).
(This requires PHP 7 anonymous classes; alternatively just extend to a regular class).
You do not need to change the register() function.
Just uncomment the following lines in bootstrap/app.php file:
$app->withEloquent();
$app->register(App\Providers\AppServiceProvider::class);
$app->register(App\Providers\AuthServiceProvider::class);
$app->routeMiddleware([
'auth' => App\Http\Middleware\Authenticate::class,
]);
And in app/Providers/AuthServiceProvider.php->boot(), it has default method to retrieve the authenticated user.
$this->app['auth']->viaRequest('api', function ($request) {
if ($request->input('api_token')) {
return User::where('api_token', $request->input('api_token'))->first();
}
});
You may use an API token in the request headers or query string, a bearer token on the request, or using any other approach your application requires.
After that, you may retrieve the authenticated user like this:
use Illuminate\Http\Request;
$app->get('/post/{id}', ['middleware' => 'auth', function (Request $request, $id) {
$user = Auth::user();
$user = $request->user();
//
}]);
The rebinding method will add an additional reboundCallbacks which this callback will be triggered right after the abstract is rebound. As long as your abstract is not rebound, the reboundCallbacks are not called. So, you can simply rebound your abstract, like so:
$this->app->rebinding('request', function ($app, $request) {
$request->setUserResolver(function () use ($app) {
$token = $this->request->bearerToken();
dd($token);
// do the rest
});
});
// REBOUND HERE
$this->app->instance('request', $this->app->make('request'));
// TEST
// $this->app->make('request')->user(); // output is $token
Try uncomment the rebound line above, your dd will not called at all.
Extra
You can use refresh method (to register reboundCallbacks) combined with extend method (to rebound) for cleaner code:
public function register()
{
parent::register();
$this->app->refresh('request', $this, 'overrideUserResolver');
// REBOUND HERE, JUST ANOTHER WAY TO REBOUND
$this->app->extend('request', function ($request) { return $request; });
// TEST
$this->app->make('request')->user();
}
public function overrideUserResolver($request)
{
$request->setUserResolver(function ($guard = null) use ($request) {
$token = $request->bearerToken();
dd($token);
// do the rest
});
}
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.
I can't use the Request class in laravel for a Ajax request and the input request.
I'm trying to call a ajax request to the controller and this works until I wanted to request the data that has been posted to the controller. This has somthing to do with the Request class that I use.
use Request;
This class is used by the Ajax Request
use Illuminate\Http\Request;
This is the class used to request the input.
The problem is that I cannot use them both.
public function postQuestion(Request $request) {
//dd($request->answer);
if(Request::ajax()) {
// $answer = new Answers;
// $answer->answer = $request->answer;
// $answer->description = "Test";
// $answer->Questions_id = 1;
// $answer->save();
return Response::json($request->answer);
}
}
This is my code what i've wrote.
Anyone seeing somthing familiar? Or has a answer for it?
Issue turned out to be not being able to use two classes with same namespace. For such a case, PHP provides the as keyword.
use Illuminate\Http\Request as HttpRequest;
use Some\Other\Namespace\Request;
then in code both these classes can be used. E.g. HttpRequest::method() and Request::method()
you can use?
public function postQuestion(Requests\ModelRequest $request) { //your logic }
replace your Model in Requests\ModelRequest