How to make a wrapper to call functions in different classes? - php

I'm trying to solve a problem in ajax which is coming out from the moment that my client asked me to not use any framework for web applications.
I have always used CodeIgniter and I never had any problem with ajax requests, especially when I had to call a method simply perform this call:
var postUrl = GlobalVariables.baseUrl + 'application/controllers/user.php/ajax_check_login';
//http://localhost/App_Name/application/controllers/user.php/ajax_check_login <-postUrl content
var postData =
{
'username': $('#username').val(),
'password': $('#password').val()
};
$.post(postUrl, postData, function(response)
{
// do stuff...
});
How you can see from the code above what I want to do is call a method within the controller user.php whose name is ajax_check_login.
What I have done so far to achieve the desired result is to make this code:
$allowed_functions = array('ajax_check_login');
$ru = $_SERVER['REQUEST_URI']
$func = preg_replace('/.*\//', '', $ru);
if (isset($func) && in_array($func, $allowed_functions)) {
$user = new User();
$user->$func();
}
if you want to see the complete structure of a class click here.
The problem is that this code should be placed inside each controller,
and you have to set all the methods offered, sometimes the function available reaching fifty, leads to discard this solution...
What I want to know is: how can I make a wrapper, a class that allows me to invoke a method of the controller from url and execute it?
Before all this work was done by CodeIgniter. So now I have to write my own class that allows me to access the controls easily and recall methods in different classes.
All classes that have to respond to ajax request reside in application/controllers / ... folder. In the controllers folder I have 20 controllers.

You can add ajax.php:
<?php
preg_match_all('/([^\/.]*)\.php\/([^\/]*)$/', $_SERVER['REQUEST_URI'], $matches);
$class = $matches[1][0];
$func = $matches[2][0];
$allowed_classes = array('user','account','foo');
if (isset($class) && isset($func) && in_array($class, $allowed_classes)) {
require_once "application/controllers/" . $class. ".php";
// here you could do some security checks about the requested function
// if not, then all the public functions will be possible to call
// for example if you don't want to allow any function to be called
// you can add a static function to each class:
// static function getAllowedFunctions() {return array('func1','func2');}
// and use it the same way you had checked it in the question
$obj = new $class();
$obj->$func();
// or if need to pass $_POST:
// call_user_func(array($obj, $func, $_POST));
}
and in javascript post to:
var postUrl = GlobalVariables.baseUrl + 'application/controllers/ajax.php/user.php/ajax_check_login';
If you have apache, then you might be able to do it even without adding ajax.php by adding this to .htaccess in the controller directory:
RewriteEngine On
RewriteBase /baseUrl.../application/controllers/
RewriteRule ^([^\.]*\.php)/[^/]*$ ajax.php?file=$1&func=$2
Of course you need your real baseUrl there. And change the 1st 3 lines in the php to:
$class = $_GET['class'];
$func = $_GET['func'];

Related

How are controller actions and views rendered through a single entry script in php frameworks?

For example in a Yii Framework application the url is in this format
www.example.com/index.php?r=foo/bar
Which renders the script inside the actionBar() method of class FooController. Further, this class (or its parent class) implements a render() method which can render a view file.
All the url's are handled through the entry script index.php.
I would like to write my own class which can handle url's through this way.
Can someone give me a very basic 'hello world' example of writing such a script ?
I'll give it a shot:
// index.php
$r = $_REQUEST['r']; // 'foo/bar'
$rParts = explode('/',$r);
$foo = $rParts[0];
$bar = $rParts[1];
$controller = new $foo; // foo
echo $controller->$bar();
Here is what I did for a friend recently, when teaching him how frameworks works. This is a basic example, but it demonstrates how a container works, how to handle the router, giving the controller a request and a response and handling redirects and the like.
<?php
require 'autoload.php';
$container = [];
$container['controller.elephant'] = function() {
return new Controller\Elephant();
};
$routes = [];
$routes['/babar'] = 'controller.elephant:babar';
$routes['/celeste'] = 'controller.elephant:celeste';
$request = new Request();
if (!isset($routes[$request->path()])) {
http_response_code(404);
exit;
}
$route = $routes[$request->path()];
list($class, $method) = explode(':', $route);
$controller = $container[$class]();
$response = $controller->{$method}($request, new Response());
if ($response->isRedirect()) {
http_response_code($response->status());
header('Location: '.$response->destination());
} else {
echo $response->content();
}
exit;
I won't include anything more than that (albeit there is other files) because it would bloat the answer needlessly (I can send them to you by other means if you want to).
I highly advise you to look at the Slim Framework code, as it is a micro framework that basically do just that.
Well in the Symfony documentation you have this page: http://symfony.com/doc/current/components/http_kernel/introduction.html
where it explains how is the life cycle of a request, it's just a flow diagram.
But it will give you a really good idea on how you should build yours
If you are more interested in how based on a url you get the controller you should read the RoutingComponent in symfony
http://symfony.com/doc/current/components/routing/introduction.html
http://symfony.com/doc/current/components/routing/hostname_pattern.html
But if you want to write your own class, you should use something like regex expression groups where you can detect the url parts separated by i.e: '/' then you somehow map the url to the controller i.e associative array 'Hash'
someurl.com/someController/someAction
$mappings = [
...
'someController' => 'The\Controller\Class'
]
$controller = new $mappings[$urlControllerPart]();
$response = $controller->{$urlActionPart}($request);
return $response;

