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.
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.
If I call $this->url() from a view I get the url with parameters. Ex: /test/view/var1/value1
Is there any way to get the currect url/location without parameters (var1/value1) and without passing the urlOptions:
For example, if I use this it works:
$this->url(array("controller"=>"test", "action"=>"view"),null,true);
//Returns /test/view
But I would like to avoid passing the parameters
$this->url(array(),null,true);
Is there any way to do this?
This sounds like a job for a view helper. Something like this?
class Zend_View_Helper_Shorturl {
public function shorturl() {
$request = Zend_Controller_Front::getInstance()->getRequest();
$module = $request->getModuleName();
$controller = $request->getControllerName();
$action = $request->getActionName();
return $this->view->url(array('module'=>$module, 'controller'=>$controller,'action'=>$action), null, true);
//return "/$controller/$action"; //Left this in incase it works better for you.
}
}
Then you just write $this->shorturl(); in your view.
Just to be clear this would go in scripts/helpers/Shorturl.php
Edit:
In fact, I've just tried this and it works. I'd say this is the solution to use.
class Zend_View_Helper_Shorturl {
public function shorturl() {
return $this->view->url(array('module'=>$module, 'controller'=>$controller,'action'=>$action), null, true);
}
}
Not really - the Zend_Controller_Request_Http object that the router (called by the url view helper) uses to generate the link doesn't really distinguish between the module/controller/action parameters and other parameters your action might use.
Either use the first form that you quoted above, or if you need a solution that works for every action/controller, create something like:
class MyApplication_Controller_Action_Base extends Zend_Controller_Action {
public function preDispatch() {
//Generate a URL to the module/controller/action
//(without any other parameters)
$this->view->bareUrl = $this->view->url(
array_intersect_key(
$this->getRequest()->getParams(),
array_flip(array('module','view','controller')
),
null,
true
);
}
}
In any view, you can then use <?=$this->bareUrl?>
Just use $this->url() to get the baseUrl/controller like it appears in the browser URL for example if your it is www.your-domain.ro/project/members it will return project/members
I am using the events system in Symfony 1.3.8.
I am writing logic for the event handlers. As part of my logic, I may need to send email. I therefore need to get the appropriate partial for the email to be sent.
What is the best way to do this?
I have this so far:
class MyEventHandler
{
public static function handleFooEvent(sfEvent $event)
{
// I need to get partial here
// $body = $this->getPartial('somemodule', 'foo', $params);
}
}
I notice that getPartial() is implemented in sfAction like this:
public function getPartial($templateName, $vars = null)
{
$this->getContext()->getConfiguration()->loadHelpers('Partial');
$vars = null !== $vars ? $vars : $this->varHolder->getAll();
return get_partial($templateName, $vars);
}
To load an helper from anywhere in your application, you can use the following:
sfProjectConfiguration::getActive()->loadHelpers("Partial", "Url", "MyHelper");
Then you can get the needed partial just with:
get_partial('somemodule/somepartial', $params)
is there a class to handle $_REQUEST that makes the life of a php developer easier?
I want to handle the client requests easier.
I dont want to test with if(is_set($_REQUEST['blabla'])) {makesomthing();}
I wish there could be a solution like this.
class rpclike
{
public function getMember()
{
$memberid = $this->inputhandler['memberid'];
$member = $this->memberclass->getmember($memberid);
foreach($member as $mem)
{
echo $mem->id;
}
}
}
$rpc = new rpclike();
then if i call the rpclike from a javascript like this
Get member
Which class can do something like that?
It's not recommended that you use $_REQUEST as it poses security concerns. You should be using one of $_GET, $_POST, or $_COOKIE depending on what global request var you are trying to retrieve. Your best bet would be to have something like the following:
class input {
public static function get($key, $value = false) {
return (!empty($_GET[$key])) ? $_GET[$key] : $value;
}
public static function post($key, $value = false) {
return (!empty($_POST[$key])) ? $_POST[$key] : $value;
}
public static function cookie($key, $value = false) {
return (!empty($_COOKIE[$key])) ? $_COOKIE[$key] : $value;
}
}
You could then use the class like:
if (input::post('field', null) != null) {
}
or
if (input::get('field', false) != false) {
}
Although this still requires testing, you can explicitly set the return values in the event no data was set for the global variable.
PHP doesn't really have a default class structure that you can utilize in that kind of manner, as it's origins are in procedural-based programming.
It would be fairly trivial for you to create a class like that if you felt the need for it. However, you would really just be adding overhead. If the convenience of it is worth it for you, then you could utilize the __get() and __set() methods to handle existence checks for you.
The fact that you want to use this for handling client requests in an easier fashion is probably a good indicator that you should move to something like an MVC framework, which usually handle URLs and route them to appropriate methods for you automatically. Most PHP frameworks will do this for you already. For a nice overview on how the process commonly works, you could see how CodeIgniter does it.
Aside from the obvious security risks involved in this, it is feasible. It's a common pattern to use for steering requests in an MVC system.
Say you request index.php?class=User&method=ViewProfile
$module = new $_GET['class']();
if(!method_exists($module,$_GET['method']))
$module->$eventName();
I don't think so. Being able to invoke an arbitrary method would be a massive security hole.
Do something like:
url: /foo/bar?req=getMembers&memberid=22
Then you can do:
$request = $_GET['req'];
$request();
Slightly less dangerous version:
$req_methods = array(
getMembers => 'some_function',
saveMembers => 'another_function',
sendMessage => 'send_him_an_email'
);
$request = $_GET['req'];
$req_methods[$request]();
This situation arises from someone wanting to create their own "pages" in their web site without having to get into creating the corresponding actions.
So say they have a URL like mysite.com/index/books... they want to be able to create mysite.com/index/booksmore or mysite.com/index/pancakes but not have to create any actions in the index controller. They (a non-technical person who can do simple html) basically want to create a simple, static page without having to use an action.
Like there would be some generic action in the index controller that handles requests for a non-existent action. How do you do this or is it even possible?
edit: One problem with using __call is the lack of a view file. The lack of an action becomes moot but now you have to deal with the missing view file. The framework will throw an exception if it cannot find one (though if there were a way to get it to redirect to a 404 on a missing view file __call would be doable.)
Using the magic __call method works fine, all you have to do is check if the view file exists and throw the right exception (or do enything else) if not.
public function __call($methodName, $params)
{
// An action method is called
if ('Action' == substr($methodName, -6)) {
$action = substr($methodName, 0, -6);
// We want to render scripts in the index directory, right?
$script = 'index/' . $action . '.' . $this->viewSuffix;
// Script file does not exist, throw exception that will render /error/error.phtml in 404 context
if (false === $this->view->getScriptPath($script)) {
require_once 'Zend/Controller/Action/Exception.php';
throw new Zend_Controller_Action_Exception(
sprintf('Page "%s" does not exist.', $action), 404);
}
$this->renderScript($script);
}
// no action is called? Let the parent __call handle things.
else {
parent::__call($methodName, $params);
}
}
You have to play with the router
http://framework.zend.com/manual/en/zend.controller.router.html
I think you can specify a wildcard to catch every action on a specific module (the default one to reduce the url) and define an action that will take care of render the view according to the url (or even action called)
new Zend_Controller_Router_Route('index/*',
array('controller' => 'index', 'action' => 'custom', 'module'=>'index')
in you customAction function just retrieve the params and display the right block.
I haven't tried so you might have to hack the code a little bit
If you want to use gabriel1836's _call() method you should be able to disable the layout and view and then render whatever you want.
$this->_helper->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
I needed to have existing module/controller/actions working as normal in a Zend Framework app, but then have a catchall route that sent anything unknown to a PageController that could pick user specified urls out of a database table and display the page. I didn't want to have a controller name in front of the user specified urls. I wanted /my/custom/url not /page/my/custom/url to go via the PageController. So none of the above solutions worked for me.
I ended up extending Zend_Controller_Router_Route_Module: using almost all the default behaviour, and just tweaking the controller name a little so if the controller file exists, we route to it as normal. If it does not exist then the url must be a weird custom one, so it gets sent to the PageController with the whole url intact as a parameter.
class UDC_Controller_Router_Route_Catchall extends Zend_Controller_Router_Route_Module
{
private $_catchallController = 'page';
private $_catchallAction = 'index';
private $_paramName = 'name';
//-------------------------------------------------------------------------
/*! \brief takes most of the default behaviour from Zend_Controller_Router_Route_Module
with the following changes:
- if the path includes a valid module, then use it
- if the path includes a valid controller (file_exists) then use that
- otherwise use the catchall
*/
public function match($path, $partial = false)
{
$this->_setRequestKeys();
$values = array();
$params = array();
if (!$partial) {
$path = trim($path, self::URI_DELIMITER);
} else {
$matchedPath = $path;
}
if ($path != '') {
$path = explode(self::URI_DELIMITER, $path);
if ($this->_dispatcher && $this->_dispatcher->isValidModule($path[0])) {
$values[$this->_moduleKey] = array_shift($path);
$this->_moduleValid = true;
}
if (count($path) && !empty($path[0])) {
$module = $this->_moduleValid ? $values[$this->_moduleKey] : $this->_defaults[$this->_moduleKey];
$file = $this->_dispatcher->getControllerDirectory( $module ) . '/' . $this->_dispatcher->formatControllerName( $path[0] ) . '.php';
if (file_exists( $file ))
{
$values[$this->_controllerKey] = array_shift($path);
}
else
{
$values[$this->_controllerKey] = $this->_catchallController;
$values[$this->_actionKey] = $this->_catchallAction;
$params[$this->_paramName] = join( self::URI_DELIMITER, $path );
$path = array();
}
}
if (count($path) && !empty($path[0])) {
$values[$this->_actionKey] = array_shift($path);
}
if ($numSegs = count($path)) {
for ($i = 0; $i < $numSegs; $i = $i + 2) {
$key = urldecode($path[$i]);
$val = isset($path[$i + 1]) ? urldecode($path[$i + 1]) : null;
$params[$key] = (isset($params[$key]) ? (array_merge((array) $params[$key], array($val))): $val);
}
}
}
if ($partial) {
$this->setMatchedPath($matchedPath);
}
$this->_values = $values + $params;
return $this->_values + $this->_defaults;
}
}
So my MemberController will work fine as /member/login, /member/preferences etc, and other controllers can be added at will. The ErrorController is still needed: it catches invalid actions on existing controllers.
I implemented a catch-all by overriding the dispatch method and handling the exception that is thrown when the action is not found:
public function dispatch($action)
{
try {
parent::dispatch($action);
}
catch (Zend_Controller_Action_Exception $e) {
$uristub = $this->getRequest()->getActionName();
$this->getRequest()->setActionName('index');
$this->getRequest()->setParam('uristub', $uristub);
parent::dispatch('indexAction');
}
}
You could use the magic __call() function. For example:
public function __call($name, $arguments)
{
// Render Simple HTML View
}
stunti's suggestion was the way I went with this. My particular solution is as follows (this uses indexAction() of whichever controller you specify. In my case every action was using indexAction and pulling content from a database based on the url):
Get an instance of the router (everything is in your bootstrap file, btw):
$router = $frontController->getRouter();
Create the custom route:
$router->addRoute('controllername', new Zend_Controller_Router_Route('controllername/*', array('controller'=>'controllername')));
Pass the new route to the front controller:
$frontController->setRouter($router);
I did not go with gabriel's __call method (which does work for missing methods as long as you don't need a view file) because that still throws an error about the missing corresponding view file.
For future reference, building on gabriel1836 & ejunker's thoughts, I dug up an option that gets more to the point (and upholds the MVC paradigm). Besides, it makes more sense to read "use specialized view" than "don't use any view".
// 1. Catch & process overloaded actions.
public function __call($name, $arguments)
{
// 2. Provide an appropriate renderer.
$this->_helper->viewRenderer->setRender('overload');
// 3. Bonus: give your view script a clue about what "action" was requested.
$this->view->action = $this->getFrontController()->getRequest()->getActionName();
}
#Steve as above - your solution sounds ideal for me but I am unsure how you implmeented it in the bootstrap?