SitePoint Sponsor

User Tag List

Results 1 to 9 of 9
  1. #1
    monitormensch oerdec's Avatar
    Join Date
    Sep 2004
    Location
    Hamburg
    Posts
    706
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Please check my MVC implementation

    Hi,

    please check my MVC implementation and tell me if it´s ok. I tried to keep it as simple as possible.

    For now there are just four classes:
    • User_Model is the model class. For the sake of simplicity all data is stored in an array inside this class. Normally it would be in a database instead. There´s just one method which returns this data.
    • User_Controller is the controller class. It has two methods. They are completely independend from each other. The first one gets its data from the model and passes it to a template. The second one is just passing arguments to another template. OK, the latter hasn‘t anything to do with the model – it´s just there for demonstration.
    • Template is the "template engine". It loads a template file and passes variables to it.
    • Application is the core of the whole thing. It puts everything together. (Loading the model and the controller, passing the model and a template object to the controller, running an action (method) and passing parameters and so on)

    I know some important things are missing, like a dispatcher/router for checking for file/class/method existence and some things are hard coded … but that´s ok for now. Just tell me if this approach makes sense or is just rubbish.

    OK, here it is:

    PHP Code:
    <?php
    error_reporting
    (E_ALL E_STRICT);
    header('Content-Type: text/plain; charset=utf-8');

    class 
    User_Model {

      private 
    $user = array('name' => 'oerdec''email' => 'oerdec@example.com');

      public function 
    getUser() {
        return 
    $this->user
      }
    }


    class 
    User_Controller extends Application {

      
    /**
      tpl_user.php:
      
      <p><?php echo $name; ?></p>
      */
      
    protected function displayUser() {
        
    $data $this->user->getUser();
        foreach (
    $data as $key => $value) {
          
    $modified_data[$key] = strtoupper($value);
        }

        return 
    $this->tpl->render('tpl_user.php'$modified_data);
      }
      
      
    /**
      tpl_params.php:
      
      <p><?php echo $first; ?></p>
      <p><?php echo $second; ?></p>
      */
      
    protected function displayParams($first$second) {
        
    $params = array('first' => $first'second' => $second);
        return 
    $this->tpl->render('tpl_params.php'$params);
      }
    }


    class 
    Template {
      
      public function 
    render($tpl_file$data) {
        
    extract($data);
        
    ob_start();
        include(
    $tpl_file);
        return 
    ob_get_clean();
      }
    }


    class 
    Application {

      private   
    $model;
      private   
    $controller;
      protected 
    $tpl;

      public function 
    loadModel($model) {
        
    $this->model = new $model;
      }
      
      public function 
    loadController($controller) {
        
    $this->controller = new $controller;
        
    $this->controller->user $this->model;
        
    $this->controller->tpl = new Template;
      }
      
      public function 
    runAction() {
        
    $params     func_get_args();  
        
    $action     $params[0];   
        
    $num_params sizeof($params);
        
        if (
    $num_params >= 2) {
          unset(
    $params[0]);
          return 
    call_user_func_array(array($this->controller$action), $params);
        }
        else {
          return 
    $this->controller->$action();
        }
      }
    }


    // Test
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    $application = new Application;
    $application->loadModel('User_Model');
    $application->loadController('User_Controller');

    echo 
    "User_Controller::displayUser():\n\n";
    print_r($application->runAction('displayUser'));

    echo 
    "\n";

    echo 
    "User_Controller::displayParams():\n\n";
    print_r($application->runAction('displayParams''one''two'));
    ?>

    Thanks a lot,

    oerdec
    Last edited by oerdec; May 13, 2007 at 22:24.

  2. #2
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    It appears that you're showing a sample implementation of a generic mechanism. Yet, in Application, which I would assume to be a generic baseclass, you make assumptions about the descendant:
    PHP Code:
    $this->controller->user $this->model
    This assumes, that $this->model is an instance of User_Model, thus breaking your layer of abstraction.

    There are a number of stylistic issues, which I won't really comment on. Speaking about model in singular, is a mistake though. The whole idea of MVC, is to separate the layers, thus forming a *..* relationship between each. This means that a controller can use one model component, multiple model components, or none at all. Controllers can also share model components (and often will). Having a User_Controller and a User_Model may happen, but the relationship between them isn't stronger than between any other controllers, which might happen to use the User_Model.

    Quote Originally Posted by oerdec View Post
    PHP Code:
    header('Content-Type: text/plain; charset=utf-8'); 
    You are doing right in returning output data to output it at the top level. However, echo and print are not the only ways to output. You should provide a way to encapsulate output through header, session- and cookies-related functions.

  3. #3
    monitormensch oerdec's Avatar
    Join Date
    Sep 2004
    Location
    Hamburg
    Posts
    706
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thank you for your input, kyberfabrikken.

    I made some changes:

    1. The class names of the models are now plural and without '_Model'.
    2. The model classes extend the class Base_Model, which doesnīt do anything yet. But itīs there for a reason.
    3. Thereīs a new class Registry. I donīt know if "Registry" is the proper term. It basically loads all model classes. It also could be used to provide other things like language or config data.
    4. When a controller is loaded all models are available in it. That means Iīm now able to use one, more or no models in a controller.


    BTW: The header() function at the beginning is there just during development. I think itīs better to decide inside a contoller how the output should look like. I still have to find a good way to do this.

    PHP Code:
    class Base_Model {
      
    // do something for all models, e.g. database stuff 
    }

    class 
    Users extends Base_Model {

      private 
    $user = array('name' => 'oerdec''email' => 'oerdec@example.com');

      public function 
    getUser() {
        return 
    $this->user
      }
    }


    class 
    Admins extends Base_Model {
      
      private 
    $admin = array('name' => 'monitormensch''email' => 'monitormensch@example.com');

      public function 
    getAdmin() {
        return 
    $this->admin
      }
    }


    class 
    User_Controller extends Application {

      protected function 
    displayUser() {
        
    $data $this->users->getUser();
        foreach (
    $data as $key => $value) {
          
    $modified_data[$key] = strtoupper($value);
        }

        return 
    $this->tpl->render('tpl_user.php'$modified_data);
      }

      protected function 
    displayParams($first$second) {
        
    $params = array('first' => $first'second' => $second);
        return 
    $this->tpl->render('tpl_params.php'$params);
      }
      
      protected function 
    registryTest() {
        return 
    $this->admins->getAdmin();
      }
    }


    class 
    Registry {
      
      private 
    $registered_models = array();
      
      private function 
    getDeclaredModels() {
        
    $classes get_declared_classes();
        foreach (
    $classes as $class) {
          if (
    is_subclass_of($class'Base_Model')) {
            
    $models[] = $class;
          }
        }
        return isset(
    $models) ? $models FALSE;
      }
      
      public function 
    registerModels() {
        
    $models $this->getDeclaredModels();
        if (
    $models !== FALSE) {
          foreach (
    $models as $model) {
            
    $this->registered_models[] = new $model;
          }
        }
      }
      
      public function 
    getRegisteredModels() {
        return !empty(
    $this->registered_models) ? $this->registered_models FALSE;
      }
    }


    class 
    Template {
      
      public function 
    render($tpl_file$data) {
        
    extract($data);
        
    ob_start();
        include(
    $tpl_file);
        return 
    ob_get_clean();
      }
    }


    class 
    Application {

      private   
    $controller;
      private   
    $models FALSE;
      protected 
    $tpl;
      
      public function 
    loadModels($models) {
        
    $this->models $models;
      }
      
      public function 
    loadController($controller) {
        
    $this->controller = new $controller;
        
    $this->controller->tpl = new Template;
        
        if (
    $this->models !== FALSE) {
          foreach (
    $this->models as $model) {
            
    $class_name strtolower(get_class($model));
            
    $this->controller->$class_name $model;
          }
        }
      }
      
      public function 
    runAction() {
        
    $params     func_get_args();  
        
    $action     $params[0];   
        
    $num_params sizeof($params);
        
        if (
    $num_params >= 2) {
          unset(
    $params[0]);
          return 
    call_user_func_array(array($this->controller$action), $params);
        }
        else {
          return 
    $this->controller->$action();
        }
      }
    }


    // Test
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
    $registry = new Registry;
    $registry->registerModels();
    $registered_models $registry->getRegisteredModels();

    $application = new Application();
    $application->loadModels($registered_models);
    $application->loadController('User_Controller');

    echo 
    "User_Controller::displayUser():\n\n";
    print_r($application->runAction('displayUser'));

    echo 
    "User_Controller::displayParams():\n\n";
    print_r($application->runAction('displayParams''one''two'));

    echo 
    "User_Controller::registryTest():\n\n";
    print_r($application->runAction('registryTest')); 

  4. #4
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by oerdec View Post
    The model classes extend the class Base_Model, which doesnīt do anything yet. But itīs there for a reason.
    I would advise against that. Model level components are often very diverse, so a single baseclass will either get so abstract, that it adds no real value, or get too restrictive. Controllers and views on the other hand, have a tendency to have simple interfaces, which makes them good candidates for using a baseclass.

    You should try to think a little broader with model components, than just as active record type entities. Any component, which represents business logic, is a model component. For example, an API to communicate with an external service, such as a credit card gateway, is a model component.

    Quote Originally Posted by oerdec View Post
    I donīt know if "Registry" is the proper term.
    'Registry' is a pretty loaded term; What you describes looks like it fits the bill.

    There is a thing, which I didn't see the first time. Your User_Controller extends from Application, but at the same time, the Application creates an instance of User_Controller. It seems like Application has too many tasks -- Maybe you can split it into an abstract base class, from which Application and User_Controller could inherit, instead of User_Controller extending from Application.

  5. #5
    monitormensch oerdec's Avatar
    Join Date
    Sep 2004
    Location
    Hamburg
    Posts
    706
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by kyberfabrikken View Post
    You should try to think a little broader with model components, than just as active record type entities. Any component, which represents business logic, is a model component. For example, an API to communicate with an external service, such as a credit card gateway, is a model component.
    The only reason - at least for now - why the Base_Model class exists is to register all model classes. Registry::getDeclaredModels() checks if a declared class is a subclass of Base_Model. If yes, it loads it. I could put all models in a directory instead, then scan it and load all found classes. I thought all classes could use the same stuff from a base class... like a db connection. But as you said, maybe that&#180;s too restricted.

    Quote Originally Posted by kyberfabrikken View Post
    It seems like Application has too many tasks -- Maybe you can split it into an abstract base class, from which Application and User_Controller could inherit, instead of User_Controller extending from Application.
    Please could you show me how you&#180;d do this? I don&#180;t see the advantages of using an abstract class for this.

    oerdec

  6. #6
    monitormensch oerdec's Avatar
    Join Date
    Sep 2004
    Location
    Hamburg
    Posts
    706
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I should better say I don&#180;t know how to realise this with an abstract class

    Edit: OK, I tried it again. Is this what you mean?

    PHP Code:
    // Models
    class Users {
      public 
    $data = array('name' => 'oerdec');
    }
    class 
    Admins {
      public 
    $data = array('name' => 'mufflon');
    }


    // The abstract Class
    abstract class AbstractClass {  
      public function 
    __construct() {
        
    $this->users  = new Users;
        
    $this->admins = new Admins;
      }
    }


    // Application
    class Application extends AbstractClass {

      public function 
    loadController($controller) {
        
    $this->controller = new $controller;
      }
      
      public function 
    runAction($action) {
        return 
    $this->controller->$action();
      }
    }


    // a Controller
    class People_Controller extends AbstractClass {
      public function 
    getUser() {
        return 
    $this->users->data['name'];
      }
      
      public function 
    getAdmin() {
        return 
    $this->admins->data['name'];
      }
    }


    // Test
    $app = new Application;
    $app->loadController('People_Controller');

    print_r($app->runAction('getUser'));
    print_r($app->runAction('getAdmin')); 
    When I print_r($app) I get this:

    Code:
    Application Object
    (
        [users] => Users Object
            (
                [data] => Array
                    (
                        [name] => oerdec
                    )
    
            )
    
        [admins] => Admins Object
            (
                [data] => Array
                    (
                        [name] => mufflon
                    )
    
            )
    
        [controller] => People_Controller Object
            (
                [users] => Users Object
                    (
                        [data] => Array
                            (
                                [name] => oerdec
                            )
    
                    )
    
                [admins] => Admins Object
                    (
                        [data] => Array
                            (
                                [name] => mufflon
                            )
    
                    )
    
            )
    
    )
    Sorry, I don&#180;t want to bother you or anyone else with this. But I really want to get this MVC stuff in my brain

    oerdec
    Last edited by oerdec; May 18, 2007 at 00:41.

  7. #7
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by oerdec View Post
    Registry::getDeclaredModels() checks if a declared class is a subclass of Base_Model. If yes, it loads it.
    Why? You could simply let the controller request what it needs, from the registry -- model classes or not model classes all the like.

    Quote Originally Posted by oerdec View Post
    Please could you show me how you&#180;d do this? I don&#180;t see the advantages of using an abstract class for this.
    I think this would fit your current design:
    PHP Code:
    <?php
    class Registry
    {
      protected 
    $instances = Array();
      function 
    get($classname) {
        
    $classname strtolower($classname);
        if (!isset(
    $this->instances[$classname])) {
          
    $this->instances[$classname] = new $classname();
        }
        return 
    $this->instances[$classname];
      }
    }

    class 
    Application
    {
      protected 
    $map = Array(
        
    'people' => 'People_Controller',
      );
      protected 
    $registry;
      
      function 
    __construct($registry) {
        
    $this->registry $registry;
      }

      public function 
    dispatch($controller$action) {
        
    $classname $this->map[$controller];
        
    $controller = new $classname($this->registry);
        return 
    $controller->$action();
      }
    }

    class 
    People_Controller
    {
      function 
    __construct($registry) {
        
    $this->users $registry->getUsers();
        
    $this->tpl = new Template();
      }

      public function 
    getUser() {
        
    $data $this->users->getUser();
        foreach (
    $data as $key => $value) {
          
    $modified_data[$key] = strtoupper($value);
        }
        return 
    $this->tpl->render('tpl_user.php'$modified_data);
      }
    }

    $app = new Application(new Registry());
    echo 
    $app->dispatch('people''getUser');
    You could chose to merge Registry and Application into one entity; Then let Application pass itself ($this) to the controller it creates, rather than $this->registry. If you don't merge them, I would probably rename Application to Dispatcher.

    The registry here is rather crude -- in reality, classes have dependencies, so you need some sort of factory for creating them (Users might need an instance of DB for example). Let that rest until you run into the problem.

    Quote Originally Posted by oerdec View Post
    Sorry, I don&#180;t want to bother you or anyone else with this. But I really want to get this MVC stuff in my brain
    That's the point of these forums anyway. I could just chose not to read it.

  8. #8
    monitormensch oerdec's Avatar
    Join Date
    Sep 2004
    Location
    Hamburg
    Posts
    706
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thanks you very much! I got it to work after some slight modifications. At the first glance I didnīt like some things however:

    - the need of a constructor in controller
    - manually "loading" the model(s) and the template engine.

    But it makes completely sence. I can choose which model(s) I want to use and also which template engine... or nothing at all.

    OK, I think I have a good base to continue.

  9. #9
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by oerdec View Post
    - the need of a constructor in controller
    - manually "loading" the model(s) and the template engine.
    If you want, you could simply store the registry in the controller. That way, you don't have to write the explicit constructor-code, for each controller:
    PHP Code:
    class Controller
    {
      protected 
    $registry;
      function 
    __construct($registry) {
        
    $this->registry $registry;
      }
    }

    class 
    People_Controller extends Controller
    {
      public function 
    getUser() {
        
    $data $this->registry->get('users')->getUser();
        foreach (
    $data as $key => $value) {
          
    $modified_data[$key] = strtoupper($value);
        }
        return 
    $this->registry->get('tpl')->render('tpl_user.php'$modified_data);
      }

    Give it a try without though. Writing explicit constructors is often not as bad in reality, as it may appear when thinking abstractly about it. It has the benefits of low coupling (You don't need the registry to use the code) and it's very easy to follow the code.


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •