Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I implement controller action in a PHP MVC framework?

I'm trying to create my own MVC framework in PHP to learn about them but I'm currently having trouble implementing the action of the controller.

The problem is that some controllers have one or multiple arguments such as actionView($id), while others have no arguments like actionCreate(). How do I handle these differently?

For example:

$action; //contains my action
$params; //array containing my arguments

In my controller I would call...

$this->$action(); //for no parameters
$this->$action($params[0]); //for one parameter
$this->$action($params[0], $params[1]); //for two parameters
... and so on

It wouldn't be feasible to handle every possible situation like this. Perhaps I am implementing the actions wrong. Can someone guide me in the right direction?

EDIT: How do the MVC frameworks out there make handling multiple arguments possible?

like image 548
Rain Avatar asked Dec 20 '25 18:12

Rain


2 Answers

I would simply pass the array of arguments as the only argument to every action. Let it be known that your framework's convention includes passing arguments to actions in this way. This allows the implementing code to choose how they want to deal with it; use func_get_args() maybe or provide a paramater in the method signature:

public function action(array $args = array()) { ...

Something to be wary of though is that although your action may require a parameter the user may not actually provide one. If your method signature is setup to require a value be passed and you don't have enough values to plug in then you may get all kinds of errors. This is part of the reason I would choose to simply pass in the array of parameters and always pass an array, even if it is empty.


Alternatively, partly influence off of BrNathanH answer, you could somehow tie an object to a given action and then inject that array into the object's constructor. That object can be responsible for providing the data needed for the action and giving suitable defaults/checking for the values to exist. You could then just add an object signature to your actions, if you wanted to be doubly sure that they are getting the appropriate data. I'm not sure exactly how you would implement this in your own code, the primary problem being the mapping of controller::action to the appropriate "ParamObject". But wrapping the array into objects might be a good idea and would solve the problem of not knowing how many parameters to pass. It's always one, the "ParamObject" associated with that action.

like image 96
Charles Sprayberry Avatar answered Dec 22 '25 12:12

Charles Sprayberry


The best way I know how to explain this is with examples, so here is an example of my own implementation for a PHP-MVC application. Make sure to pay close attention to the Hook::run function, which handles the arguments for loading the control.

index.php:

<?php
class Hook {
    const default_controller = 'home';
    const default_method = 'main';
    const system_dir = 'system/';

    static function control ($name, $method, $parameter) {
        self::req(Hook::system_dir.$name.'.php'); // Include the control file
        if (method_exists('Control', $method)) { // For my implementation, I can all control classes "Control" since we should only have one at a time
            if (method_exists('Control', 'autoload')) call_user_func(array('Control', 'autoload')); // This is extremely useful for having a function to execute everytime a particular control is loaded
            return call_user_func(array('Control', $method), $parameter); // Our page is actually a method
        }
        throw new NotFound($_GET['arg']); // Appear page.com/control/method does not exist, so give a 404
    }

    static function param ($str, $limit = NULL) { // Just a wrapper for a common explode function
        return (
            $limit
                ? explode('/', rtrim($str, '/'), $limit)
                : explode('/', rtrim($str, '/'))
            );
    }

    static function req ($path) { // Helper class to require/include a file
        if (is_readable($path)) return require_once($path); // Make sure it exists
        throw new NotFound($path); // Throw our 404 exeception if it doesn't
    }

    static function run() {
        list($name, $method, $parameter) = ( // This implementaion expects up to three arguements
            isset($_GET['arg'])
                ? self::param($_GET['arg'], 3) + array(self::default_controller, self::default_method, NULL) // + array allows to set for default params
                : array(self::default_controller, self::default_method, NULL) // simply use default params
        );
        return self::control($name, $method, $parameter); // Out control loader
    }

}

class AuthFail extends Exception {}
class UnexpectedError extends Exception {}
class NotFound extends Exception {}

try {
    Hook::run();
}
catch (AuthFail $exception) { // Makes it posssible to throw an exception when the user needs to login
    // Put login page here
    die('Auth failed');
}
catch (UnexpectedError $exception) { // Easy way out for error handling
    // Put error page here
    die('Error page');
}
catch (NotFound $exception) { // Throw when you can't load a control or give an appearance of 404
    die('404 not found');
}

system/home.php:

<?php

class Control {
    private function __construct () {}
    static function autoload () { // Executed every time home is loaded
        echo "<pre>Home autoload\n";
    }
    static function main ($param='') { // This is our page
        // Extra parameters may be delimited with slashes
        echo "Home main, params: ".$param;
    }
    static function other ($param='') { // Another page
        echo "Home other, params:\n";
        $param = Hook::param($param);
        print_r($param);
    }
}

.htaccess:

RewriteEngine On

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l

RewriteRule ^(.+)$ index.php?arg=$1 [QSA,L]

With this htaccess file, we are able to load the home.php control using localhost/home/main. Without it, our urls would look like localhost/index?args=home/main.

Demonstration screenshot 1 (localhost/simple_mvc/home/main/args1/args2/args3): Demonstration screenshot 1

You don't always know how many arguments an particular control method is going to expect, which is why I believe it is best to pass a single argument delimited by slashes. If the control method expects more than one argument, you can then use the provided Hook::param function to further evaluate.

Demonstration screenshot 2 (localhost/simple_mvc/home/other/args1/args2/args3): Demonstration screenshot 2

To answer your question:
The key file here that really helps to answer your question is the .htaccess file, which transparently turns localhost/these/types/of/urls into something like localhost/index.php?args=these/types/of/urls. That then allows you to split the arguments using explode($_GET['args'], '/');.

A GREAT video tutorial on this subject is here. It really helps to explain a lot.

I've made some changes to home.php and index.php with the latest edit

like image 25
Shea Avatar answered Dec 22 '25 12:12

Shea



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!