Jump to content

Conceptual question: user authentication with simple MVC


cgm225

Recommended Posts

I have a simple MVC (included below) which works fine, and recently I also coded a user authentication class (also included below) which works fine as well.  However, I need the user authentication object to be available to all modules pulled in by the MCV, but I am not sure the best way to integrate this "global-like" object within my MVC without mixing their logics (which I want to avoid). 

 

Basically, I want to do something like instantiate the authentication class before/outside the MVC class, as such...

 

$mysqli = new mysqli(MYSQL_SERVER, MYSQL_SERVER_USERNAME, MYSQL_SERVER_PASSWORD,
    'example_authentication');
$authentication = new MysqliAuthentication($username, $password, $mysqli);

 

..and then somehow have the doLogin() and checkPermission() methods available to all modules brought in by the MCV (because other applications are going to need to check if someone is logged in or has proper permissions).

 

Any ideas?  Thank you all in advance!

 

 

My MVC:

<?php

class FrontController extends ActionController {

    //Declaring variable(s)
    private static $instance;
    protected $controller;

    private function __construct(){}

    //Starting new instance of this class with a singleton pattern
    public static function getInstance() {
        if(!self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function dispatch($throwExceptions = false) {

        /*  
            Checking for the GET variables $module and $action, and, if present,
            will strip them down with a regular expression function with a white
            list of allowed characters, removing anything that is not a letter,
            number, underscore or hyphen.
        */
        $regex  = '/[^-_A-z0-9]++/';
        $module = isset($_GET['module']) ? preg_replace($regex, '', $_GET['module']) : 'home';
        $action = isset($_GET['action']) ? preg_replace($regex, '', $_GET['action']) : 'frontpage';

        /*
            Generating Actions class filename (example: HomeActions) and path to
            that class (example: home/HomeActions.php5), checking if $file is a
            valid file, and then, if so, requiring that file.
        */
        $class = ucfirst($module) . 'Actions';
        $file  = $this->pageDir . '/' . $module . '/' . $class . '.php5';

        if (!is_file($file)) {
            throw new FrontControllerException('Page not found!');
        }

        require_once $file;

        /*  
            Creating a new instance of the Actions class (example: $controller
            = new HomeActions(), and passing page directory variable to the
            ActionController class.
        */
        $controller = new $class();
        $controller->setPageDir($this->pageDir);

        try {
            //Using the setModule method in the ActionController class
            $controller->setModule($module);

            /*
                The ActionController dispatchAction method Checks if the method
                exists, then runs the displayView function in the
                ActionController class.
            */    
            $controller->dispatchAction($action);
        }
        catch(Exception $error) {

            /*
                An exception has occurred, and will be displayed if
                $throwExceptions is set to true.
            */
            if($throwExceptions) {
                echo $error->errorMessage($error); //Full exception echoed
            } else {
                echo $error->errorMessage(null); //Simple error messaged echoed
            }
        }
    }
}

abstract class ActionController {

    //Declaring variable(s)
    protected $pageDir;
    protected $module;
    protected $viewData = array();

    public function setPageDir($pageDir){
        $this->pageDir = $pageDir;
    }

    public function setModule($module) {
        $this->module = $module;
    }

    public function getModule() {
        return $this->module;
    }

    //Placing a value in the $viewData array at the key position
    public function setVar($key, $value) {
        $this->viewData[$key] = $value;
    }

    //Returns a value from the $viewData array located at the key position
    public function getVar($key) {
        if (array_key_exists($key, $this->viewData)) {
            return $this->viewData[$key];
        }
    }

    public function getViewData($viewData) {
        $this->viewData = $viewData;
    }

    /*
        Checking for actionMethod in the Actions class (example: doFrontpage()
        within home/HomeActions.php5) with the method_exists function and, if
        present, the actionMethod and displayView functions are executed.
    */  
    public function dispatchAction($action) {
        $actionMethod = 'do' . ucfirst($action);
        if (!method_exists($this, $actionMethod)) {
            throw new FrontControllerException('Page not found!');
        }
        $this->$actionMethod();
        $this->displayView($action);
    }

    public function displayView($action) {
        if (!is_file($this->pageDir . '/' . $this->getModule() . '/' . $action . 'View.php5')) {
            throw new FrontControllerException('Page not found!');
        }

        //Setting $this->actionView to the path of the action View file
        $this->actionView = $this->pageDir . '/' . $this->getModule() . '/' . $action . 'View.php5';

        /*
            Creating a new instance of the View class and passing the $pageDir,
            $viewData, and actionView variables
        */  
        $view = new View();
        $view->setPageDir($this->pageDir);
        $view->getViewData($this->viewData);
        $view->setVar('actionView',$this->actionView);
        $view->render();
    }
}

class View extends ActionController {

    public function render() {

        //Including default template settings
        require_once $this->pageDir . '/defaultTplSettings.php5';

        /*
            Merging the default template variables with variables provided in
            the $this->viewData array, taken from the Actions class, giving
            priority to those provided by the action class, then extracting the
            array with the extract() function, which creates a variable for
            every array value, and the name of the value (the variable name) is
            the key's value.
        */
        $templateSettings = array_merge($defaultTemplateSettings, $this->viewData);
        extract($templateSettings, EXTR_OVERWRITE);

        //Including template file within which the action View file is included
        require_once $this->pageDir . '/default.tpl';
    }
}

class FrontControllerException extends Exception {
    public function errorMessage($error) {
        
        /*
            If and throwExceptions is true, then the full exception will be
            returned.
        */
        $errorMessage = isset($error) ? $error : $this->getMessage();
        return $errorMessage;
    }
}

?>

 

 

My user authentication class:

interface Authentication {
    public function doLogin();
    public function checkPermission($permission);
}

class MysqliAuthentication implements Authentication {
    
    /* Variable declaration
     */
    private $username;
    private $password;
    private $connection;

    /* Sets username and raw password for authentication, as well as the 
     * mysqli connection object WITH database selected
     */
    public function __construct($username, $password, mysqli $connection) {
        $this->username = $username;
        $this->password = md5($password);
        $this->connection = $connection;
    }

    /* Checks provided username and password against a MySQL database table and
     * returns true if login is successful, followed with setting of username
     * and password session variables.
     */
    public function doLogin() {
        $query = "SELECT COUNT(*) FROM users
            WHERE username = ? AND password = ?";
        $statement = $this->connection->prepare($query);
        $statement->bind_param('ss', $this->username, $this->password);
        $statement->execute();
        $statement->bind_result($count);
        $statement->fetch();
        if ($count == 1) {
            return $this->setSession($this->username);
        } else {
            return false;
        }
    }

    /* Checks if a username has a given permission found within a MySQL database
     * table, returning true if the username has the given permission.
     */
    public function checkPermission($permission) {
        $query = "SELECT COUNT(*) FROM permissions
            WHERE username = ? AND permission_for = ?";
        $statement = $this->connection->prepare($query);
        $statement->bind_param('ss', $this->username, $permission);
        $statement->execute();
        $statement->bind_result($count);
        $statement->fetch();
        return $count == 1;
    }

    /* Sets the provided username and password to session variables
     */
    private function setSession($username) {
        $_SESSION['username'] = $username;
        return true;
    }
}

Link to comment
Share on other sites

Check out singletons:

 

http://us2.php.net/manual/en/language.oop5.patterns.php#language.oop5.patterns.singleton

 

Basically, you do the following:

1) Create a private static variable

2) Store $this into that variable in the constructor

