3

I am trying to delve into MVC with Front Controller Design.

I want to invoke my entire application by using one line, for example in index.php:

require_once(myclass.php);
$output = new myClass();

I would love to get rid of the require_once line, but I don't see how I could load my class without including it?

Anyway my main question would be how to load my various controllers and models and views etc using a one front end class. So far I have come up with:

class myClass
{
    private $name;
    public $root = $_SERVER['DOCUMENT_ROOT'];
    private $route = array("Controller" => "", "Model" => "", "View" => "", "Paremeters" => "");
    function __construct() 
    {   $uri = explode("/",$_SERVER['REQUEST_URI']);
        if(isset($uri[2])) {$this->route['Controller'] = $uri[2];}
        if(isset($uri[3])) {$this->route['Model'] = $uri[3];}
        if(isset($uri[4])) {$this->route['View'] = $uri[4];}
        if(isset($this->route['Controller'])) 
        {
            include($this->root."/".$this->route['Controller'].".php");
        }
    }

}

But it seems a bit convoluted and over the top. Also, once i've included the new class in the __construct, How am I supposed to load it?

Im sorry for the lack of knowledge, I have googled this a number of times and I keep coming up with the same pages that don't seem to expand my knowledge on the matter.

Chud37
  • 4,907
  • 13
  • 64
  • 116
  • 1
    If you want to get rid of `require` use [autoloading](http://php.net/manual/en/language.oop5.autoload.php) – Thomas Glaser Nov 19 '12 at 12:24
  • but then where would I put the autoload function? I would like my index file to be the $ouput = new myClass() only, how will it know to look in myClass.php for the autoload? – Chud37 Nov 19 '12 at 12:26
  • you might find [this](http://stackoverflow.com/a/13312606/727208) and [this](http://stackoverflow.com/a/9855170/727208) relevant. – tereško Nov 19 '12 at 12:35
  • You could also take a look at code of existing mvc frameworks. For example [Kohana](http://kohanaframework.org/) – Thomas Glaser Nov 19 '12 at 12:46

3 Answers3

5

If your URLs are definitely going to be http://domain.com/[controller]/[action]/parameters, then your front controller could look like this:

<?php
class Application
{
    public function __construct()
    {
        $this->setupAutoloader();
        $this->route();
    }

    private function setupAutoloader()
    {
        // do your autoloading here
    }

    private function route()
    {
        $request = explode('/', trim($_SERVER['REQUEST_URI'], '/'));

        $controller = isset($request[0]) ? ucwords(array_shift($request)) . 'Controller' : 'HomeController';
        $action = isset($request[0]) ? array_shift($request) : 'index';
        $parameters = $request;

        $response = call_user_func_array(array($controller, $action), $parameters);
    }
}

From here, you can add your autoloader implementation, do whatever you want with your response, and call it from your index.php as follows:

<?php
require 'path/to/Application.php';

$application = new Application();

Unfortunately, you’re always going to have to include the very first file if it’s stored elsewhere in your file system, but as above from here you can then autoload other classes, such as libraries, controllers, models etc.

Martin Bean
  • 38,379
  • 25
  • 128
  • 201
  • Just checking... It may be correct... Is `array_unshift()` correct or should there be `array_shift()`? – Jo Smo Jul 13 '14 at 20:43
  • 1
    @tastro Looking at the code again it should be `array_shift()`, yes. Good spot! My bad. – Martin Bean Jul 13 '14 at 20:50
  • Pursuant to this answer, I agree with you Chud37, I hate include & require and prefer auto-loading my classes. However, I think just the 1 require statement in your front controller file is acceptable. I built an MVC framework that is loosely based on Zend Framework, and it works exactly like this answer (with the single require call in the index.php file). You can check it out if you are interested, here https://github.com/jfingar/tinyphp – Jason Fingar Jul 13 '14 at 21:01
2

In order to load a class without manually include it you should take a look at spl_autoload_register that enables you to automatically load classes whenever required by the application.

As for the other question, well, it's all up to your design. If you want to load the common "controller" and "action" you should create a routable folder (this means the folder in which only controllers can stay) and place all the controller classes and then create a routable action space (the group of routable methods inside a controller class, usually is by defining the convention that actions methods are prefixed by action_). When you are done with that simply create your controllers like this:

class Blog {
    public function action_posts() { ... };
}

and manage it so that the URL /blog/posts/ will call this. (It just needs the same URI manipulation you have already proved to be good at). (Notice that the autoloader should handle the including of the actual class file by itself without requiring you to load all controller classes). Once you have the controller name and action you just need of something on the line of:

if (class_exists($controller)) {
    $app = new $controller;
    if (method_exists($app, $action)) {
        $app->$action(); // also take a look at call_user_func and call_user_func_array
    }
}

You can also allow parameters, but that is going to be quite difficult, in order to allow this kind of result:

class Blog {
    public function action_post($id) { ... };
}

and be called by /blog/post/14/ for example.

When dealing with URI also remember that you could have bad inputs and that you should handle them, but if you have created the correct routing space you are half way done already.

Finally if you want to have inspiration or for anything else, just take a look at CodeIgniter (which is one of the most easy to learn frameworks out there) or any other framework.

Hope it helped.

Shoe
  • 74,840
  • 36
  • 166
  • 272
2

I'm surprised that in those two long and informative previous answers nobody bothered to actually answer your question in the simplest way.

You want the __autoload() function. You'll still have to define it somewhere in your code, but it can simply be added to a global header file and then you don't have to explicitly write an include for every class definition.

/* auto load model classes */
function __autoload($class_name) {
        $filename = 'class.' . strtolower($class_name) . '.php';
        $file = __SITE_PATH . '/_model/' . $filename;
        if( file_exists($file) == false ) {
                return false;
        }
        require($file);
}
Sammitch
  • 30,782
  • 7
  • 50
  • 77
  • As time flies by this isn't the correct way to do so anymore. __autoload is deprecated since PHP 7.2.0 and removed since PHP 8.0.0. Therefore a autoloader should only be registered using spl_autoload_register as described in the answer below. – Alexander Behling Mar 28 '23 at 08:02