FastRoute: Pass route prefix to handler - php

For example, lets say I have this route.
<?php declare(strict_types = 1);
$dispatcher = FastRoute\cachedDispatcher(function(FastRoute\RouteCollector $router) {
$router->addRoute('GET', '/{slug}', ['App\Controllers\SomeController', 'someMethod']);
}, [ 'cacheFile' => ROOT . '/storage/cache/route.cache', 'cacheDisabled' => true, ]);
Here is how I handle routes, and call the controller and its method.
switch ($routeInfo[0]) {
case FastRoute\Dispatcher::NOT_FOUND:
echo '404 Not Found';
break;
case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
$allowedMethods = $routeInfo[1];
echo '405 Method Not Allowed';
break;
case FastRoute\Dispatcher::FOUND:
$controller = $dice->create($routeInfo[1][0]);
echo $controller->{$routeInfo[1][1]}($routeInfo[2]);
break;
}
How do I pass {slug} to the controller method? It doesn't mention anything about it in its documentation, and no information about it can be found via a google search.

I didn't work with DICE until now, though I looked in its implementation in order to present the first option to you. I hope it will work. If not, feel free to read the DICE documentation/code regarding the call rule and the create method.
Note: The title should be something like "FastRoute: Pass route parameters to handler", or "FastRoute: Pass route arguments to handler", because a prefix is defined as the route part which is prepended to each route pattern inside of a route group.
Option 1: Use the call rule of the DI container (DICE):
This is, of course, the recommended way, since the DI container automatically injects the method arguments. Which could be more than the ones read from the route!
Note: controller method =: "action".
See (in DICE docs):
The call rule in 3. Configuring the container with Dice Rules.
3.4 Setter injection example.
Route:
$router->addRoute('GET', '/{userName}[/{userId:\d+}]', ['UserController', 'list']);
Note: if you have optional route parts, then you have to define the corresponding action parameters as optional.
Dispatching request by FastRoute:
case FastRoute\Dispatcher::FOUND:
$controllerName = $routeInfo[1][0]; // "UserController"
$action = $routeInfo[1][1]; // "list" action
$parameters = $routeInfo[2]; // Action parameters list (e.g. route parameters list)
$rule['call'] = [ // Define the method to be called and the parameters to be passed to the further created controller.
[$action, $parameters],
];
$dice->addRule($controllerName, $rule);
$controller = $dice->create($controllerName); // UserController instance
break;
Action in UserController:
public function list($userName, $userId = NULL) {
return 'User name = ' . $userName . ', User id = ' . $userId ?? 'N/A';
}
Option 2: Call the action (without DICE), separately passing all route parameters to it:
Route:
The same.
Dispatching request by FastRoute:
case FastRoute\Dispatcher::FOUND:
$controllerName = $routeInfo[1][0]; // "UserController"
$action = $routeInfo[1][1]; // "list" action
$parameters = $routeInfo[2]; // Action parameters list (e.g. route parameters list)
$controller = $dice->create($controllerName); // UserController instance
call_user_func_array(
[$controller, $action] // callable
, $parameters
);
break;
Action in UserController:
public function list($userName, $userId = NULL) {
return 'User name = ' . $userName . ', User id = ' . $userId ?? 'N/A';
}
Option 3: Call the action (without DICE), passing an instance of a Request class:
Assign the route parameters list to a Request instance (See PSR-7), as attribute, and pass the instance as action argument.
Route:
The same.
DI container definitions:
// Share a Request instance.
$dice->addRule('Request', ['shared' => true]);
Dispatching request by FastRoute:
case FastRoute\Dispatcher::FOUND:
$controllerName = $routeInfo[1][0]; // "UserController"
$action = $routeInfo[1][1]; // "list" action
$parameters = $routeInfo[2]; // Action parameters list (e.g. route parameters list)
// Create Request instance.
$request = $dice->create('Request');
// Assign the route parameters list to the Request instance.
$request->setAttribute('parameters') = $parameters
$controller = $dice->create($controllerName); // UserController instance
call_user_func_array(
[$controller, $action] // callable
, [$request]
);
break;
Action in UserController:
public function list(ServerRequestInterface $request) {
$userName = $request->getAttribute('parameters')['userName'];
$userId = $request->getAttribute('parameters')['userId'] ?? 'N/A';
return 'User name = ' . $userName . ', User id = ' . $userId ?? 'N/A';
}

Related

CodeIgniter 4: How do you enable user defined routes?

