I am using silex to create an api and my routes look something similar to this:
$api = $app['controllers_factory'];
$users = $app['controllers_factory'];
$users->match('/', UsersController::action);
$api->mount('/users', $users);
$app->mount('/api', $api);
So the route would be baseurl/api/users
What I want to do now is to attach a before() to the $api controller group and enforce validation for the api, so any link prefixed with /api/...users, posts
would require validation.
But it seems that is not the way it works, when i put a before to the $api, it only works for the root of /api, not api/users or api/posts or api/categories, they require their own middlewares.
So my question is : how can I enforce everything after baseurl/api/... to require validation in a Silex enviroment.
You can add before() to your $app instance (see http://silex.sensiolabs.org/doc/middlewares.html#before-middleware).
And test the $request object to see if authentication is required.
You could also use the SecurityServiceProvider, but that may be overkill depending on your use case.
Related
Per http://www.slimframework.com/docs/concepts/middleware.html, one can add middleware to an application, route, or group.
How should one add middleware to all routes (i.e. to the application), but exclude it from specific routes?
EDIT. As a possible solution, I am thinking of adding some logic in my application middleware to bypass the middleware functionality. Getting the method is easy enough using $request->getMethod(), however, the other URL methods described by http://www.slimframework.com/docs/objects/request.html are not available in the middleware.
I think you need to add middleware to group of route, sample admin route group and user group.
If you would like to change the behaviour of dynamic you have to modify your middleware logic to check your specific request,
sample your route returns JSON if client set type of request.
EDIT:
$app = new \Slim\App();
//pass $app to middleware
$app->add(function ($request, $response, $next) use ($app) {
//do what you want with $app.
//reads config file with routes for exclude
$response = $next($request, $response);
return $response;
});
$app->get('/', function ($request, $response, $args) {
$response->getBody()->write(' Hello ');
return $response;
});
$app->run();
I believe, as of writing this post (I'm using Slim 3.12.1), the answer to your question is it's not possible to remove a previously added middleware from a route.
The solution suggested by the accepted answer makes middleware implementation tightly coupled with route definition. If route (or in similar suggested solution, route name) changes, then middleware code needs to change as well. This is due to the fact that the middleware is technically not removed from route middleware stack. This gets complicated in some situations. For example, if the middleware is written by someone else. You probably want to avoid changing code of a third party library, so it's possible that you end up extending the third party middleware to override its functionality in some way, or some similar solution, yet to make the implementation coupled with your route definitions.
My suggestion is, instead of changing the logic of your middleware to prevent its execution for some routes, add logic to your code to control which middlewares should be added to which routes. Here is a fully functional example to demonstrate how to add a common middleware to all routes while adding some middlewares only to some specific routes but not all (the ones you meant to exclude). To keep things simple, there is no logic for determining which middlewares should be added to which routes, but this demonstrates the general idea. Here are the assumptions:
paths starting with /user should be protected by Authorization middleware
/user/login and /user/signup are exceptions and do not need to be protected by Authentication middleware. Instead, we want to allow login/signup attempts from specific IP addresses only, so we need to protect this route with IP Access Control middleware
all requests to all paths starting with /user should be logged using Logger middleware (no exception here)
other routes don't need any middleware (so we should avoid using $app->add())
<?php
require __DIR__ . '/../vendor/autoload.php';
$app = new Slim\App;
// A simple middleware to write route path and middleware name
class SampleMiddleware
{
public function __construct($name)
{
$this->name = $name;
}
public function __invoke($request, $response, $next)
{
$response->write($request->getUri()->getPath() . ' invokes ' . $this->name . "\n");
return $next($request, $response);
}
}
// Three middleware instances with different names
$auth = new SampleMiddleware('Authorization');
$ipAC = new SampleMiddleware('IP Access Control');
$logger = new SampleMiddleware('Logger');
$group = $app->group('/user', function($app) {
$app->get('/profile', function($request, $response, $args){
return $response;
});
$app->get('/messages', function($request, $response, $args){
return $response;
});
})->add($auth)
->add($logger);
$group = $app->group('/user', function($app) {
$app->get('/login', function($request, $response, $args){
return $response;
});
$app->get('/signup', function($request, $response, $args){
return $response;
});
})->add($ipAC)
->add($logger);
$app->get('{p:.*}', function($request, $response, $args){
$response->write('No middleware for ' . $request->getUri()->getPath());
return $response;
});
$app->run();
Output:
/user/profile
/user/profile invokes Logger
/user/profile invokes Authorization
/user/login
/user/login invokes Logger
/user/login invokes IP Access Control
/something/else
No middleware for /something/else
You can access the uri inside middleware with the following.
$uri = $request->getUri()->getPath();
With that information you can return from the middleware early if the $uri matches path you want to exclude from the middleware.
If you want to add a global middleware the easiest way to achieve this is the following.
$app->group('', function () {
//Add your routes here, note that it is $this->get() ... etc inside the closure
})->add(MyNewMiddlewareClass::class);
Docs say to register middleware in app/Http/Kernel.php. How do I register middleware outside of the framework?
I'm not quite sure what you mean by outside of the framework but it pretty much comes down to this:
// Get your Illuminate\Routing\Router from somewhere.
// This could either be from the container instance or
// an instance you instantiated yourself somewhere
$router = new Illuminate\Routing\Router(....
// or in case you plan on doing this in a service provider for example
$router = $this->app['router'];
// Now you add your middleware using the following syntax
$router->middleware('name', My\Middleware::class);
// It's also possible to add a middleware group
$router->middlewareGroup('group_name', [
'name',
My\Other\Middleware::class
]);
// It's also possible to do this using the Laravel Facade's
Route::middleware(...
Route::middlewareGroup(...
is theoretically possible use more services for routing?
For example if somebody use Silex and has this code:
$app = new Silex\Application();
$app->get('/test/{id}', function ($id) {
// ...
});
$app->run();
And i create api using Slim like that:
$app = new \Slim\Slim();
$app->get('/api/' . $version . 'something', function () use ($app){
$data = $app->request->params();
});
$app->run();
How user could implement my API withou rewrite Slim route function to Silex route function?
Thank you very much.
Giving a quick though, the way I see it you have 3 options:
Refactor controller closures to a named function
Both, Silex and Slim[1] can use any form of callable, so instead of passing a closure just pass a function name (or an array with class name, method name) or any other callable. This way with 1 declaration you'll be able to call it from both Slim and Silex (yes, you have to define the routes on both sides).
This has its drawbacks as the controller signature is different for the 2 frameworks, so you'll need to hook into silex flow and change the parameters (you have the Kernel.controller event to do that).
Also you'll need to redefine all your services and use the container wisely (i.e, don't use it as a service locator in your controllers).
This is probably the most inflexible way.
Define the routes in Silex and instatiate a Slim APP inside to call and return that
You'll need to define the api routes again in Silex and in each route you can try to instatiate the Slim APP (using something as require "path/to/the/slim/app/php/file.php") and then force the run method with the silent option on[2]
You'll probably need to refactor your Slim application a bit, you'll have to define your app in a file and call the run method in another.
Create a Silex middleware to handle all the /api/ calls
The easiest (?) way that I can think of is redefine the second option and create a catch all incoming request to the /api/ mount point by creating a Silex middleware that checks for each incoming request if the request has the /api/ string inside the request. Then you'll simply forawrd the request to the Slim application as I've told you in the second option.
Using this method you don't need to redefine the routes in Silex as everything under the /api/ point will be forwarded to the Slim application.
NOTE
In all cases you'll probably need to transform the response given from Slim to a Symfony response. Slim 3 uses the new PSR-7 HTTP interface so you have a solution here already. Slim 2 echos the response directly so you'll need to catch that and put it inside a Symfony response (new Response(ob_get_clean())
[1] This is for the Slim3, Slim2 may also be able to do that but I'm not sure (and the signature is different!)
[2] Again this is for Slim3, Slim2 does not has this option, so you'll need to figure something to get the response (maybe ob_get_clean()?)
In Laravel, we can get route name from current URL via this:
Route::currentRouteName()
But, how can we get the route name from a specific given URL?
Thank you.
A very easy way to do it Laravel 5.2
app('router')->getRoutes()->match(app('request')->create('/qqq/posts/68/u1'))->getName()
It outputs my Route name like this slug.posts.show
Update: For method like POST, PUT or DELETE you can do like this
app('router')->getRoutes()->match(app('request')->create('/qqq/posts/68/u1', 'POST'))->getName()//reference https://github.com/symfony/http-foundation/blob/master/Request.php#L309
Also when you run app('router')->getRoutes()->match(app('request')->create('/qqq/posts/68/u1', 'POST')) this will return Illuminate\Routing\Route instance where you can call multiple useful public methods like getAction, getValidators etc. Check the source https://github.com/illuminate/routing/blob/master/Route.php for more details.
None of the solutions above worked for me.
This is the correct way to match a route with the URI:
$url = 'url-to-match/some-parameter';
$route = collect(\Route::getRoutes())->first(function($route) use($url){
return $route->matches(request()->create($url));
});
The other solutions perform bindings to the container and can screw up your routes...
I don't think this can be done with out-of-the-box Laravel. Also remember that not all routes in Laravel are named, so you probably want to retrieve the route object, not the route name.
One possible solution would be to extend the default \Iluminate\Routing\Router class and add a public method to your custom class that uses the protected Router::findRoute(Request $request) method.
A simplified example:
class MyRouter extends \Illuminate\Routing\Router {
public function resolveRouteFromUrl($url) {
return $this->findRoute(\Illuminate\Http\Request::create($url));
}
}
This should return the route that matches the URL you specified, but I haven't actually tested this.
Note that if you want this new custom router to replace the built-in one, you will likely have to also create a new ServiceProvider to register your new class into the IoC container instead of the default one.
You could adapt the ServiceProvider in the code below to your needs:
https://github.com/jasonlewis/enhanced-router
Otherwise if you just want to manually instantiate your custom router in your code as needed, you'd have to do something like:
$myRouter = new MyRouter(new \Illuminate\Events\Dispatcher());
$route = $myRouter->resolveRouteFromUrl('/your/url/here');
It can be done without extending the default \Iluminate\Routing\Router class.
Route::dispatchToRoute(Request::create('/your/url/here'));
$route = Route::currentRouteName();
If you call Route::currentRouteName() after dispatchToRoute call, it will return current route name of dispatched request.
In Laravel, we can get route name from current URL via this:
Route::currentRouteName()
But, how can we get the route name from a specific given URL?
Thank you.
A very easy way to do it Laravel 5.2
app('router')->getRoutes()->match(app('request')->create('/qqq/posts/68/u1'))->getName()
It outputs my Route name like this slug.posts.show
Update: For method like POST, PUT or DELETE you can do like this
app('router')->getRoutes()->match(app('request')->create('/qqq/posts/68/u1', 'POST'))->getName()//reference https://github.com/symfony/http-foundation/blob/master/Request.php#L309
Also when you run app('router')->getRoutes()->match(app('request')->create('/qqq/posts/68/u1', 'POST')) this will return Illuminate\Routing\Route instance where you can call multiple useful public methods like getAction, getValidators etc. Check the source https://github.com/illuminate/routing/blob/master/Route.php for more details.
None of the solutions above worked for me.
This is the correct way to match a route with the URI:
$url = 'url-to-match/some-parameter';
$route = collect(\Route::getRoutes())->first(function($route) use($url){
return $route->matches(request()->create($url));
});
The other solutions perform bindings to the container and can screw up your routes...
I don't think this can be done with out-of-the-box Laravel. Also remember that not all routes in Laravel are named, so you probably want to retrieve the route object, not the route name.
One possible solution would be to extend the default \Iluminate\Routing\Router class and add a public method to your custom class that uses the protected Router::findRoute(Request $request) method.
A simplified example:
class MyRouter extends \Illuminate\Routing\Router {
public function resolveRouteFromUrl($url) {
return $this->findRoute(\Illuminate\Http\Request::create($url));
}
}
This should return the route that matches the URL you specified, but I haven't actually tested this.
Note that if you want this new custom router to replace the built-in one, you will likely have to also create a new ServiceProvider to register your new class into the IoC container instead of the default one.
You could adapt the ServiceProvider in the code below to your needs:
https://github.com/jasonlewis/enhanced-router
Otherwise if you just want to manually instantiate your custom router in your code as needed, you'd have to do something like:
$myRouter = new MyRouter(new \Illuminate\Events\Dispatcher());
$route = $myRouter->resolveRouteFromUrl('/your/url/here');
It can be done without extending the default \Iluminate\Routing\Router class.
Route::dispatchToRoute(Request::create('/your/url/here'));
$route = Route::currentRouteName();
If you call Route::currentRouteName() after dispatchToRoute call, it will return current route name of dispatched request.