4

I am building a custom MVC framework using PHP. My problem is when I want to access any model class through the controller class. One way I have seen this done is through a registry design pattern using magic methods such as get and set, though PHP get and set are considered bad practise by some. I have read about dependency injection done through a container, but I can not see this working effectily as the container would have to call the models or it would have to contain the models which would defeat the purpose of MVC and create a massive super class. Singleton is seen as bad practise. Is there any solutions or improvements of the methods I have mentioned. It may just be my understand and knowledge of PHP needs improving.

Currently I have this: router.php (loads up controllor through a GET varible

 <?php

class router {



function __construct() {

    if (file_exists("controller/".$_GET['url']."Controller.php"))  {


       function __autoload($controller) {

           $controlinclude = "controller/".$controller.".php";
            include $controlinclude;

      }
       $control = $_GET['url']."Controller";
        new $control();


    }
    else    {

        // throw exception
    }

}

}
?>

Hope that makes sence

Thom Wiggers
  • 6,938
  • 1
  • 39
  • 65
Matthew Underwood
  • 1,459
  • 3
  • 19
  • 33
  • Well if you put the autloader on some more general level, it could at least auto-load your controllers and the models. And you wouldn't need to make up your mind so much about patterns at the moment. – hakre Mar 12 '12 at 21:36
  • The easiest way is to browse through current MVC frameworks and see how they do it, find what makes the most sense for your use and try something similar. – aknosis Mar 12 '12 at 21:51

2 Answers2

6

First of all ... Do no put autoloading script in routing mechanism. You are mixing the responsibilities. You will be better off creating a separate class for this based on spl_autoload_register.

Neeext .. do no put complicated operations on constructor. It is makes you code somewhat untestable. Maybe you should be something like:

// you might want to replace $_GET with $_SERVER['QUERY_STRING'] later
$router = new Router( $_GET['url'] );

// where 'default' is the name of fallback controller
$controller_class = $router->get_controller( 'default' );
$method_name = $router->get_action( 'index' );

$model_factory = new ModelFactory( new PDO( ... ) );
$controller = new {$controller_class}( $model_factory );
$controller->{$method_name}();

Additionally, you should look into php namespaces. There is no point in ending class with ...Controller just to know where the class will be located.

Ok ... back to the Model.

There is quite common misconception about models in web development community ( i blame RoR for this mess ). Model in MVC is not a class, but an application layer which contains multitude of instances. Most of the instances belong to one of two types of classes. With following responsibilities:

  • Domain Logic :

    Deals with all the computation, calculation and all the domain specific details. Objects in this group have no knowledge of where and how data is actually stored. They only manipulate the information.

  • Data Access

    Usually made of objects that fit DataMapper pattern (do not confuse with ORM of same name .. nothing in common). Responsible for storing data from Domain Objects and retrieving them. Might be in database.. might not. This is where your SQL queries would be.

In semi-real world situation () it might looks something like this (related to code abowe):

class SomeController
{
   // ... snip ...
   protected $model_factory = null;
   // ... snip ...

   public function __construct( ModelFactory $factory )
   {
       $this->model_factory = $factory;
   }
   // ... snip ...
   
   public function action_foobar()
   {
      $user = $this->model_factory->build_object( 'User' );
      $mapper = $this->model_factory->build_mapper( 'User' );

      $user->set_id(42);
      $mapper->fetch($user);

      if ( $user->hasWarning()  )
      {
          $user->set_status( 'locked' );
      }

      $mapper->store( $user );
   }

   // ... snip ...
}

As you see, there is no indication how the data was stored. It does not even matter if user account was new, or already existing.

Some materials you might find useful

Videos

Books:

Community
  • 1
  • 1
tereško
  • 58,060
  • 25
  • 98
  • 150
  • Pretty good write-up, quick clarification, what do you mean by "Model in MVC is not a class, but an application layer which contains multitude of instances". You mean a multitude of classes (objects) which represent the schema? – Mike Purcell Mar 12 '12 at 22:50
  • @MikePurcell , structures within the model layer do not directly map onto database schema. You are thinking about ActiveRecord pattern which is kinda bad idea ( details: Advanced OO Patterns video ). – tereško Mar 12 '12 at 22:54
  • @MikePurcell , with "model is not a class" i mean that you should not have a class named "Model" in your codebase. Model is a layer which contains all of the domain business logic. I cannot explain it in 600 characters, you will have to read http://martinfowler.com/eaaDev/uiArchs.html and http://martinfowler.com/eaaCatalog/serviceLayer.html – tereško Mar 12 '12 at 23:00
  • No need to explain, was just looking to clarify. My exposure to 'models' has been by way of ORMs through frameworks, so I thought it odd that you stated it had nothing to do with the schema (where as doctrine and propel it absolutely does). Thanks. – Mike Purcell Mar 13 '12 at 01:57
  • @teresko When you have names in the get methods of the router class, am I right in thinking that these are just examples of what the methods would return? Otherwise it seems odd that you are setting a get method with a value as when the script is run, I would only receive a default controller with the action index. So if I wanted to call other controllers how would this occur? thanks for the extremely good post btw I voted you and gave you best answer – Matthew Underwood Mar 14 '12 at 19:07
  • @MatthewUnderwood , they were meant as "default" of "fallback" values. In a real world situation you would have different defaults for each `route`, but this was means just as an example to illustrate basic mechanics. – tereško Mar 14 '12 at 19:09
1

A great Dependency Injection container is "pimple", which may be considered a service locator by some. It uses php 5.3's closures to create a class, that is used to create all of your project's objects through lazy loading. So, for instance, you can create a closure that contains the code for initializing a given object. You would then use the DI container's get() method, which would in turn, call the closure to create the object. Or simply pass you the object, if it has already been created.

// simplified dic class
class dic {

    protected $closures = array();
    protected $classes = array();

    public function addResource($name, Closure $initialization_closure) {
        $this->closures[$name] = $initialization_closure;
    }

    public function get($name) {
        if (isset($this->classes[$name]) === false) {
           $this->classes[$name] = $this->closures[$name]();
        }
        return $this->classes[$name];
    }
}

//setup
$dic = new dic();
$dic->addResource('user', function() {
   return new UserClass($some_args);
});
$dic->addResource('userContainer', function() use ($dic) {
   return new UserContainerClass($dic->get('user'));
});

// usage
$userContainer = $dic->get('userContainer');

This allows you to keep the flexibility of being able to change how and what objects get created throughout your project by only changing a very small amount of code.

dqhendricks
  • 19,030
  • 11
  • 50
  • 83