3) Create a public static method called getInstance() that returns the value of the static variable

4) In your other classes, you would get the object like:

$auth = MysqliAuthentication::getInstance();

Link to comment
Share on other sites

You actually already use the concept in your FrontController

 

With this, you will only be allowed to have one user, but I would assume that is what you want...right?

 

class MysqliAuthentication implements Authentication {
    
    /* Variable declaration
     */
    private $username;
    private $password;
    private $connection;
    private static $instance;

    /* Sets username and raw password for authentication, as well as the 
     * mysqli connection object WITH database selected
     */
    public function __construct($username, $password, mysqli $connection) {
        $this->username = $username;
        $this->password = md5($password);
        $this->connection = $connection;
        self::$instance = $this;
    }
    public static function getInstance(){
      return self::$instance;
    }
....

 

So now, you do this once:

$mysqli = new mysqli(MYSQL_SERVER, MYSQL_SERVER_USERNAME, MYSQL_SERVER_PASSWORD,
    'example_authentication');
$authentication = new MysqliAuthentication($username, $password, $mysqli);

 

And from anywhere, can you retrieve it with:

$auth = MysqliAuthentication::getInstance();
$auth->doLogin();

Link to comment
Share on other sites

Thanks again for the help and examples.  I tried your code (with the new variable declaration and constructor), and it works as it should when I test the following Outside my MVC, like in my index.php5 file (after I include the authentication class) for example...

 

[ Line 1 ] $mysqli = new mysqli(MYSQL_SERVER, MYSQL_SERVER_USERNAME, MYSQL_SERVER_PASSWORD,
[ Line 2 ]     'example_authentication');
[ Line 3 ] $authentication = new MysqliAuthentication($username, $password, $mysqli);
[ Line 4 ] $auth = MysqliAuthentication::getInstance();
[ Line 5 ] $auth->doLogin();

 

However, if I first include lines 1 - 3, or even the 4th as well, before I instantiate my MVC class (like earlier in my index.php5 file), and then try to call the $auth->doLogin method in my Action Class (which is called by my MCV (see earlier code; an example would be HomeActions), I get the following errors

 

Notice: Undefined variable: auth ...

Fatal error: Call to a member function doLogin() on a non-object ...

 

Any ideas on what I am doing wrong?

 

Thanks again for all your help!!

Link to comment
Share on other sites

Almost there. Inside your MVC class, you don't have access to $auth, as it's outside of your scope. So, we simply retrieve it again with getInstance()

 

class FrontController extends ActionController {

  //blah blah
  function someFunction ( ) {
    $auth = MysqliAuthentication::getInstance();
    $auth->doLogin();
  }
  //blah blah
}

 

Then in index.php

<?php
$mysqli = new mysqli(MYSQL_SERVER, MYSQL_SERVER_USERNAME, MYSQL_SERVER_PASSWORD,'example_authentication');
new MysqliAuthentication($username, $password, $mysqli); //We don't even need to store the object in a variable since it's stored within itself

$mvc = new FrontController();
$mvc->someFunction();
?>

Link to comment
Share on other sites

So follow up question: given the structure of my MVC, where would you place the...

 

$auth = MysqliAuthentication::getInstance();

 

...so that the doLogin() method is available to all the extended classes?  I was thinking somewhere in the ActionController class?  I just don't want to have to redundantly include that line of code every time I want to use the doLogin().

 

Thanks again.

Link to comment
Share on other sites

That is exactly what I needed.  And here is another question, in my case, with my MVC, how would you get that $this->auth object value to the abstract ActionController class (which most of my classes extend)?  Would you create a setNewAuth function in the ActionController and pass it that way?  Like this I mean (similar to how I pass the page dir):

 

    public function setNewAuth($newAuth){
        $this->auth = $newAuth;
    }

 

Or would you do it another way?  My ActionController is abstract so I don't have a constructor in there.

 

Edit: Also, again, I want to be careful here that I am not mixing up my different logics?

 

Thanks again!

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.