php routing how to handle query parameters? - php

I have a router class in my php project that works like this:
public function dispatch(){
foreach ($this->routes as $url => $action) {
if( $url == $_SERVER['REQUEST_URI'] ){
if(is_callable($action)) return $action();
$actionArr = explode('#', $action);
$controller = 'My\\system\\controllers\\'.$actionArr[0];
$method = $actionArr[1];
return (new $controller)->$method();
}
}
}
And I define the routes like this:
My\system\classes\Registry::get("Router")->add('/My/admin/','AdminController#index');
So when the URL SERVER/My/admin is called the index method of the AdminController class is called.
My problem: How do I handle query strings?
I'd like to have a page with a form. On submit, the form gets sent to SERVER/My/admin/check, i.e. to the check.php page in the admin folder.
I defined the route like this
My\system\classes\Registry::get("Router")->add('/My/admin/check','AdminController#check');
but the URL isn't found, of course, because the query string is attatched to the URL. How should I handle this best?

Before checking $_SERVER['REQUEST_URI'], remove everything past the first ?, if one is present. Use that value to check if it matches with $url. Something as simple as this will do the trick:
$request = $_SERVER['REQUEST_URI'];
if( ($pos = strpos($request, '?')) !== false) $request = substr($request, 0, $pos);
Any controllers that need to work with query parameters should be able to get them from $_GET, or at worst $_SERVER['QUERY_STRING'].

This example is from my project, how I handle this.
REQUEST_URI - The URI which was given in order to access this page; for instance, '/index.html'.
$full_router = $_SERVER['REQUEST_URI'];
strtok() splits a string (string) into smaller strings (tokens), with each token being delimited by any character from token.
$router = strtok($full_router, '?'); // This is how you can handle query parameters
Now you can match the URL with if statement
if($router === '/' ){
include('/pages/home.php');
}

Related

Remove parameters from Symfony 4.4 HttpFoundation\Request and re-generate URL

I need to re-generate the URL of my page, removing the additional parameters. For example: when I receive:
/bao1/bao2/?removeMe1=anything&keepMe1=anything&removeMe2=&keepMe2=anything
I want to generate the URL with removeMe query var removed, but with everything else intact. Like this:
/bao1/bao2/?keepMe1=anything&keepMe2=anything
I autowired the request:
public function __construct(RequestStack $httpRequest)
{
$this->httpRequest = $httpRequest;
}
Then I'm playing around like this:
public function getCleanUrl()
{
// HttpFoundation\Request
$currentHttpRequest = $this->httpRequest->getCurrentRequest();
// Trying to remove the parameters
$currentHttpRequest->query->remove("removeMe1");
return $currentHttpRequest->getUri()
}
The query->remove("removeMe1") works, but when I invoke getUri() I still get the full input url, as if remove() was never invoked. I think I'm probably missing to call some kind of $currentHttpRequest->regenerate()->getUri() but I cannot find anything.
To get the modified URL after calling mutator methods on a Request object, you need to call overrideGlobals().
If not, Request methods will give you results accordin to the original superglobals ($_GET, $_POST, $_SERVER). By calling Request::overrideGlobals() you tell the object not to.
E.g.:
if ($request->query->has('amp') && Request::METHOD_GET === $request->getMethod()) {
$request->query->remove('amp');
$request->overrideGlobals();
return new RedirectResponse($request->getUri(), Response::HTTP_MOVED_PERMANENTLY));
}
Or maybe, something more adjusted to your use case (untested, but the general idea should hold):
$queryParams = array_keys($request->query->all());
$goodParams = ['foo', 'bar', 'baz'];
$badParams = array_diff($queryParams, $goodParams);
foreach ($badParams as $badParam) {
$request->query->remove($badParam);
}
$request->overrideGlobals();
// get modified URL
echo $request->getUri();
I had to make this work, so I devised a non-Symfony solution:
$currentHttpRequest = $this->httpRequest->getCurrentRequest();
$arrParams = $currentHttpRequest->query->all();
$arrParams = array_intersect_key($arrParams, array_flip([
"keepMe1", "keepMe2"
]));
$currentUrlNoQs = strtok($currentHttpRequest->getUri(), '?');
if( empty($arrParams) ) {
$canonical = $currentUrlNoQs;
} else {
$queryString = http_build_query($arrParams);
$canonical = $currentUrlNoQs . '?' . $queryString;
}
return $canonical;
I'm not too fond of it, but it got the job done.

Create Permission Without Checking URI Segment on Codeigniter