I'm just wondering how I can redirect to StudProfile after using the UpStudProf function. After running UpStudProf function, the URL became http://localhost/csms/public/index.php/Home/StudProfile, but it should be http://localhost/Home/StudProfile and is it possible to remove the Controllers name Home on the URL?
public function StudProfile(){
$crudModel = new Mod_Stud();
$data = [];
$data['user_data'] = $crudModel->orderBy('s_id', 'ASC')->findAll();
$data['title'] = 'SMS | STUDENT PROFILE';
$data['heading'] = 'Welcome to SMS';
$data['main_content'] = 'stud-prof'; // page name
return view('innerpages/template', $data);
}
public function UpStudProf(){
$crudModel = new Mod_Stud();
$s_id = $this->request->getPost('s_id');
$data = array(
's_lrn' => $this->request->getPost('s_lrn'),
's_fname' => $this->request->getPost('s_fname'),
's_mname' => $this->request->getPost('s_mname'),
's_lname' => $this->request->getPost('s_lname'),
);
$crudModel->upStud($data, $s_id);
return redirect()->to('Home/StudProfile'); //return to StudProfile
}
Routes.php
$routes->setDefaultNamespace('App\Controllers');
$routes->setDefaultController('Home');
$routes->setDefaultMethod('index');
$routes->setTranslateURIDashes(false);
$routes->set404Override();
$routes->setAutoRoute(true);
... is it possible to remove the Controllers name Home on the URL?
Use Defined Routes Only
When no defined route is found that matches the URI, the system will
attempt to match that URI against the controllers and methods as
described above. You can disable this automatic matching, and restrict
routes to only those defined by you, by setting the setAutoRoute()
option to false:
$routes->setAutoRoute(false);
Secondly, after disabling automatic matching, declare your user-defined route:
app/Config/Routes.php
$routes->get('student-profiles', 'Home::StudProfile');
Lastly: \App\Controllers\Home::UpStudProf,
redirect(string $route)
Parameters: $route (string) – The reverse-routed or named route to
redirect the user to.
Instead of:
// ...
return redirect()->to('Home/StudProfile'); //return to StudProfile ❌
// ...
Use this:
// ...
return redirect()->to('/student-profiles'); ✅
// ...

Passing variables from one controller to another with laravel

