phpmodel-view-controllerhmvc

PHP hierarchical MVC design from scratch?


Background

I have been working through various tutorials over the past couple of months and am currently trying understand PHP frameworks.

One of the ways I am doing this is by trying to design my own very simple MVC framework from scratch.

I am trying to re-factor an application (which I have already built using spaghetti procedural PHP). This application has a front end for teachers and a back-end for the administrators.

I would like to separate concerns and have URL's like this

http://example.com/{module}/{controller}/{method}/{param-1}/{param-2}

Now the MVC framework I have cobbled together up to this point does not handle routing for 'modules' (I apologise if this is not the correct terminology), only the controller/method/params.

So I have separated the public_html from the app logic and inside of the /app/ folder I have specified two folders, my default "learn module" and the "admin module" so that the directory tree looks like this:

enter image description here

Apparently this design pattern is a "H"MVC?

My Solution

I am basically making use if the is_dir(); function to check if there is a "module" directory (such as "admin") and then unsetting the first URL array element $url[0] and reindexing the array to 0... then I am changing the controller path according to the URL... the code should be clearer...

<?php

class App
{

    protected $_module = 'learn'; // default module --> learn
    protected $_controller = 'home'; // default controller --> home
    protected $_method = 'index'; // default method --> index
    protected $_params = []; // default parameters --> empty array

    public function __construct() {

        $url = $this->parseUrl(); // returns the url array

        // Checks if $url[0] is a module else it is a controller
        if (!empty($url) && is_dir('../app/' . $url[0])) {

            $this->_module = $url[0]; // if it is a model then assign it
            unset($url[0]);

            if (!empty($url[1]) && file_exists('../app/' . $this->_module . '/controllers/' . $url[1] . '.php')) {

                $this->_controller = $url[1]; // if $url[1] is also set, it must be a controller
                unset($url[1]);
                $url = array_values($url); // reset the array to zero, we are left with {method}{param}{etc..}

            }

        // if $url[0] is not a module then it might be a controller...
        } else if (!empty($url[0]) && file_exists('../app/' . $this->_module . '/controllers/' . $url[0] . '.php')) {

            $this->controller = $url[0]; // if it is a controller then assign it
            unset($url[0]);
            $url = array_values($url); // reset the array to zero

        } // else if url is empty default {module}{controller}{method} is loaded

        // default is ../app/learn/home/index.php
        require_once '../app/' . $this->_module . '/controllers/' . $this->_controller . '.php';
        $this->_controller = new $this->_controller;

        // if there are methods left in the array
        if (isset($url[0])) {
            // and the methods are legit
            if (method_exists($this->_controller, $url[0])) {
                // sets the method that we will be using
                $this->_method = $url[0];
                unset($url[0]);

            } // else nothing is set
        }

        // if there is anything else left in $url then it is a parameter
        $this->_params = $url ? array_values($url) : [];
        // calling everything
        call_user_func_array([$this->_controller, $this->_method], $this->_params);
    }

    public function parseUrl() {
        // checks if there is a url to work with
        if (isset($_GET['url'])) {
            // explodes the url by the '/' and returns an array of url 'elements'
            return $url = EXPLODE('/', filter_var(rtrim($_GET['url'], '/'), FILTER_SANITIZE_URL));
        }
    }
}

this so far appears to be working for me, but.....

Question

I am not sure if this is the preferred solution to this issue. Is calling the is_dir() check for every page request going slow down my app?

How would you engineer a solution or have I completely misunderstood the issue?

Many thanks in advance for your time and consideration!!


Solution

  • In my experience, I often use .htaccess file to redirect any request to an only index.php file, both these files place in the public_html folder. The content of .htaccess file as following (your apache server should enabled mod_rewrite):

    <IfModule mod_rewrite.c>
     RewriteEngine On
     RewriteBase /
     RewriteRule ^index\.php$ - [L]
     RewriteCond %{REQUEST_FILENAME} !-f
     RewriteCond %{REQUEST_FILENAME} !-d
     RewriteRule . /index.php [L]
    </IfModule>
    

    Then, in the index.php file you can parse request url, define configs, common variables, paths, etc... and include neccessary others php files. An example:

     require( dirname( __FILE__ ) . '/your_routing.php' );
     require( dirname( __FILE__ ) . '/your_configs.php' );
     require( dirname( __FILE__ ) . '/admin/yourfile.php' );
     ...