i've a function from my simple library and called on some controllers
$activeUrl = str_replace(base_url(),"",current_url());
erporate_acl::has_permission($activeUrl);
and then here is my library code, here the code try to matching current URI ($param) data from database :
public static function has_permission($param){
$CI =& get_instance();
$CI->load->model('acl_model');
$user = $CI->session->userdata('user');
$arrPerms = $CI->acl_model->permissions($user);
$currentMeth = strtolower(str_replace("::", "/", $param));
$result = "";
if (!empty($arrPerms))
{
if (strpos($currentMeth,'edit') !== false || strpos($currentMeth,'view') !== false || strpos($currentMeth,'delete') !== false) {
$str = preg_replace('#\/[^/]*$#', '', $currentMeth);
$result = in_array($str, $arrPerms);
}else{
$result = in_array($currentMeth, $arrPerms);
}
}else{
$result = false;
}
if ($result == false) {
show_error("<font size='+1'>Sorry, You Don't Allowed to Access !</font><br><a href='".#$_SERVER['HTTP_REFERER']."'>Back to Previous Page</a>");
}
}
the point of my script above is matching in_array if in_array return false then we got error message, else allowed to access the page
Is it possible to create permission without checking URI segment ?
Thank you and sorry for my bad English
You can use the following:
$this->router->class to get the controller class that is being used;
$this->router->method to get the method being called;
$this->router->directory to get the directory in case you have sub-folders in your url being used.
This way you don't really need to check the url that's being used. You can just check the controller and method.
This is an agnostic way from the routes. In case you have something like:
users/get/1
user/marco-monteiro
ACL based on the uri segment can be hard in these situations. Using the methods I mentioned it gets easier.

Laravel: Parse arbitrary URL to its corresponding Controller/Route?

Given I have an arbitrary URL mapped (amongst many others) like this
...
Route::get('/foobar/{parameter}', 'MyFoobarController#index');
...
How can I "reverse parse/resolve" the URL (like http://localhost/foobar/foo) into this configured controller (MyFoobarController) again? Please note: I am not talking about the current Request but a general approach to parse any URL that is mapped in Laravel to its corresponding Controller and Action (anywhere in the code independent of the current request). Thanks!
Update: It should also correctly match Routes, that have parameters in them.
You can compare the URL path, to the paths added to the router. So let's take your example:
Route::get('/foobar', 'MyFoobarController#index');
You can use the Route facade to get a list of all registered routes:
// This is your URL as a string
$url = 'http://localhost/foobar';
// Extract the path from that URL
$path = trim(parse_url($url, PHP_URL_PATH), '/');
// Iterate over the routes until you find a match
foreach (Route::getRoutes() as $route) {
if ($route->getPath() == $path) {
// Access the action with $route->getAction()
break;
}
}
The getAction method will return an array containing the relevant information about the action mapped for that route. You can check out the Illuminate\Routing\Route API for more info on what methods are available for you to use once you have matched a route.
private function getMatchRoutes($request)
{
$referer = $request->header('Referer');
$refererPath = parse_url($referer,PHP_URL_PATH);
$routes = Route::getRoutes()->getRoutes();
$matchedRoute = null;
foreach ($routes as $route) {
$route->compiled = (new RouteCompiler($route))->compile();
if (is_null($route->getCompiled())) continue;
if (preg_match($route->getCompiled()->getRegex(), rawurldecode($refererPath))) {
$matchedRoute = $route;
}
}
if (is_null($matchedRoute)) return $matchedRoute;
return explode('#',$matchedRoute->getActionName())[0];
}
the codes above i wrote is to get controller/action from request referer ,you can replace it by a valid url, try it , may be helpful ~~~

Replace matches from one string in another string

I'm writing a router for my PHP MVC application, and I currently need to find a way to use matches in a route as variables for controllers and actions.
For example, if I have the following route: /users/qub1/home
I would like to use a regex similar to this: \/users\/(?!/).*\/(?!/).*
Then I would like to specify the action like this: $2 (in the example, this would be home)
And the parameter to pass to the action like this: $1 (in the example, this would be qub1).
This would then execute code similar to this:
$controller = new UsersController();
$controller->$2($1);
Configured routes are stored as such:
public function setRoute($route, $regex = false, $controller = 'Index', $action = 'index', $parameters = array()) {
if(!$regex) {
$route = preg_quote($route, '/');
}
$this->routes[] = [
'route' => $route,
'controller' => $controller,
'action' => $action,
'parameters' => $parameters
];
}
Where the above example would be stored like this: $router->setRoute('\/users\/(?!/).*\/(?!/).*', true, 'User', '$2', [$1]);
So essentially, I want to use matched groups from one regex expression as variables to replace inside another regex expression (if that makes sense).
I hope I've described my problem accurately enough. Thanks for the help.
EDIT:
The code I'm currently using to parse routes (it doesn't work, but it should illustrate what I'm trying to achieve):
public function executeRoute($route) {
// Loop over available routes
foreach($this->routes as $currentRoute) {
// Check if the current route matches the provided route
if(preg_match('/^' . $currentRoute['route'] . '$/', '/' . $route, $matches)) {
// If it matches, perform the current route's action
// Define names
$controllerClass = preg_replace('\$.*\d', $matches[str_replace('$', '', '$1')], ucfirst($currentRoute['controller'] . 'Controller'));
$actionMethod = preg_replace('\$.*\d', $matches[str_replace('$', '', '$1')], strtolower($currentRoute['action']) . 'Action');
$parameters = preg_replace('\$.*\d', $matches[str_replace('$', '', '$1')], join(', ', $currentRoute['parameters']));
// Create the controller
$controller = new $controllerClass();
$controller->$actionMethod($parameters);
// Return
return;
}
}
}
While I am not sure that it is a very well designed approach, it is doable. This is the code that replaces yours within the if:
// you already specify the controller name, so no need for replacing
$controllerClass = ucfirst($currentRoute['controller'] . 'Controller');
// also here, no need to replace. You just need to get the right element from the array
$actionMethod = strtolower($matches[ltrim($currentRoute['action'], '$')] . 'Action';
// here I make the assumption that this parameter is an array. You might want to add a check here
$parameters = array();
foreach ($currentRoute['parameters'] as $parameter) {
$parameters[] = $matches[ltrim($parameter, '$')];
}
// check before instantiating
if (!class_exists($controllerClass)) {
die('invalid controller');
}
$controller = new $controllerClass();
// also check before invoking the method
if (!method_exists($controller, $actionMethod)) {
die('invalid method');
}
// this PHP function allows to call the function with a variable number of parameters
call_user_func_array(array($controller, $actionMethod), $parameters);
One reason why your approach is not very favorable is that you make a lot of assumptions:
the regex needs to have as many groups as you use in the other parameters
if you are imprecise with the regex, it might be possible to call any method in your code
Maybe this will be good enough for your project but you should consider using a well-established router if you want to create something not for educational purposes.

How to use $_GET with mod_rewrite and AltoRouter

I am having issues getting $_GET variables with mod_rewrite enabled. I have the following .htaccess:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [L]
and I am using "AltoRouter" for routing.
So an example of a Route that I might have is /login?redirect=localhost%2Fnetwork%2Fdashboard which would be rewritten as /login.
What I am trying to do is get $_GET['redirect'] and I cannot seem to do this. Can anyone help? Apologies in advance for a bit of a code dump.
You don't continue to use $_GET with AltoRouter.
Look here and here
Your problem may be that you are not generating URLs through AltoRouter.
Alto Router calls this "reverse routing" - look at the source:
/**
* Reversed routing
*
* Generate the URL for a named route. Replace regexes with supplied parameters
*
* #param string $routeName The name of the route.
* #param array #params Associative array of parameters to replace placeholders with.
* #return string The URL of the route with named parameters in place.
*/
public function generate($routeName, array $params = array()) {
The way to get params in the URL:
$router = new AltoRouter();
$router->map( 'GET', '/', function() { .. }, 'home' );
// assuming current request url = '/'
$match = $router->match();
/*
array(3) {
["target"] => object(Closure)#2 (0) { }
["params"] => array(0) { }
["name"] => 'home'
}
*/
Another example
$router = new AltoRouter();
// map homepage
$router->map( 'GET', '/', function() {
require __DIR__ . '/views/home.php';
});
// map user details page
$router->map( 'GET', '/user/[i:id]/', function( $id ) {
require __DIR__ . '/views/user-details.php';
});
// match current request url
$match = $router->match();
// call closure or throw 404 status
if( $match && is_callable( $match['target'] ) ) {
call_user_func_array( $match['target'], $match['params'] );
} else {
// no route was matched
header( $_SERVER["SERVER_PROTOCOL"] . ' 404 Not Found');
}
This function helped me:
public static function _GET(){
$__GET = array();
$ru = $_SERVER['REQUEST_URI'];
$_get_str = explode('?', $ru);
if( !isset($_get_str[1]) ) return $__GET;
$params = explode('&', $_get_str[1]);
foreach ($params as $p) {
$parts = explode('=', $p);
$__GET[$parts[0]] = isset($parts[1])? $parts[1] : '';
}
return $__GET;
}
and:
$__GET = App::_GET();
$url = urldecode( $__GET['redirect'] )
Old question but you can get the GET variables the same way before with $_GET, but you have to still match the route. I.e., if the route isn't matched your script doesn't proceed.
The route in altorouter for:
/login?redirect=localhost%2Fnetwork%2Fdashboard
Would be (you can use just GET or POST if you like) :
$router->map('GET|POST','/login/*', 'controllerforthisroute', "login");
After you can do <?php echo $_GET['redirect'] ?> and get:
localhost/network/dashboard
Old question, but it seems it's not so easy to deal with Altorouter & query string parameters.
As explained here by the author, it's not suitable to output the query string parameters in Altorouter's $match['parameters'] to respect REST principles.
Query string parameters have to be threated as external data, not part of Altorouter data.
Here's a simple solution to retrieve the URL query string & register parameters in PHP global $_GET:
// Register URL query string parameters in $_GET since Altorouter ROUTE doesn't deal with these.
$parts = parse_url($_SERVER['REQUEST_URI']);
if (isset($parts['query'])) {
parse_str($parts['query'], $_GET);
}
// now we can use $_GET
// echo $_GET['something'];

Categories