Is it possible when redirect from one controller to another also to pass variables? What I mean is that I have submit function and after submit I make redirect to url. Then in other controller trying to display some info based on variables. This is the redirect from first controller
return Redirect::to('/cart/payment/order/' . $order->order_id . '+' . $order->user_id);
then in second controller this
public function paymentView() {
$order = Order::where('order_id', $orderId)->first();
if (!$order) {
App::abort(404);
}
$userID = $order['user_id'];
$orderID = $order['order_id'];
}
Question is how to get $user_id and $order_id now?
First your route declaration should accept them, something like this, depending on your route:
Route::get('cart/payment/order/{orderID}/{userID}'...
Then Laravel will automatically inject them in your controller method:
public function paymentView( $orderID, $userID, ) {
And I recommend your URL to be with slash not with plus sign:
return Redirect::to('/cart/payment/order/' . $order->order_id . '/' . $order->user_id);

Pass fixed variable from route to controller in Laravel

I'm trying to pass a variable through my route to my controller, but I have multiple routes (categories) leading to the same controller i.e.
Route::get('/category1/{region}/{suburb?}', 'SearchController#search');
Route::get('/category2/{region}/{suburb?}', 'SearchController#search');
Making /category1, 2, etc. to be a parameter /{category} is not an option and I don't want to make separate controller function for each category.
How do I send the first segment of the url to my search controller? i.e. category1 or category2?
At present controller is as follows:
public function search($region, $suburb = null) { }
Thanks!
You can specify a mask for your {category} parameter so that it must fit the format "category[0-9]+" in order to match the route.
Route::get('/{category}/{region}/{suburb?}', 'SearchController#search')
->where('category', 'category[0-9]+');
Now, your example url (from the comments) www.a.com/var1/var2/var3 will only match the route if var1 matches the given category regex.
More information can be found in the documentation for route parameters here.
Edit
Yes, this can work with an array of string values. It is a regex, so you just need to put your array of string values into that context:
Route::get('/{category}/{region}/{suburb?}', 'SearchController#search')
->where('category', 'hairdresser|cooper|fletcher');
Or, if you have the array built somewhere else:
$arr = ['hairdresser', 'cooper', 'fletcher'];
// run each array entry through preg_quote and then glue
// the resulting array together with pipes
Route::get('/{category}/{region}/{suburb?}', 'SearchController#search')
->where('category', implode('|', array_map('preg_quote', $arr)));
Edit 2 (solutions for original request)
Your original question was how to pass the hardcoded category segment into the controller. If, for some reason, you didn't wish to use the solution above, you have two other options.
Option 1: don't pass the value in, just access the segments of the request in the controller.
public function search($region, $suburb = null) {
$category = \Request::segment(1);
dd($category);
}
Option 2: modify the route parameters using a before filter (L4) or before middleware (L5).
Before filters (and middleware) have access to the route object, and can use the methods on the route object to modify the route parameters. These route parameters are eventually passed into the controller action. The route parameters are stored as an associative array, so that needs to be kept in mind when trying to get the order correct.
If using Laravel 4, you'd need a before filter. Define the routes to use the before filter and pass in the hardcoded value to be added onto the parameters.
Route::get('/hairdresser/{region}/{suburb?}', ['before' => 'shiftParameter:hairdresser', 'uses' => 'SearchController#search']);
Route::get('/cooper/{region}/{suburb?}', ['before' => 'shiftParameter:cooper', 'uses' => 'SearchController#search']);
Route::get('/fletcher/{region}/{suburb?}', ['before' => 'shiftParameter:fletcher', 'uses' => 'SearchController#search']);
Route::filter('shiftParameter', function ($route, $request, $value) {
// save off the current route parameters
$parameters = $route->parameters();
// unset the current route parameters
foreach($parameters as $name => $parameter) {
$route->forgetParameter($name);
}
// union the new parameters and the old parameters
$parameters = ['customParameter0' => $value] + $parameters;
// loop through the new set of parameters to add them to the route
foreach($parameters as $name => $parameter) {
$route->setParameter($name, $parameter);
}
});
If using Laravel 5, you'd need to define a new before middleware. Add the new class to the app/Http/Middleware directory and register it in the $routeMiddleware variable in app/Http/Kernel.php. The logic is basically the same, with an extra hoop to go through in order to pass parameters to the middleware.
// the 'parameters' key is a custom key we're using to pass the data to the middleware
Route::get('/hairdresser/{region}/{suburb?}', ['middleware' => 'shiftParameter', 'parameters' => ['hairdresser'], 'uses' => 'SearchController#search']);
Route::get('/cooper/{region}/{suburb?}', ['middleware' => 'shiftParameter', 'parameters' => ['cooper'], 'uses' => 'SearchController#search']);
Route::get('/fletcher/{region}/{suburb?}', ['middleware' => 'shiftParameter', 'parameters' => ['fletcher'], 'uses' => 'SearchController#search']);
// middleware class to go in app/Http/Middleware
// generate with "php artisan make:middleware" statement and copy logic below
class ShiftParameterMiddleware {
public function handle($request, Closure $next) {
// get the route from the request
$route = $request->route();
// save off the current route parameters
$parameters = $route->parameters();
// unset the current route parameters
foreach ($parameters as $name => $parameter) {
$route->forgetParameter($name);
}
// build the new parameters to shift onto the array
// from the data passed to the middleware
$newParameters = [];
foreach ($this->getParameters($request) as $key => $value) {
$newParameters['customParameter' . $key] = $value;
}
// union the new parameters and the old parameters
$parameters = $newParameters + $parameters;
// loop through the new set of parameters to add them to the route
foreach ($parameters as $name => $parameter) {
$route->setParameter($name, $parameter);
}
return $next($request);
}
/**
* Method to get the data from the custom 'parameters' key added
* on the route definition.
*/
protected function getParameters($request) {
$actions = $request->route()->getAction();
return $actions['parameters'];
}
}
Now, with the filter (or middleware) setup and in use, the category will be passed into the controller method as the first parameter.
public function search($category, $region, $suburb = null) {
dd($category);
}

Rewriting route depending of parameter value Yii

I have several rules in Yii that allows me to rewrite some routes, where every will be pass to the action as a get parameter.
'<department>' => 'products/index',
'<department>/<category>' => 'products/index',
I want to explicitly write a rule that depending of the parameter value will change the url to whatever I want
example, right now I have an URL like this
www.mysite.com/Books+%26+Pencils which was rewritten because of this rule '<department>' => 'products/index', which is ok
I want to change that URL to www.mysite.com/books-pencils , if anyone know how to write a rule that compares the value of the deparment attribute and then rewrites it to whatever I want.
THanks
You can use a custom class to handle you special requests.
I have used sth like this, to get my custom URLs out of a database:
'urlManager'=>array(
'rules'=>array(
array(
'class' => 'application.components.UrlRule',
),
),
),
Then you create your custo class similar to this:
<?php
Yii::import("CBaseRule");
class UrlRule extends CBaseUrlRule
{
public function createUrl($manager,$route,$params,$ampersand)
{
// check for my special case of URL route, if not found, then return the unchaged route
preg_match("/^(.+)\/(.+)$/", $route, $r);
if(!is_array($r) or !isset($r[1]) or !isset($r[2])) {
return $route;
}
// handle your own route request, and create your url
$url = 'my-own-url/some-thing';
// check for any params, which i also want to add
$urlParams = $manager->createPathInfo($params,"=","&");
$return = trim($url,'/');
$return.= $urlParams ? "?" . $urlParams : "";
return $return;
}
public function parseUrl($manager,$request,$pathInfo,$rawPathInfo)
{
// handle my special url request
$controller = '....';
$action = '.....';
// return the controller/action that should be used
return lcfirst($controller)."/".$action;
}
}
I do not know if this was what you wanted, but at least in this class you can do everything you need with the URL requested.
If you would e.g. like to redirect a lot of similar URLs with a 301 Redirect to 1 URL, you could think of sth like this in the parseUrl function
// check my route and params, and if I need to redirect
$request->redirect('/your/new/url/?params=bla',true,'301');
First of all, if you want to change a URL, you should do a redirect (in this case 301). To implement this logic you can use custom URL rule class.
Url manager configuration:
'rules' => array(
// custom url rule class
array(
'class' => 'application.components.MyUrlRule',
),
)
MyUrlRule class:
class MyUrlRule extends CBaseUrlRule
{
public function createUrl($manager,$route,$params,$ampersand)
{
// Logic used to create url.
// If you do not create urls using Yii::app()->createUrl() in your app,
// you can leave it empty.
}
public function parseUrl($manager,$request,$pathInfo,$rawPathInfo)
{
// modify url
$pathInfoCleaned = strtolower(preg_replace('+%26+', '-', $pathInfo));
// redirect if needed
if ($pathInfo !== $pathInfoCleaned) {
$request->redirect($pathInfoCleaned, true, 301);
}
// parse params from url
$params = explode('/', $pathInfo);
if (isset($params[0])) {
$_GET['department'] = $params[0];
if (isset($params[1])) {
$_GET['category'] = $params[1];
}
}
return 'products/index';
}
}

Zend Framework - Custom defined routes overridden when adding Zend_Rest_Route

I'm creating an application that exposes a RESTful API in a module called api. For the other modules I created a little class that returns a Zend_Controller_Router_Rewrite object with custom defined routes:
$router = new Zend_Controller_Router_Rewrite();
foreach ($this->_modules as $module) {
if ($module === 'api') continue;
foreach ($this->_getConfigFiles($module) as $filename) {
$config = new Zend_Config_Ini($filename, 'routes');
$router->addConfig($config, 'routes');
}
}
return $router;
For the default module I have the following route:
[routes]
routes.default_index_index.type = Zend_Controller_Router_Route
routes.default_index_index.route = /
routes.default_index_index.defaults.module = default
routes.default_index_index.defaults.controller = index
routes.default_index_index.defaults.action = index
Now, in my Bootstrap file file I have the following:
$router = Shark_Module_Loader::getInstance()->getRouter();
$frontController->setRouter($router);
$frontController->getRouter()->removeDefaultRoutes();
$apiRoute = new Zend_Rest_Route($frontController, array(), array('api'));
$router->addRoute('rest', $apiRoute);
If I skip adding the rest route everything works fine for the default module, of course. But when I add the RESTful route the routes defined in the router are overridden(?), so the current route in the index action of the index controller of the default module ($this->getFrontController()->getRouter()->getCurrentRoute();) is an instance of Zend_Rest_Route. Thus, when trying to access a custom route defined in on of the route config files, lets say:
...
routes.default_pages_view.type = Zend_Controller_Router_Route
routes.default_pages_view.route = /view/:page
routes.default_pages_view.defaults.module = default
routes.default_pages_view.defaults.controller = pages
routes.default_pages_view.defaults.action = view
...
I get a 404 error saying that the request action (get) is not present.
I already went through the docs and didn't see any hint that suggests this behavior.
Any help and guidance will be appreciated.
There is no way to do this out of the box. (Check out this question)
You need to extend the Zend_Controller_Router_Route class. I've done it like this:
class Mauro_Controller_Router_Route_Method extends Zend_Controller_Router_Route {
protected $_method;
public function __construct($route, $defaults = array(), $reqs = array(), Zend_Translate $translator = null, $locale = null) {
list($this->_method, $route) = explode(' ', $route, 2);
parent::__construct($route, $defaults, $reqs, $translator, $locale);
}
public function match($path, $partial = false) {
$requestMethod = $this->getRequest()->getMethod();
$requestMethod = $this->getRequest()->getParam('method')
? strtoupper($this->getRequest()->getParam('method'))
: $requestMethod;
return $requestMethod == strtoupper($this->_method)
? parent::match($path, $partial)
: false;
}
protected function getRequest() {
return Zend_Controller_Front::getInstance()->getRequest();
}
}
You can then use it like this:
$router->addRoute( new Mauro_Controller_Router_Route_Method( 'GET /view/:page', array( 'controller' => 'pages', 'action' => 'view' ), array( 'page' => '/d+', ) ) );

Categories