42

I have been reading a lot about how and why to use an MVC approach in an application. I have seen and understand examples of a Model, I have seen and understand examples of the View.... but I am STILL kind of fuzzy on the controller. I would really love to see a thorough enough example of a controller(s). (in PHP if possible, but any language will help)

Thank you.

PS: It would also be great if I could see an example of an index.php page, which decides which controller to use and how.

EDIT: I know what the job of the controller is, I just don't really understand how to accomplish this in OOP.

James A Mohler
  • 11,060
  • 15
  • 46
  • 72
BDuelz
  • 3,890
  • 7
  • 39
  • 62
  • When you think about a controller think about flow control. Simply put another developer should be able to look at your controller and get a clear idea of what that action does how it does it without going into any detail. – Alex_Nabu Oct 26 '14 at 04:12
  • You should see my centralized `index.php`. It depends on URL rewriting. http://stackoverflow.com/questions/42172228/is-this-how-an-mvc-router-class-typically-works – Anthony Rutledge Feb 14 '17 at 11:55

6 Answers6

67

Request example

Put something like this in your index.php:

<?php

// Holds data like $baseUrl etc.
include 'config.php';

$requestUrl = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
$requestString = substr($requestUrl, strlen($baseUrl));

$urlParams = explode('/', $requestString);

// TODO: Consider security (see comments)
$controllerName = ucfirst(array_shift($urlParams)).'Controller';
$actionName = strtolower(array_shift($urlParams)).'Action';

// Here you should probably gather the rest as params

// Call the action
$controller = new $controllerName;
$controller->$actionName();

