I am extending and overwriting the createUrl method to make pretty URLs.
Here is a snippet:
public function createUrl($manager, $route, $params, $ampersand) {
if($route == 'widget/view'){
$widget = Widget::model()->findByPk($params['id']);
return 'widget/' . $params['id'] . '/' . SEOUrlRule::slug($widget->title);
}
}
Lots of things don't matter much here. And I removed irrelevant parts.
It works fine. However, sometimes the method may be called with additional parameters such as an anchor tag, or GET parameters to put at the end of the URL.
Using my method, these parameters will be lost. To pass them forward in the new URL, do I have to re-do whatever the original createUrl method did manually? Or is there a nice object oriented way to pass the job along to a competent method?
You've made every argument required - is that intentional?
You can have Yii construct the parameter string by using the createUrl from CApplication, e.g.:
public function createUrl($manager, $route, $params, $ampersand) {
if($route == 'widget/view'){
$widget = Widget::model()->findByPk($params['id']);
$url = 'widget/' . $params['id'] . '/' . SEOUrlRule::slug($widget->title);
return Yii::app()->createUrl(trim($url,'/'),$params,$ampersand);
}
}
Related
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.
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.
I need to create a simple routing mechanism that takes a request like: /foo/bar and translates it to FooController->barAction(); I have to use a single script as an access point to load these controller classes and action methods. I also cannot use any external frameworks or libraries to accomplish this task. This needs to be able to be run on a PHP 5.3 Server with Apache.
Below is what I've written already, but I'm having trouble getting it to work:
class Router {
private static $routes = array();
private function __construct() {}
private function __clone() {}
public static function route($pattern, $callback) {
$pattern = '/' . str_replace('/', '\/', $pattern) . '/';
self::$routes[$pattern] = $callback;
}
public static function execute() {
$url = $_SERVER['REQUEST_URI'];
$base = str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME']));
if (strpos($url, $base) === 0) {
$url = substr($url, strlen($base));
}
foreach (self::$routes as $pattern => $callback) {
if (preg_match($pattern, $url, $params)) {
array_shift($params);
return call_user_func_array($callback, array_values($params));
}
}
}
}
I'm trying to at least execute my current script which is based off another simple Router, but I cannot actually get an output using
Router::route('blog/(\w+)/(\d+)', function($category, $id){
print $category . ':' . $id;
});
Router::execute();
Instead of trying to break out the PATH. Why not use .htaccess instead.
So you could have internal URL's that look like this:
index.php?module=MODULE&action=INDEX
Then use .htaccess to provide the paths in the URL and the route them accordingly.
www.mydomain.com/MODULE/INDEX
This post can help with the rewriting regex for creating pritty urls in htaccess
There might be a better one, was just a quick google search.
This way you can access like this:
$module = $_GET['module'];
$action = $_GET['action];
Then you can do checks to corresponding actions in your router to check if it exists and then re-route accordingly.
I'm experimenting with php mvc and I'm stucked with the following issue. My request and router classes are really simple and I would like to extend theme to can handle controller calls from sub folders and to controller classes functions should be able to pick up url variables send it threw get and post.
my router looks as it follows
class Router{
public static function route(Request $request){
$controller = $request->getController().'Controller';
$method = $request->getMethod();
$args = $request->getArgs();
$controllerFile = __SITE_PATH.'/controllers/'.$controller.'.php';
if(is_readable($controllerFile)){
require_once $controllerFile;
$controller = new $controller;
if(!empty($args)){
call_user_func_array(array($controller,$method),$args);
}else{
call_user_func(array($controller,$method));
}
return;
}
throw new Exception('404 - '.$request->getController().'--Controller not found');
}
}
and Request class
private $_controller;
private $_method;
private $_args;
public function __construct(){
$parts = explode('/',$_SERVER['REQUEST_URI']);
$this->_controller = ($c = array_shift($parts))? $c: 'index';
$this->_method = ($c = array_shift($parts))? $c: 'index';
$this->_args = (isset($parts[0])) ? $parts : array();
}
public function getController(){
return $this->_controller;
}
public function getMethod(){
return $this->_method;
}
public function getArgs(){
return $this->_args;
}
}
The problem is:when I try to send threw ajax, variables to a controller method this are not recognized because of its url structure.
For example
index/ajax?mod_title=shop+marks&domain=example
is accepted just if it look
index/ajax/shop+mark/example
Your code contains what is known as an LFI vulnerability and is dangerous in its current state.
You should whitelist your what can be used as your $controller, as otherwise an attacker could try to specify something using NUL bytes and possibly going up a directory to include files that SHOULD NOT be ever included, such as /etc/passwd, a config file, whatever.
Your router is not safe for use; beware!
edit: example on whitelisting
$safe = array(
'ajax',
'somecontroller',
'foo',
'bar',
);
if(!in_array($this->_controller, $safe))
{
throw new Exception(); // replace me with your own error 404 stuff
}
Since your Request class uses a URI segments approach for identifying controller, action and arguments, global variables such as $_GET or $_REQUEST are not taken into account from within your Request.
What you need to do is to make some additions to your Request code. Specifically:
Remove the line:
$this->_args = (isset($parts[0])) ? $parts : array();
And add the following:
$all_parts = (isset($parts[0])) ? $parts : array();
$all_parts['get'] = $_GET;
$this->_args = $all_parts;
This way, $_GET (ie variables passed via the url) variables will be available in the actions called, as they will be in $args (they will be available as $args['get'] actually, which is the array that holds the $_GET vars, so you will be able to have access to domain=example by using $args['get']['domain']).
Ofcourse, you can add one more method in your Request class (e.g. query) that might look like that:
public function query($var = null)
{
if ($var === null)
{
return $_GET;
}
if ( ! isset($_GET[$var]) )
{
return FALSE;
}
return $_GET[$var];
}
This way, you can get a single variable from the url (e.g. $request->query('domain')) or the whole $_GET array ($request->query()).
That's because php will put "?mod_title=..." in the $_GET array automatically. Your getArgs() function should check for $_GET, $_POST or $_REQUEST.
If you're trying for a minimal MVC approach, have a look at rasmus' example: http://toys.lerdorf.com/archives/38-The-no-framework-PHP-MVC-framework.html
If your use case is going to get more complex, have a look at how Zend (http://framework.zend.com/manual/en/zend.controller.html) or Symfony (https://github.com/symfony/symfony/tree/master/src/Symfony/Component/Routing) do their stuff.
Choose any popular MVC to see how they implement it under the hood. In addition, spl_autoload_register and namespace are your friends.
I am looking for a pretty equivalent in PHP to its function call_user_func.
The problem I am encountering with this function is that it does not go into an "object mode". By this, I mean I cannot use $this and other stuff in the class, so pretty much in two words: not oop.
I need this basically as I am dealing with the requested url, parsing it, and seeing if everything is ok and so on, and then doing the following lines:
call_user_func(array(ucfirst( $controller . "Controller" ), '_initAction'), $param);
call_user_func(array(ucfirst( $controller . "Controller" ), $action . 'Action'), $param);
as I want to dynamically call the "Controller" and its actions. But I cannot use $this in the $action methods as it is not OOP.
Here the message I get:
Fatal error: Using $this when not in object context in E:\htdocs\wit\application\controller\InformationController.php on line 6
So I hope that somebody could help me.
Could you also tell me if I am approaching this problem in a wrong way?
PS: Please don't recommend me any MVC frameworks that take care of this stuff. I like Zend, but sometimes, its just too heavy :(( I need a lightweight setup for this.
You can call the object method by passing object in first element of the callback:
$class = ucfirst($controller . "Controller");
$controller = new $class();
call_user_func(array($controller, $action . 'Action'), $param);
Actually, you can even use
$controller = new $class();
$controller->{$action . 'Action'}();
Take a look at how Glue calls user defined functions. It may point you in the right direction, since users define classes to handle routes.
<?php
require_once('glue.php');
$urls = array(
'/' => 'index'
);
class index {
function GET() {
echo "Hello, World!";
}
}
glue::stick($urls);
?>
If I completely miss the point of your question, I'm sorry. It sounds to me like you're trying to call an object method using call_user_func. You can do that, you just pass an array with the object as the first index, and the string name of the method as the second index.
For example, say you have your controller "IndexController" with "index" as your action/method.
class IndexController {
public function index() {
// $this available here
}
}
$controller = new IndexController();
// if you know your parameters all ahead of time
call_user_func(array($controller, 'index'), $param1, $param2);
// if you want to pass an unknown number of params
call_user_func_array(array($controller, 'index'), $params);
$view = View::getInstance( $config['view'] );
$err = new ErrorController(array(), $view);
//load the class and its method
//pass the params to it
$class = ucfirst( $controller . "Controller" );
if ( class_exists($class) )
{
$con = new $class($param, $view);
$act = $action . 'Action';
if ( method_exists($con, $act) )
{
$con->$act();
}
else
{
$view->setController("error");
$view->setAction('index');
$err->indexAction();
}
}
else
{
$view->setController("error");
$view->setAction('index');
$err->indexAction();
}
So this is my solution how i solved my problem. it is based on #AlexAtNet solution and parts of glue that was recommend to me by #Sean
Also you can try this way:
$controller = new $class();
$method = $action . 'Action';
[$controller, $method]()