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.
Related
A lot of pieces to this so here's the meat. Code very slightly tweaked for brevity.
Extended class:
<?php
namespace App\Http;
use Illuminate\Http\Request as LaravelRequest;
class Request extends LaravelRequest
{
}
Middleware:
<?php
namespace App\Http\Middleware;
use App\Http\Request as CustomizedRequest;
use Closure;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Request;
class CustomizeRequest
{
protected $app;
protected $customizedRequest;
public function __construct(Application $app, CustomizedRequest $customizedRequest){
$this->app = $app;
$this->customizedRequest = $customizedRequest;
}
public function handle(Request $request, Closure $next){
$this->app->instance(
'request',
Request::createFrom($request, $this->customizedRequest);
);
return $next($this->customizedRequest);
}
}
Routes:
Route::get('/books1/{id}',[BookController::class, 'frontend1']);
Route::get('/books2/{id}',[BookController::class, 'frontend2']);
Controller:
<?php
namespace App\Http\Controllers;
use App\Models\Book;
class BookController extends Controller
{
public function frontend1(\Illuminate\Http\Request $request){
dump($request);
dump($request->all());
dump($request->route('id'));
return Book::all();
}
public function frontend2(\App\Http\Request $request){
dump($request);
dump($request->all());
dump($request->route('id'));
return Book::all();
}
}
The /books1/5?foo=bar and frontend1() path works. $request is populated as expected.
The /books2/5?foo=bar and frontend2() path is broken. $request has vast amounts of missing data, like it was instantiated with nothing.
Evidently if I type-hint my subclass instead of the more generic parent, it's causing some kind of broken instantiation. From an OO perspective I think this should be perfectly fine and I do specifically need my subclass being provided so prefer that type-hint. Is something deep within Laravel tripping this up? Is this some obscure PHP behavior I haven't seen before?
This is kind of tricky.
First of all, you need to be familiar with the service container and dependency injection. Here is the full doc: https://laravel.com/docs/8.x/container
When you type hint a class inside a controller method, Laravel will try to understand what it should do with it.
If nothing is registered inside the service container, it will try to make a new instance of it.
\Illuminate\Http\Request is bound as a singleton (https://laravel.com/docs/8.x/container#binding-a-singleton).
While a simple bind will return a new instance at each call, a singleton will always return the exact same instance.
Here is a quick demo:
\App\Models\User::class is a class that is not explicitly bound.
When you try to resolve it using the service container, it will not find it and will try to make a new instance:
$u1 = app(\App\Models\User::class);
// Searching \App\Models\User::class...
// Cannot find \App\Models\User::class...
// returning new \App\Models\User();
$u2 = app(\App\Models\User::class);
// same process again
$u3 = app(\App\Models\User::class);
// and again
// You can check these instances are indeed different by checking their hash:
dd(
spl_object_hash($u1), // 000000004af5213500000000220f0bc0 (52135)
spl_object_hash($u2), // 000000004af5213400000000220f0bc0 (52134)
spl_object_hash($u3) // 000000004af5213700000000220f0bc0 (52137)
);
But since \Illuminate\Http\Request::class is bound by Laravel, it follows a different path:
$r1 = app(\Illuminate\Http\Request::class);
// Searching \Illuminate\Http\Request::class...
// Found it! Bound as a singleton.
// returning new \Illuminate\Http\Request() and storing the
// instance in case it is required again later;
$r2 = app(\Illuminate\Http\Request::class);
// Searching \Illuminate\Http\Request::class...
// Found it and already called! Returning the stored instance ($r1)
$r3 = app(\Illuminate\Http\Request::class);
// Searching \Illuminate\Http\Request::class...
// Found it and already called! Returning the stored instance ($r1)
// Their hash are the same
dd(
spl_object_hash($u1), // 0000000011f522cf0000000077704cd1
spl_object_hash($u2), // 0000000011f522cf0000000077704cd1
spl_object_hash($u3) // 0000000011f522cf0000000077704cd1
);
Now, what's happening?
Under the hood, when a new request is made to your app and before hitting the controller method, Laravel will do a lot of things to prepare the \Illuminate\Http\Request instance.
For instance, it will setup the route resolver inside Illuminate\Routing\Router:
/**
* Return the response for the given route.
*
* #param \Illuminate\Http\Request $request
* #param \Illuminate\Routing\Route $route
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function runRoute(Request $request, Route $route)
{
// here
$request->setRouteResolver(function () use ($route) {
return $route;
});
//
$this->events->dispatch(new RouteMatched($route, $request));
return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
}
Each time Laravel internally call a method like this:
protected function method(Request $request){
// do something to $request
}
$request is always the same instance, because it is bound as a singleton.
We are now in your controller.
public function frontend1(\Illuminate\Http\Request $request){
// Searching \Illuminate\Http\Request::class...
// Found it and already called!
// Returning the stored instance that has been prepared through all
// Laravel core classes
dump($request);
dump($request->all()); //well prepared
dump($request->route('id')); //well setup
return Book::all();
}
public function frontend2(\App\Http\Request $request){
// Searching \App\Http\Request::class...
// Cannot find \App\Http\Request::class...
// returning new \App\Http\Request();
dump($request);
dump($request->all()); //nothing
dump($request->route('id')); //empty
return Book::all();
}
If you are still here, how to solve this problem?
The easiest way is to use a FormRequest, initially designed to handle form validation, but if you return an empty rules array, you should be able to do everything you did with your custom \App\Http\Request instance:
<?php
namespace App\Http;
use Illuminate\Foundation\Http\FormRequest;
class Request extends FormRequest
{
public function rules()
{
return [];
}
}
Try again, everything should work fine, since this is a feature specially designed to replace the initial \Illuminate\Http\Request object.
The full doc is here: https://laravel.com/docs/8.x/validation#creating-form-requests
I'm setting up slim router v4, and I'd like to be be able to dynamically call the controller methods, using the placeholder from the route.
I.e when a request is made to 'example.com/users/{action}', the router would call the method from Users.php controller automatically without me having to specify the routes manually.
Basically I'm trying to avoid manually adding over 100 group->get(...) when they're all under /user route.
namespace core\router;
use Slim\Interfaces\RouteCollectorProxyInterface;
use app\controllers\users;
$app->group('/user', function(RouteCollectorProxyInterface $group){
$group->get('/get-name', '\Users:name')
$group->get('/get-personality', '\Users:personality');
});
Further explanation is provided here but I'm not sure how to go about this.
The way I would suggest doing this is having a single, catch all route with a placeholder. You can then set action to an invocable controller, and execute a method based on the route parameter.
Route:
$app->get('/user/{method}', Users::class);
Controller
class Users
{
public function __invoke(Request $request, Response $response, $args)
{
if (empty($args['method'])) {
throw new InvalidArgumentException();
}
$methodName = toCamelCase($args['method']);
if (!method_exists($this, $methodName)) {
throw new InvalidArgumentException();
}
return $this->{$methodName};
}
public function getName(Request $request, Response $response)
{
// ...
}
public function getPersonality(Request $request, Response $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 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'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.