Really basic, but you get the idea... (I also didn't take care of loading the controller class, but I guess that can be done either via autoloading or you know how to do it.)

Simple controller example (controllers/login.php):

<?php    

class LoginController
{
    function loginAction()
    {
        $username = $this->request->get('username');
        $password = $this->request->get('password');

        $this->loadModel('users');
        if ($this->users->validate($username, $password))
        {
            $userData = $this->users->fetch($username);
            AuthStorage::save($username, $userData);
            $this->redirect('secret_area');
        }
        else
        {
            $this->view->message = 'Invalid login';
            $this->view->render('error');
        }
    }

    function logoutAction()
    {
        if (AuthStorage::logged())
        {
            AuthStorage::remove();
            $this->redirect('index');
        }
        else
        {
            $this->view->message = 'You are not logged in.';
            $this->view->render('error');
        }
    }
}

As you see, the controller takes care of the "flow" of the application - the so-called application logic. It does not take care about data storage and presentation. It rather gathers all the necessary data (depending on the current request) and assigns it to the view...

Note that this would not work with any framework I know, but I'm sure you know what the functions are supposed to do.

Franz
  • 11,353
  • 8
  • 48
  • 70
  • 20
    +1 I've been reading & searching around on MVC a lot lately, and that (to me) is the clearest example of a controller I think I've seen yet. – keithjgrant Apr 01 '10 at 18:19
  • 1
    Wouldn't the logoutAction belong in the LogoutController? – Kid Diamond May 21 '14 at 18:21
  • 5
    Or just think of it as AuthController. :) – Franz May 22 '14 at 07:34
  • 2
    It is critically important to note that this example does not consider security. The example is not implementing any encoding and special characters that leave the script open to attacks. Why? Because the script injects whatever is in the url (`$_SERVER['REQUEST_URI']`) into the PHP script. Nice and clear MVC controller example though. – Kristoffer Bohmann Feb 24 '16 at 17:28
  • Thanks for that remark, @KristofferBohmann. It's true that there's no extra validation of the URI (and the resulting controller and action name that we try to extract). But I see no "injection" going on here, or am I missing something? – Franz Feb 25 '16 at 09:32
  • `$controllerName` and `$actionName` are taking raw user-input + appending a word to the user-input. It is essentially the same as: `$controllerName = ucfirst($_GET['controller']).'Controller';` and `$actionName = strtolower($_GET['action']).'Action';` ... To be honest, I am unsure if this is a security issue. ... What I have in mind is to sanitize the user input with a function like this: `function h($str) { return trim(stripslashes(htmlspecialchars($str, ENT_QUOTES, 'utf-8'))); }` ... See also: [OWASP on code injection](https://www.owasp.org/index.php/Code_Injection). – Kristoffer Bohmann Feb 26 '16 at 07:21
  • Well, since we're only using it to build a class name, I guess the only thing is missing is some error handling in case the given file name cannot be found, right? – Franz Feb 26 '16 at 07:38
  • With the model, view and controller having clear names, what would files like `index.php` from this example that start the whole shebang be called? – WoodrowShigeru Aug 02 '19 at 19:56
  • @WoodrowShigeru If they are the only publicly accessible PHP script (i.e. the "front controller"), then `index.php` is a good name, as it matches conventions / defaults of most webserver software. – Franz Aug 02 '19 at 23:13
  • Well, in my project I don't bottleneck it all over one file, and I'm not using request-routing either (for legacy reasons, among other things). So I've set up htaccess-rewrite rules to reach the many files that are *like* `index.php`; that call their own Controller class. But so far, I had them in the `controllers/` dir – which I know understand is not what a controller is … I basically want to know how best to rename that dir. – WoodrowShigeru Aug 03 '19 at 09:09
  • If you're not doing any routing and just dispatching to the corresponding controller, I would just name them after the controller... But really, it's a bit hard to give good advice without seeing all the code. – Franz Aug 03 '19 at 20:55
  • I used this code but I don't know why my index.php is not called. Any idea @Franz ? – Krunal Pandya Feb 27 '20 at 11:13
2

Imagine three screens in a UI, a screen where a user enters some search criteria, a screen where a list of summaries of matching records is displayed and a screen where, once a record is selected it is displayed for editing. There will be some logic relating to the initial search on the lines of

if search criteria are matched by no records
    redisplay criteria screen, with message saying "none found"
else if search criteria are matched by exactly one record
    display edit screen with chosen record
else (we have lots of records)
    display list screen with matching records

Where should that logic go? Not in the view or model surely? Hence this is the job of the controller. The controller would also be responsible for taking the criteria and invoking the Model method for the search.

djna
  • 54,992
  • 14
  • 74
  • 117
  • So, for 300 points, would my scribble satisfy your definition of a controller based (MVC) system? http://stackoverflow.com/questions/42172228/is-this-how-an-mvc-router-class-typically-works – Anthony Rutledge Feb 14 '17 at 11:53
  • @Anthony Rutledge I don't see a controller as I would recognise it here. Your Model has a method getView(), which to me implies that the model is undertaking some of the function of a controller in the MVC sense. – djna Feb 14 '17 at 18:52
1
<?php

class App {
    protected static $router;

    public static function getRouter() {
        return self::$router;
    }

    public static function run($uri) {
        self::$router = new Router($uri);

        //get controller class
        $controller_class = ucfirst(self::$router->getController()) . 'Controller';
        //get method
        $controller_method = strtolower((self::$router->getMethodPrefix() != "" ? self::$router->getMethodPrefix() . '_' : '') . self::$router->getAction());

        if(method_exists($controller_class, $controller_method)){
            $controller_obj = new $controller_class();
            $view_path = $controller_obj->$controller_method();

            $view_obj = new View($controller_obj->getData(), $view_path);
            $content = $view_obj->render();
        }else{
            throw new Exception("Called method does not exists!");
        }

        //layout
        $route_path = self::getRouter()->getRoute();
        $layout = ROOT . '/views/layout/' . $route_path . '.phtml';
        $layout_view_obj = new View(compact('content'), $layout);
        echo $layout_view_obj->render();
    }

    public static function redirect($uri){
        print("<script>window.location.href='{$uri}'</script>");
        exit();
    }
}
Nisse Engström
  • 4,738
  • 23
  • 27
  • 42
David
  • 11
  • 1
0
<?php
class Router {

    protected $uri;

    protected $controller;

    protected $action;

    protected $params;

    protected $route;

    protected $method_prefix;

    /**
     * 
     * @return mixed
     */
    function getUri() {
        return $this->uri;
    }

    /**
     * 
     * @return mixed
     */
    function getController() {
        return $this->controller;
    }

    /**
     * 
     * @return mixed
     */
    function getAction() {
        return $this->action;
    }

    /**
     * 
     * @return mixed
     */
    function getParams() {
        return $this->params;
    }

    function getRoute() {
        return $this->route;
    }

    function getMethodPrefix() {
        return $this->method_prefix;
    }

        public function __construct($uri) {
            $this->uri = urldecode(trim($uri, "/"));
            //defaults
            $routes = Config::get("routes");
            $this->route = Config::get("default_route");
            $this->controller = Config::get("default_controller");
            $this->action = Config::get("default_action");
            $this->method_prefix= isset($routes[$this->route]) ? $routes[$this->route] : '';


            //get uri params
            $uri_parts = explode("?", $this->uri);
            $path = $uri_parts[0];
            $path_parts = explode("/", $path);

            if(count($path_parts)){
                //get route
                if(in_array(strtolower(current($path_parts)), array_keys($routes))){
                    $this->route = strtolower(current($path_parts));
                    $this->method_prefix = isset($routes[$this->route]) ? $routes[$this->route] : '';
                    array_shift($path_parts);
                }

                //get controller
                if(current($path_parts)){
                    $this->controller = strtolower(current($path_parts));
                    array_shift($path_parts);
                }

                //get action
                if(current($path_parts)){
                    $this->action = strtolower(current($path_parts));
                    array_shift($path_parts);
                }

                //reset is for parameters
                //$this->params = $path_parts;
                //processing params from url to array
                $aParams = array();
                if(current($path_parts)){ 
                    for($i=0; $i<count($path_parts); $i++){
                        $aParams[$path_parts[$i]] = isset($path_parts[$i+1]) ? $path_parts[$i+1] : null;
                        $i++;
                    }
                }

                $this->params = (object)$aParams;
            }

    }
}
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
David
  • 11
  • 1
0
  1. Create folder structure
  2. Setup .htaccess & virtual hosts
  3. Create config class to build config array

Controller

  1. Create router class with protected non static, with getters
  2. Create init.php with config include & autoload and include paths (lib, controlelrs,models)
  3. Create config file with routes, default values (route, controllers, action)
  4. Set values in router - defaults
  5. Set uri paths, explode the uri and set route, controller, action, params ,process params.
  6. Create app class to run the application by passing uri - (protected router obj, run func)
  7. Create controller parent class to inherit all other controllers (protected data, model, params - non static) set data, params in constructor.
  8. Create controller and extend with above parent class and add default method.
  9. Call the controller class and method in run function. method has to be with prefix.
  10. Call the method if exisist

Views

  1. Create a parent view class to generate views. (data, path) with default path, set controller, , render funcs to return the full tempalte path (non static)
  2. Create render function with ob_start(), ob_get_clean to return and send the content to browser.
  3. Change app class to parse the data to view class. if path is returned, pass to view class too.
  4. Layouts..layout is depend on router. re parse the layout html to view and render
Jefferson
  • 794
  • 10
  • 24
David
  • 11
  • 1
-2

Please check this:

    <?php
    global $conn;

    require_once("../config/database.php");

    require_once("../config/model.php");

    $conn= new Db;

    $event = isset($_GET['event']) ? $_GET['event'] : '';

    if ($event == 'save') {
        if($conn->insert("employee", $_POST)){
            $data = array(
                'success' => true,
                'message' => 'Saving Successful!',
            );
        }

        echo json_encode($data);
    }

    if ($event == 'update') {
        if($conn->update("employee", $_POST, "id=" . $_POST['id'])){
            $data = array(
                'success' => true,
                'message' => 'Update Successful!',
            );
        }

        echo json_encode($data);
    }

    if ($event == 'delete') {
        if($conn->delete("employee", "id=" . $_POST['id'])){
            $data = array(
                'success' => true,
                'message' => 'Delete Successful!',
            );
        }

        echo json_encode($data);
    }

    if ($event == 'edit') {
        $data = $conn->get("select * from employee where id={$_POST['id']};")[0];
        echo json_encode($data);
    }
?>