Intercept Laravel Routing - php

I am busy building a Restful API in Laravel 5.1 where the API version is passed through the header. This way I can version the features rather than copy and pasting a whole route group and increment the version number.
The problem I'm having is that I would like to have versioned methods, IE:
public function store_v1 (){ }
I have added a middleware on my routes where I capture the version from the header, but now need to modify the request to choose the correct method from the controller.
app/Http/routes.php
Route::group(['middleware' => ['apiversion']], function()
{
Route::post('demo', 'DemoController#store');
}
app/Http/Middleware/ApiVersionMiddleware.php
public function handle($request, Closure $next)
{
$action = app()->router->getCurrentRoute()->getActionName();
// dd($action)
// returns "App\Http\Controllers\DemoController#store"
}
From here on, I would attach the header version to the $action and then pass it through the $request so that it reaches the correct method.
Well, that is the theory anyway.
Any ideas on how I would inject actions into the Route?

I think Middleware might not be the best place to do that. You have access to the route, but it doesn't offer a away to modify the controller method that will be called.
Easier option is to register a custom route dispatcher that handling the logic of calling controller methods based on the request and the route. It could look like that:
<?php
class VersionedRouteDispatcher extends Illuminate\Routing\ControllerDispatcher {
public function dispatch(Route $route, Request $request, $controller, $method)
{
$version = $request->headers->get('version', 'v1'); // take version from the header
$method = sprintf('%s_%s', $method, $version); // create target method name
return parent::dispatch($route, $request, $controller, $method); // run parent logic with altered method name
}
}
Once you have this custom dispatcher, register it in your AppServiceProvider:
public function register() {
$this->app->singleton('illuminate.route.dispatcher', VersionedRouteDispatcher::class);
}
This way you'll overwrite the default route dispatcher with your own one that will suffix controller method names with the version taken from request header.

Sort of a gross alternative is to create symlinks in your public folder that point back to the public folder. Use middleware to read the url and set a global config that can be used in your controllers to determine what to show. Not ideal but it works.

Related

How do I create middleware specific to route not whole controller class

I have an api and some routes are public some need to be protected via auth. I want to have them in one controller class as they are related. I can extend the controller and have beforeRoute function but it runs for any route that is in that controller. is it possible to add a middleware only to specific routes? I'm a js dev and in express I can just pass middleware functions for any route, even multiple middlewares.
class Clanky /*extends \controllers\ProtectedController */{
public function post_novy_clanek(\Base $base) {
//needs to be protected
}
public function get_clanky(\Base $base) {
}
public function get_clanek(\base $base) {
}
public function get_kategorie(\Base $base) {
}
}
PHP is new to me, I just want to know how I can implement the concepts I know from other languages and frameworks in this weird fatfree framework. Thanks.
Use can use f3-access plugin for that purpose https://github.com/xfra35/f3-access
Fatfree is not opinionated about how to do this.. other options to solve this ask might be:
Use php8 attributes on the method and check these in beforeroute.
Consider an own named route naming schema like #admin_routename and apply checking auth in beforeroute
Use f3-middleware plugin and add auth there
Extend an other admin controller that provides auth in beforeroute or use a trait.

Using Request, Response in constructor in router class Slim Framework 3

I am using Slim Framework for my application. I am using routes. All is working fine. But now I want to do some pre-process working under my constructor on Request and Response.
So that I should not rework on every function of the class. Like getting host and token in every function. I am using middle-ware for many pre-process. But I also want to do some work in class constructor. When I am trying to access request and response interface in constructor, It is showing the error, Please show me the right way of using Request and Response in a class constructor. Will I have to append $app, or will need to work with container.
If it can be done without help of middleware, It will be great for me.
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
$app->group('/products', function() {
new \Products($this);
});
And I have a class called Products.
class Products
{
public function __construct($app)
{
$app->map(['GET','POST'], '/createupdate', array($this, 'createupdate'));
//I want to use Request and Response here in constructor. But it is showing error.
$this->req_data['request_token'] = $request->getAttribute('request_token');
}
public function createupdate($request, $response, $args) {
//This is working fine.
$this->req_data['request_token'] = $request->getAttribute('request_token');
}
}
When you really want to do this, then you could get the request/response object from the container.
class Products
{
public function __construct($app)
{
$app->map(['GET','POST'], '/createupdate', array($this, 'createupdate'));
$request = $app->getContainer()->get('request');
$this->req_data['request_token'] = $request->getAttribute('request_token');
}
// [..]
}
But, also this will not make much difference $this->req_data['request_token'] is nearly as long as $request->getAttribute('request_token'); so you should use this inside the code.
Note: I expect you to set this attribute already inside middleware, so it may not be available here, because first the container will create a new request object and second cause the middleware is not run when php executes your constructor code.
When you now still want to use $this->req_data['request_token'] inside your class then you should do this:
$products = new \Products();
$app->group('/products', function() use ($products) {
$products->addRoutes($this);
})->add($products); // add the class as middleware as well to set there the class attributes (__invoke function)
class Products
{
public function addRoutes($app)
{
$app->map(['GET','POST'], '/createupdate', array($this, 'createupdate'));
}
public function __invoke($request, $response, $next) // this is middleware function
{
$this->req_data['request_token'] = $request->getAttribute('request_token');
return $next($request, $response); // next in this example would be your route function like createupdate()
}
}
Ofcourse you will get an error, $request is not defined.
public function __construct($app)
{
$app->map(['GET','POST'], '/createupdate', array($this, 'createupdate'));
// Where does $request comes from?!
$this->req_data['request_token'] = $request->getAttribute('request_token');
}
Slim way of doing pre-processing is by using middlewares.
When a route is called, it is automatically injected with the Request and Response objects (and the request route params if any), but when a class is created for the route, it is not automatically injects those instances to the constructor, so they are not available "out of the blue".
If you have pre-processing, I would stick to middlewares, it is much cleaner code (although this is my opinion).

Access current route name in Slim3 controller's class constructor

With Slim I group my controllers and generally have an abstract BaseController I extend for each group. I use class based routing:
/* SLIM 2.0 */
// Users API - extends BaseApiController
$app->post('/users/insert/' , 'Controller\Api\UserApiController:insert');
.
.
// Campaigns - extends BaseAdminController
$app->get('/campaigns/', 'Controller\CampaignController:index')->name('campaigns');
and needed to password protect some routes, at other times I needed to have a slightly different configuration. BaseApiController, BaseAdminController... etc. There were times I needed to know which route I was in so I could execute a certain behavior for just that route. In those cases I would have a helper function like so:
/* SLIM 2.0 */
// returns the current route's name
function getRouteName()
{
return Slim\Slim::getInstance()->router()->getCurrentRoute()->getName();
}
This would give me the route name that is currently being used. So I could do something like...
namespace Controller;
abstract class BaseController
{
public function __construct()
{
/* SLIM 2.0 */
// Do not force to login page if in the following routes
if(!in_array(getRouteName(), ['login', 'register', 'sign-out']))
{
header('Location: ' . urlFor('login'));
}
}
}
I cannot find a way to access the route name being executed. I found this link
Slim 3 get current route in middleware
but I get NULL when I try
$request->getAttribute('routeInfo');
I have also tried the suggested:
'determineRouteBeforeAppMiddleware' => true
I've inspected every Slim3 object for properties and methods, I can't seem to find the equivalent for Slim3, or get access to the named route. It doesn't appear that Slim3 even keeps track of what route it executed, it just... executes it.
These are the following methods the router class has and where I suspect this value would be:
//get_class_methods($container->get('router'));
setBasePath
map
dispatch
setDispatcher
getRoutes
getNamedRoute
pushGroup
popGroup
lookupRoute
relativePathFor
pathFor
urlFor
I was hoping someone has done something similar. Sure, there are other hacky ways I could do this ( some I'm already contemplating now ) but I'd prefer using Slim to give me this data. Any Ideas?
NOTE: I'm aware you can do this with middleware, however I'm looking for a solution that will not require middleware. Something that I can use inside the class thats being instantiated by the triggered route. It was possible with Slim2, was hoping that Slim3 had a similar feature.
It's available via the request object, like this:
$request->getAttribute('route')->getName();
Some more details available here
The methods in your controller will all accept request and response as parameters - slim will pass them through for you, so for example in your insert() method:
use \Psr\Http\Message\ServerRequestInterface as request;
class UserApiController {
public function insert( request $request ) {
// handle request here, or pass it on to a getRouteName() method
}
}
After playing around I found a way to do it. It may not be the most efficient way but it works, and although it uses Middleware to accomplish this I think there are other applications for sharing data in the Middleware with controller classes.
First you create a middleware but you use a "Class:Method" string just like you would in a route. Name it whatever you like.
//Middleware to get route name
$app->add('\Middleware\RouteMiddleware:getName');
Then your middleware:
// RouteMiddleware.php
namespace Middleware;
class RouteMiddleware
{
protected $c; // container
public function __construct($c)
{
$this->c = $c; // store the instance as a property
}
public function getName($request, $response, $next)
{
// create a new property in the container to hold the route name
// for later use in ANY controller constructor being
// instantiated by the router
$this->c['currentRoute'] = $request->getAttribute('route')->getName();
return $next($request, $response);
}
}
Then in your routes you create a route with a route name, in this case I'll use "homePage" as the name
// routes.php
$app->get('/home/', 'Controller\HomeController:index')->setName('homePage');
And in your class controller
// HomeController.php
namespace Controller;
class HomeController
{
public function __construct($c)
{
$c->get('currentRoute'); // will give you "homePage"
}
}
This would allow you to do much more then just get a route name, you can also pass values from the middleware to your class constructors.
If anyone else has a better solution please share!
$app->getCurrentRoute()->getName();
$request->getAttribute('route')->getName();

Slim PHP Route in Middleware

In Slim is it possible to get the current route within middleware?
class Auth extends \Slim\Middleware{
public function call(){
$currentRoute = $this->app->getRoute(); // Something like this?
}
}
I know you can call $app->router()->getCurrentRoute() after the slim.before.dispatch hook is called, but when you call this from middleware it returns a non-object. Any help would be greatly appreciated.
Yes and no. If you look at the source code for Slim, you will see that registered Middlewares are called in LIFO order when the Slim::run method is called, and then Slim runs it's own "call" method where the processing of the request begins. It is in this method that Slim parses and processes the route. In which case, you cannot access $app->router()->getCurrentRoute() in the Middleware::call method because it won't have been parsed and defined yet.
The only way to do this is to register a listener on slim.before.dispatch inside your Middleware, and implement whatever you want to do in that method.
From the name of your class I assume you are trying to create a basic authentication module? I've done something similar to this before, and it went something like this:
class AuthMiddleware extends \Slim\Middleware
{
public function call()
{
$this->app->hook('slim.before.dispatch', array($this, 'onBeforeDispatch'));
$this->next->call();
}
public function onBeforeDispatch()
{
$route = $this->app->router()->getCurrentRoute();
//Here I check if the route is "protected" some how, and if it is, check the
//user has permission, if not, throw either 404 or redirect.
if (is_route_protected() && !user_has_permission())
{
$this->app->redirect('/login?return=' . urlencode(filter_input(INPUT_SERVER, 'REQUEST_URI')));
}
}
}
In this example, the onBeforeDispatch method will be run before of the route handlers are invoked. If you look at the source code, you can see the events are fired inside a try/catch block that is listening for the exceptions thrown by $app->redirect() and $app->pass(), etc. This means we can implement our check/redirect logic here just as if this was a route handler function.
Above is_route_protected and user_has_permission are just pseudo-code to illustrate how my auth middleware worked. I structured the class so that you could specify a list of routes or regex for routes in the Middleware constructor that were protected, as well as passing a service object that implemented the user permission checking, etc. Hope this helps.
There is an alternative method of doing this, as I've been in the same situation. What I wanted to avoid was matching anything by route and wanted to use route names instead, so you could try the following:
public function call() {
$routeIWantToCheckAgainst = $this->slimApp->router()->urlFor('my.route.name');
$requestRoute = $this->slimApp->request()->getPathInfo();
if ($routeIWantToCheckAgainst !== $requestRoute) {
// Do stuff you need to in here
}
$this->next->call();
}
You could even have an array of routes you DON'T want the middleware to run on and then just check if it's in_array() etc and if not, do what you need to.
You should use app->request()->getPathInfo() instead of app->getRoute().
class Auth extends \Slim\Middleware{
public function call(){
$currentRoute = $this->app->request()->getPathInfo();
}
}

Silex: routing variable assignment issue

I recently wrote a post about request forwarding in Silex, which used a blog example to explain sub requests in Silex.
I use a slightly modified version of this example for a domain controller.
The path to the domain endpoint = /product/domain
A domain can also have a webhosting package attached to it.
The url path for this endpoint would be /product/domain/(id)/webhosting/
You can fetch info about a webhosting package by using the url path.
The url path for this endpoint would be /product/domain/(id)/webhosting/(id)
To handle these sub requests, I have a method called forwardRequest, which has no parameters in it's method signature, but uses func_get_args to keep it dynamic.
Unfortunately this doesn't work as Silex uses the named parameters in your route to call your method. So if you have /product/domain/domain_id/webhosting/webhosting_id, your method should have a signature of method($domain_id, $webhosting_id), which is a PITA if you want to forward multiple endpoints through one method. If you have additional /product/domain/domain_id/emailhosting and /product/domain/domain_id/dns endpoints, you have to create a method for each in order to forward the request.
Does anyone have a solution in which I can use only 1 method to forward all these sub requests?
Note: I'm using PHP 5.3.
The part of silex that decides which arguments to pass to the controller is called the "controller resolver". The default controller resolver uses reflection. You can override the controller_resolver service with a custom implementation though.
Defining a custom controller resolver that wraps the existing one but replaces the arguments with a single one, the request:
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
class RequestArgumentControllerResolver implements ControllerResolverInterface
{
protected $resolver;
public function __construct(ControllerResolverInterface $resolver)
{
$this->resolver = $resolver;
}
public function getController(Request $request)
{
return $this->resolver->getController($request, $controller);
}
public function getArguments(Request $request, $controller)
{
return [$request];
}
}
Extend the existing controller resolver with the newly defined decorator:
$app['controller_resolver'] = $app->share($app->extend('controller_resolver', function ($resolver, $app) {
return new RequestArgumentControllerResolver($resolver);
}));
Note: This is just one way of doing it. You don't have to decorate, you can also replace the resolver completely if you like. And obviously this is just a very basic example of only passing a single arg to the controller, you can do something more sophisticated.

Categories