how to build a good router for php mvc

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.

Is there a method to get the module/action a URL refers to?

In symfony, is there a method that I can use which does a reverse lookup on my routes to determine the module and action a URL points to?
Say, something like:
get_module("http://host/cars/list"); // ("cars")
get_action("http://host/frontend_dev.php/cars/list"); // ("list")
Bear in mind, I don't want to perform simple string-hacking to do this as there may be mappings that are not quite so obvious:
get_module("/"); // (What this returns is entirely dependent on the configured routes.)
Thanks!
Use the sfRouting class to match URLs. This is part of the sfContext object. Your code (in an action) would look like this:
public function executeSomeAction(sfWebRequest $request)
{
if($params = $this->getContext()->getRouting()->parse('/blog'))
{
$module = $params['module']; // blog
$action = $params['action']; // index
$route = $params['_sf_route'] // routing instance (for fun)
}
else
{
// your URL did not match
}
}

How to properly work _around_ Javascript persistence?

There is basic persistence of Javascript vars/etc. You call a function/method, and the next time you call that same function/method, it is holding the data from the last time.
You can delete the vars when you are done with them, but that removes the advantage of using the code again for that instance.
So what is the proper way to write code which can be reused, on different elements, inside the same page.
Therefore, I need the ability to write code so that I can point it at several different elements, and then interact with that code segregated for each element.
So in PHP (as an example) I would do:
$element1 = new MyClass();
$element2 = new MyClass();
$element3 = new MyClass();
in that case it's the same code running in three segregated scopes. How can I do this properly with JS. Even using jQuery's extend() gives me problems.
Thanks.
Use the var keyword when defining local variables (otherwise they'll default to globals).
function foo() {
var i;
// code code code code
}
To create an instance in JavaScript you need to write a constructor function, and call that using new. For instance:
function MyClass( somevalue ) {
this.somevalue = somevalue;
this.somefunction = function() {
alert(somevalue);
}
}
var instance1 = new MyClass(1);
var instance2 = new MyClass(2);
var instance3 = new MyClass(3);
You can namespace your JavaScript to make it a lot like what you're after. See below for an example. It does sound like your problem is related to using global variables where you want to use local variables though - i.e. you declare var myvariable; outside of your function, but only want to use it and forget it within your function. In that case, declare the variable inside your function to make it local.
var MyNameSpace = function() {
return {
sayhello : function() {
alert("hello");
},
saygoodbye : function() {
alert("see ya");
}
};
}();
It sounds like what you're looking for is the ability to have instances of a class and have private data that's associated with each instance.
You can do this using the following technique:
function Foo()
{
// Member variable only visible inside Foo()
var myPrivateVar;
// Function only visible inside Foo()
var myPrivateFunction = function()
{
alert("I'm private!");
}
// Member variable visible to all
this.myPublicVar = "Hi, I'm public!";
// Function visible to all
this.myPublicFunction = function()
{
myPrivateVar = "I can set this here!";
}
}
You can create and use one of these using the following syntax:
var myFoo = new Foo();
myFoo.myPublicVar = "I can set this!";
myFoo.myPublicFunction();

A Generic, catch-all action in Zend Framework... can it be done?

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?

Categories