Hi Folks i upgrading my slim framework from slim 2 to slim 4 for older project
for one route i added the one value before route using slim 2 slim.before in index.php
example code:
$app->hook('slim.before', function () use ($app) {
$env = $app->environment();
$path = $env['PATH_INFO'];
// spliting the route and adding the dynamic value to the route
$uriArray = explode('/', $path);
$dynamicvalue = 'value';
if(array_key_exists($uriArray[1], array)) {
$dynamicvalue = $uriArray[1];
//we are trimming the api route
$path_trimmed = substr($path, strlen($dynamicvalue) + 1);
$env['PATH_INFO'] = $path_trimmed;
}
});
i read about the add beforemiddleware but cannot able find correct way to add it and i cannot able to find the replacement for $app->environment();
i want to append the dynamic value directly to route
for example
i have one route like this
https://api.fakedata.com/fakeid
by using the above route splitting code i appending the value route using slim.before in slim 2
for example take the dynamic value as test
the route will be
https://api.fakedata.com/test/fakeid
the response of the both api will be same we want to just add value to the route
can any one help me how to do with slim 4
I assume you need to and PATH_INFO to the environment so you can later refer to it in the route callback. You can add a middleware to add attributes to the $request the route callback receives:
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Psr7\Response;
class PathInfoMiddleware {
public function __invoke(Request $request, RequestHandler $handler) : Response {
$info = 'some value, path_trimmed for example...'; // this could be whatever you need it to be
$request = $request->withAttribute('PATH_INFO', $info);
return $handler->handle($request);
}
}
// Add middleware to all routes
$app->add(PathInfoMiddleware::class);
// Use the attribute in a route
$app->get('/pathinfo', function(Request $request, Response $response){
$response->getBody()->write($request->getAttribute('PATH_INFO'));
return $response;
});
Now visiting /pathinfo gives the following output:
some value, path_trimmed for example...
Related
Considering the following route group in Laravel 5:
Route::prefix('{locale}')->group(function () {
// ... Other routes here
});
All routes that are nested within this route group have views that extend a certain layout.
Somewhere in this layout I loop all available locales in my application, and I put links to change to this locale. What would be the easiest way to print these links in my view, keeping the current route and possibly the other route parameters but changing only the {locale} parameter?
Had this problem and came up with this:
app('url')->toRoute(
request()->route(),
array_merge(request()->route()->parameters(), ['locale' => $otherLocale]),
true
);
You could turn it into a helper like route:
function current_route($params = [], $absolute = true)
{
return app('url')->toRoute(
request()->route(),
array_merge(request()->route()->parameters(), $params),
$absolute
);
}
and call it with current_route(['locale' => 'sv'])
Maybe just Type-hint the request and pass the prefix into your view through your controller and use it in your links:
public function ControllerWithLocaleLinks(Request $request)
{
$currentLocale = $request->route()->getPrefix();
return view('locale', compact('currentLocale'));
}
I'm trying to implement json-schema validator from justinrainbow as middleware in Slim 3.
I can't figure out how to get the clients input from GET/POST requests in middleware.
tried like this:
$mw = function ($request, $response, $next) {
$data = $request->getParsedBody();
print_r($data); // prints nothing
$id = $request->getAttribute('loan_id');
print_r($id); // prints nothing
// here I need to validate the user input from GET/POST requests with json-schema library and send the result to controller
$response = $next($request, $response);
return $response;
};
$app->get('/loan/{loan_id}', function (Request $request, Response $response) use ($app, $model) {
$loanId = $request->getAttribute('loan_id'); // here it works
$data = $model->getLoan($loanId);
$newResponse = $response->withJson($data, 201);
return $newResponse;
})->add($mw);
There are 2 possible ways of how I need it. what i'm doing wrong ?
validate it in middleware and send some array/json response to the controller, which i will then get as I understood with $data = $request->getParsedBody();
validate it in middleware but final check will be in controller like this:
$app->get('/loan/{loan_id}', function (Request $request, Response $response) use ($app, $model) {
if($validator->isValid()){
//
}
$loanId = $request->getAttribute('loan_id'); // here it works
$data = $model->getLoan($loanId);
$newResponse = $response->withJson($data, 201);
return $newResponse;
})->add($mw);
Best option for me it do something like here
but I don't understand what should i return in container, and how to pass get/post input to container
Your code in the first point seems alright, you only try to access route parameter from within middleware. At that point the route is not yet resolved and therefore parameters are not parsed from the URL.
This is a known use case and is described in Slim's documentation. Add the following setting to your app configuration to get your code working:
$app = new App([
'settings' => [
// Only set this if you need access to route within middleware
'determineRouteBeforeAppMiddleware' => true
]
]);
In order to understand how middleware works and how to manipulate response object, I suggest you read the User Guide - it's not that long and explains it really well.
How to get value of one route into another
Route::get('/first_url', function () {
return "Hello this is test";
});
I Tried something like this but not worked.
Route::get('/second_url', function () {
$other_view = Redirect::to('first_url');
});
I want to get returned value from first_url to variable $other_view in second_url to process and manipulate returned value.
Using Redirect is changing url. Which I dont want to use.
Any Idea ??? Or Am I trying wrong thing to do.
If you just want to return first_url, do this:
Route::get('/first_url', ['as' => 'firstRoute', function () {
return "Hello this is test";
}]);
Route::get('/second_url', function () {
return redirect()->route('firstRoute');
});
Learn more about redirects to routes here.
Update:
If you want to pass variable, you can use form or just create a link with parameters. You can try something like this {{ route('second_url', ['param' => 1]) }}
Then your second route will look like this:
Route::get('/second_url/{param}', ['uses' => 'MyController#myMethod', 'param' => 'param']);
And myMethod method in MyController:
public function myMethod($param){
echo $param;
...
I don't know why you would like to do this, but you can get the rendered contents of the route by executing a simple HTTP request to your route and reading the contents:
Route::get('/second_url', function () {
$other_view = file_get_contents(URL::to('first_url'));
return $other_view; // Outputs whatever 'first_url' renders
});
You need to send HTTP request and then process the response. You can use file_get_contents as #tommy has suggested or you can use HTTP library like Guzzle:
$client = new GuzzleHttp\Client();
$res = $client->get(route('firstRoute'));
in this case u should use a named route.
https://laravel.com/docs/5.1/routing#named-routes
somthing like this:
Route::get('/first_url', ['as' => 'nameOfRoute', function () {
return "Hello this is test";
}]);
Route::get('/second_url', function () {
redirect()->route('nameOfRoute');
});
You can not pass the variable value to another route directly. http is stateless protocol. if you want to preserve the value of variable to another route you can do that by 3 methods query string, sessions and cookies only. Your can pass parameter to to specific route like this
Route::get('/second_url/{param}', ['uses' => 'MyController#myMethod',
'param' => 'param']);
The idea behind achieving what you want is naming the function of your first route and calling it within both the first route and your second route. Your function will simply return the view to the first route, and retrieve the rendered html for your second.
function MyFirstRouteFunction() {
// Do whatever your do in your first route
// I assume your function return here an instance of Laravel's View
}
Route::get('/first_url', MyFirstRouteFunction);
Route::get('/second_url', function () {
$contentsOfFirstRoute = MyFirstRouteFunction()->render();
// Make use of rendered HTML
});
There is no need to make one extra HTTP request.
You should use Guzzle or curl to achive this:
Route::get('/second_url', function () {
//:::::Guzzle example:::::
$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'http://...second_url...', []);
//:::::curl example:::::
$ch = curl_init();
//define options
$optArray = array(
CURLOPT_URL => 'http://...second_url...',
CURLOPT_RETURNTRANSFER => true
);
//apply those options
curl_setopt_array($ch, $optArray);
//execute request and get response
$res = curl_exec($ch);
});
Note that using Guzzle may need you to install required libraries.
If you put your first_route closure into a controller action you could try to instantiate that controller and call the method directly.
This is considered as bad practice.
routes.php
Route::get('/first_url', 'TestController#getFirstUrl');
App/Http/Controllers/TestController.php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
class TestController extends Controller
{
public function getFirstUrl()
{
return view('my-view');
}
}
routes.php
Route::get('/second_url', function () {
$controller = new \App\Http\Controllers\TestController();
$contentsOfFirstRoute = $controller->getFirstRoute();
// Make use of rendered HTML
});
I need to have the current found controller and action in a middleware, so that I can do some authentication. But I found it impossible, because the pipe is like Middleware1 -> Middleware2-> do the dispatching -> controller#action() -> Middleware2 -> Middleware1.
Therefore before the dispatching, I cannot get the route info. It is definitely not right to do it after the $controller->action().
I did some research and found this.
$allRoutes = $this->app->getRoutes();
$method = \Request::getMethod();
$pathInfo = \Request::getPathInfo();
$currentRoute = $allRoutes[$method.$pathInfo]['action']['uses'];
But this does not work when visiting URI like app/role/1, because $allRoutes only have index of app/role/{id} instead of app/role/1.
Is there any workaround about this?
After do some research, I got solution. Here they go:
Create Custom Dispatcher
First, you have to make your own custom dispatcher, mine is:
App\Dispatcher\GroupCountBased
Stored as:
app/Dispatcher/GroupCountBased.php
Here's the content of GroupCountBased:
<?php namespace App\Dispatcher;
use FastRoute\Dispatcher\GroupCountBased as BaseGroupCountBased;
class GroupCountBased extends BaseGroupCountBased
{
public $current;
protected function dispatchVariableRoute($routeData, $uri) {
foreach ($routeData as $data) {
if (!preg_match($data['regex'], $uri, $matches)) continue;
list($handler, $varNames) = $data['routeMap'][count($matches)];
$vars = [];
$i = 0;
foreach ($varNames as $varName) {
$vars[$varName] = $matches[++$i];
}
// HERE WE SET OUR CURRENT ROUTE INFORMATION
$this->current = [
'handler' => $handler,
'args' => $vars,
];
return [self::FOUND, $handler, $vars];
}
return [self::NOT_FOUND];
}
}
Register Your Custom Dispatcher in Laravel Container
Then, register your own custom dispatcher via singleton() method. Do this after you register all your routes! In my case, I add custom dispatcher in bootstrap/app.php after this line:
require __DIR__.'/../app/Http/routes.php';
This is what it looks like:
/*
|--------------------------------------------------------------------------
| Load The Application Routes
|--------------------------------------------------------------------------
|
| Next we will include the routes file so that they can all be added to
| the application. This will provide all of the URLs the application
| can respond to, as well as the controllers that may handle them.
|
*/
require __DIR__.'/../app/Http/routes.php';
// REGISTER YOUR CUSTOM DISPATCHER IN LARAVEL CONTAINER VIA SINGLETON METHOD
$app->singleton('dispatcher', function () use ($app) {
return FastRoute\simpleDispatcher(function ($r) use ($app) {
foreach ($app->getRoutes() as $route) {
$r->addRoute($route['method'], $route['uri'], $route['action']);
}
}, [
'dispatcher' => 'App\\Dispatcher\\GroupCountBased',
]);
});
// SET YOUR CUSTOM DISPATCHER IN APPLICATION CONTEXT
$app->setDispatcher($app['dispatcher']);
Call In Middleware (UPDATE)
NOTE: I understand it's not elegant, since dispatch called after middleware executed, you must dispatch your dispatcher manually.
In your middleware, inside your handle method, do this:
app('dispatcher')->dispatch($request->getMethod(), $request->getPathInfo());
Example:
public function handle($request, Closure $next)
{
app('dispatcher')->dispatch($request->getMethod(), $request->getPathInfo());
dd(app('dispatcher')->current);
return $next($request);
}
Usage
To get your current route:
app('dispatcher')->current;
I found the correct answer to this problem. I missed one method named routeMiddleware() of Application. This method registers the route-specific middleware which is invoked after dispatching. So Just use $app->routeMiddleware() to register you middleware. And get the matched route info by $request->route() in your middleware.
$methodName = $request->getMethod();
$pathInfo = $request->getPathInfo();
$route = app()->router->getRoutes()[$methodName . $pathInfo]['action']['uses'];
$classNameAndAction = class_basename($route);
$className = explode('#', $classNameAndAction)[0];
So the title describes my problem pretty well I think, but let me explain why I want to do this as theremight be an other solution to my problem that I haven't thought about.
Let's say that I have a route specifying the class of the object it will patch:
Route::patch('{class}/{id}', array(
'as' => 'object.update',
function ($class, $id) {
$response = ...;
// here I want to call the update action of the right controller which will
// be named for instance CarController if $class is set to "car")
return $response;
}
));
This is something pretty easy to do with $app->make($controllerClass)->callAction($action, $parameters); but doing it this way won't call the filters set on the controller.
I was able to do it with laravel 4.0 with the callAction method, passing the app and its router, but the method has changed now and the filters are called in the ControllerDispatcher class instead of the Controller class.
If you have routes declared for your classes then you may use something like this:
$request = Request::create('car/update', 'POST', array('id' => 10));
return Route::dispatch($request)->getContent();
In this case you have to declare this in routes.php file:
Route::post('car/update/{id}', 'CarController#update');
If you Use this approach then filters will be executed automatically.
Also you may call any filter like this (not tested but should work IMO):
$response = Route::callRouteFilter('filtername', 'filter parameter array', Route::current(), Request::instance());
If your filter returns any response then $response will contain that, here filter parameter array is the parameter for the filter (if there is any used) for example:
Route::filter('aFilter', function($route, $request, $param){
// ...
});
If you have a route like this:
Route::get('someurl', array('before' => 'aFilter:a_parameter', 'uses' => 'someClass'));
Then the a_parameter will be available in the $param variable in your aFilter filter's action.
So I might have found a solution to my problem, it might not be the best solution but it works. Don't hesitate to propose a better solution!
Route::patch('{class}/{id}', array(
'as' => 'object.update',
function ($class, $id) {
$router = app()['router']; // get router
$route = $router->current(); // get current route
$request = Request::instance(); // get http request
$controller = camel_case($class) . 'Controller'; // generate controller name
$action = 'update'; // action is update
$dispatcher = $router->getControllerDispatcher(); // get the dispatcher
// now we can call the dispatch method from the dispatcher which returns the
// controller action's response executing the filters
return $dispatcher->dispatch($route, $request, $controller, $action);
}
));