Page 1 of 1

I think I finally got this MVC thing

Posted: Fri Aug 21, 2009 6:51 am
by papa
I've been reading this book about MVC with PHP and at first I really didn't understood the concept. I had a few ideas how to implement it but didn't get the entire picture.

On my recent project I did my best to try and implement it, the project is an army builder for a game called Warhammer.:

So I have a class for my mySQL connection which I use as an argument for my model class.

So the example is my way of dispaying a users army, any input and comments are highly appreciated:

UserArmy.php

Code: Select all

 
<?php
 
class UserArmy {
    private $army_table, $units_table, $unit_type_table, $unit_options_table, $unit_stats_table, $options_table, $options_type_table, $user_army_table, $user_units_table, $user_unit_options_table;
    
    function __construct($db, $user_id) {
        $this->db = $db;
        $this->user_id = $user_id;
        $this->army_table = "a_army";
        $this->units_table = "a_units";
        $this->unit_type_table = "a_unit_type";
        $this->unit_options_table = "a_unit_options";
        $this->unit_stats_table = "a_unit_stats";
        $this->options_table = "a_options";
        $this->options_type_table = "a_options_type";   
        $this->user_army_table = "a_user_army";
        $this->user_units_table = "a_user_units";
        $this->user_unit_options_table = "a_user_unit_options";
    }
    
    //List User Armies
    function UserArmies() {
        if($link = $this->db->dbConnect()) {
        $sql = "SELECT a.id, a.army_type, a.army_name, a.points, b.name
            FROM {$this->user_army_table} AS a
            JOIN {$this->army_table} AS b ON a.army_type = b.id
            ORDER BY a.army_type, a.points ASC";
    
            if($result = mysql_query($sql)) {
                while ($row = mysql_fetch_assoc($result)) {
                    $army[] = $row;
                }
            } else return mysql_error();
    }
    //return $army[] => id, name etc
    return $army;
    }
etc
The Controller decides what to do with the array provided. It extends the UserArmy class:

Code: Select all

 
<?php
 
class UserArmyController extends UserArmy {
    
    function __construct($db, $user_id) {
        parent::__construct($db, $user_id);
    }
    
    function UserArmy($army_id) {
        foreach(parent::UserArmy($army_id) as $a_id => $value) {
            if(is_array($value)) {
                foreach($value as $u_id => $unit) {
                $e_cost = "";
                //Unit Equip
                $value[$u_id]['equip'] = parent::UserUnitOptions($army_id, $value[$u_id]['id'], $value[$u_id]['unit_id']);
                //Calculate Equipment cost
                if(is_array($value[$u_id]['equip'])) {
                    foreach($value[$u_id]['equip'] as $key => $option) {
                        switch($option['option_type']) {
                            case 3:
                            case 4:
                            case 11:
                            case 13:
                                $e_cost += $option['cost'];
                                break;
                            default:
                                $e_cost += $option['cost'] * $unit['unit_amount'];
                        }
                    }
                } 
                //Unit Cost
                $value[$u_id]['unit_cost'] = $unit['cost'] * $unit['unit_amount'] + $e_cost;
                $total_cost += $value[$u_id]['unit_cost']; 
                }
            }
            $ua[$a_id] = $value;
            $ua['army_cost'] = $total_cost;
        }
        return $ua;
    }
}
?>
And then we have the view which adds html and displays the array in a nice way:

Code: Select all

 
<?php
 
class UserArmyView {
 
    function __construct(UserArmyController $army) {
        $this->Controller = $army;
    }
    
    function UserArmiesView() {
        echo "<h4>Army Roster</h4>\n";
        echo "<div style=\"margin-left: 1em\">\n";
        echo "\t<ul>\n";
        foreach($this->Controller->UserArmies() as $key => $value) {
            echo "\t\t<li><a href=\"{$_SERVER['SCRIPT_NAME']}?a=my_army&s=view_armies&id={$value['id']}\">{$value['name']}</a>";
            if(!empty($value['army_name'])) echo " - <i>{$value['army_name']}</i>";
            echo " ({$value['points']})pts</li>\n";
        }
        echo "\t</ul\n";
        echo "</div>\n";
    }
    
    function UserArmyView($id) {
        //Fetch Army Information
        $ua = $this->Controller->UserArmy($id);
        echo "<hr>\n";
        echo "<h4>{$ua['army_type']}</h4>\n";
        echo "<p><b>Name:</b> {$ua['army_name']}<br />\n";
        echo "<b>Points:</b> {$ua['army_cost']}/{$ua['points']}pts<br />\n";
        echo "<b>Created:</b> {$ua['date']}</p><br />\n";
        //Display Units
        if(is_array($ua['units'])) {
            echo "<h5>Units</h5>\n";
            foreach($ua['units'] as $unit) {
                $e = "";
                echo "<p><b>{$unit['name']}</b><br />\n";
                //Display Equipment
                if(is_array($unit['equip'])) {
                    echo "<b>Equipment:</b> ";
                    foreach($unit['equip'] as $equip) {
                        $e .= $equip['name'].", "; 
                    }
                    echo rtrim($e, ", ") ."<br />\n";
                    
                }
                echo "<b>Unit Cost:</b> {$unit['unit_cost']}pts</p><br />\n";
            }
        }
    }
}
?>
Lastly I have a frontController which is just a php page with a switch statement, headers, footers etc:

Code: Select all

 
...
<?php
 
switch($_GET['s']) {
    //View User Armies
    case "view_armies":
        include_once("php/Classes/UserArmy.php");
        include_once "php/Classes/UserArmyController.php";
        include_once "php/Classes/UserArmyView.php";
        $ua = new UserArmyView(new UserArmyController($db, $_SESSION['user_id']));
        $ua->UserArmiesView();
        if(!empty($_GET['id'])) {
            $ua->UserArmyView($_GET['id']);
        }
        break;
...
I bet there is a lot to work on to make this much neater, but at least I'm getting a hang of it.

Input? Be honest, I can take it... :)

Re: I think I finally got this MVC thing

Posted: Fri Aug 21, 2009 7:04 am
by Eran
You somewhat missed the point of MVC, and that is the separation of the three layers (Model, View and Controller). Your "controller" extends the UserArmy model, which would basically make it - another model. A more appropriate use is for the controller to instance the model, interface with it using its API (public methods) and pass the results to the View.

Think of the controller as a negotiator between the business logic and its presentation. Controller actions usually represent specific pages in your application (or public resources such as REST-style URIs), and for each specific page the controller decides which model(s) to instance and which template to render (for the most part).

Regarding your View object - most commonly, View objects do not store HTML internally. Instead, they provide an interface for rendering any number of templates. I personally am also against having static HTML as PHP strings. HTML should be in plain text, which is more maintainable and readable, has better IDE support and just more correct (at least for me).

You should probably have a look at how some of the major frameworks handle implementation of those concepts. I think you would learn a lot from it.

Hope this helps

Re: I think I finally got this MVC thing

Posted: Fri Aug 21, 2009 8:09 am
by papa
Thank you very much Pytrin, I separated my Model from the Controller.

Regarding the View, I will take a closer look at Zend etc, but if you feel like it, an example would be very helpful.

So the view includes static html templates for example or how would you go about that?

Re: I think I finally got this MVC thing

Posted: Fri Aug 21, 2009 8:39 am
by matthijs
What Pytrin probably means is plain php/HTML templates like this:

Code: Select all

<!DOCTYPE html>
<head>
<title><?php echo $title; ?>
</head>
<body>
 
// more html
 
<?php foreach($content as $article) : ?>
<div class="entry">
<h2><?php echo $tarticle_title; ?></h2>
<?php echo $article_content; ?>
</div>
<?php endforeach; ?>
 
//... more html
 
</body>
</html>
 
The variables are set by the controller. Another possibility is that the view pulls in it's own data directly from the model, without the controller

Re: I think I finally got this MVC thing

Posted: Fri Aug 21, 2009 8:58 am
by Paul Arnold
Look at how CodeIgniter does it.
It's very easy to understand once you read the manual and gives you a good clear understanding of MVC.

Re: I think I finally got this MVC thing

Posted: Fri Aug 21, 2009 9:01 am
by Eran
Yes, something similar to what matthijs showed. There are two common methods for loading templates - including the file directly into the scope of the view object, and using string replacement functions to replace template syntax with dynamic data. I wrote about the former a while ago in my blog, which you might find useful - http://www.techfounder.net/2008/11/18/o ... emplating/

Regarding the controller, consider the following example:

Code: Select all

class ArmyController {
    public function unitsAction() {
        $view = new View(); //The view
        $army = new UserArmy(); //The model
        $view -> units =  $army -> getUnits(); //Pushing data from the model into the view
        echo $view -> render( '/army/units.phtml' ); //Telling the view to render a specific template
    }
}
This is a very simple case, most frameworks have conventions to resolve Urls into controller / action (ie, pretty urls) and map actions to an appropriate view template without writing it explicitly.

That template would look something like: (I based it on your previous example)

Code: Select all

<h4>Army Roster</h4>
<div style="margin-left: 1em">
   <ul>
   <?php foreach($this -> units as $unit) : ?>
        <li><a href="<?php echo $this -> baseUrl; ?>?a=my_army&s=view_armies&id=<?php echo $unit['id']; ?>"><?php echo $unit['name']; ?></a>
        <?php if( ! empty($unit['army_name'])) { echo " - <i>" . $unit['army_name'] . "</i>"; } ?>
        <?php echo $unit['points']; ?> pts
        </li>
   <?php endforeach; ?>
   </ul>
</div>
Also, I definitely recommend Zend Framework, Symphony or CakePHP over code-igniter. They might be somewhat harder to get in at first, but the level of source code is very high.

Re: I think I finally got this MVC thing

Posted: Mon Aug 24, 2009 2:17 am
by papa
Thanks a lot guys, this was most helpful!

I'll might get back with a few samples. :)

I realize now that I didn't use the concept correctly. :)


So what I can see from you examples is that the model also does some of the logic. As it is now, my model only retrieves data and passes an array with information, and the controller implements the logic to my data. But maybe the model should do more than that.

Re: I think I finally got this MVC thing

Posted: Fri Aug 28, 2009 9:02 pm
by josh
Yes, fat model skinny controller. Models are supposed to re-usable. I should be able to throw out your "web" controller and easily write a "command line" controller, or "voice recognition automated phone controller" for people to dial in and speak commands. The idea is your model should be a "model" of whatever problem your software solves. You model a real world system. So a program to calculate aerodynamics for a car might have a car model, the car model might use other models like an engine model, etc... to help organize all the logic, the controller would "take this car ", start it's engine, press its gas pedal, it would then report back to the user in some way via a view ( video game display showing how the car reacted, spreadsheet print out of data points on it's aerodynamics, generating a graph, etc.. whatever your application does )

So your controller would just say

$car = new Car();
$car->pressPedal( $secondsToPress );
$this->view->result = $car->getFinalSpeed();

Then you could easily write a different controller that uses the same car but does different things with it, or you could have multiple views ( tables of data, different types of graphs ). One of the key things here to realize is none of this is hard coded, the user of your application could actually select which view to use ( think web view vs print view )

Re: I think I finally got this MVC thing

Posted: Mon Aug 31, 2009 2:27 am
by papa
Very nice, thank you.

I've been doing some Zend tutorials the past week, and been doing some reading so hopefully my next code example will look a little better. :)

I'm thinking of doing something from scratch, but maybe use some of the Zend structure, which I liked. And include the PDO library as well.

All your inputs have been most helpful so far!

Re: I think I finally got this MVC thing

Posted: Wed Sep 02, 2009 9:42 am
by papa
Hi again,

So I am currently doing some tests before re-coding my application. I borrowed the structure from Zend and used Pytrins View class as a start.

Image

Model (application/models/Index.php)

Code: Select all

 
class Index_Model
{
        function __construct()
        {
            // Some info
        }
 
        function getData() 
        {
            $users[0] = array('id'=>1, 'name'=>'Papa', 'email'=>'papa@mail.com');
            
            return $users;
        }
}
Controller (application/controllers/IndexController.php)

Code: Select all

 
class IndexController 
{
    function __construct() 
    {
        $view = new View();
        $index = new Index_Model();
        $view->users = $index->getData(); //Store data 
        echo $view->render('../application/views/scripts/index/index.phtml'); //Render Template
    }
}
 
View template (application/views/scripts/index.phtml)

Code: Select all

 
<?php echo $this->render('../application/views/scripts/header.phtml'); ?>
 
<?php foreach($this->users as $user) : ?>
ID: <?php echo $user['id']; ?> <br />
NAME: <?php echo $user['name']; ?> <br />
EMAIL: <?php echo $user['email']; ?> <br />
<?php endforeach; ?>
 
<?php echo $this->render('../application/views/scripts/footer.phtml'); ?>
 
Pytrin's View class Very nice blog post

Code: Select all

 
class View {
 
    public function render($script) 
    {
        ob_start();
      
        $this->_include($script);
 
        return ob_get_clean();
    }
 
    public function __get($key) 
    {
        return (isset($this -> $key) ? $this -> $key : null);
    }
 
    protected function _include() 
    {
        include func_get_arg(0);
    }
 
}
Bootstrap (public/index.php)

Code: Select all

 
// Error Reporting
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);
ini_set('display_errors', 'on');
 
// Modify include path to include path to libary
//ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR . '../library/');
 
// Includes
 
//Models
require_once "../application/models/Index.php";
//Views
require_once "../library/View.php";
//Controllers
require_once "../application/controllers/IndexController.php";
 
new IndexController();
 
I feel that this can be something useful and a good way to learn and understand Zend and patterns better.

I do have a question regarding my bootstrap. Is there a neat way to maybe autoload my classes similar to Zend or how would you go about this?

I realize there is still a lot to implement, like displaying subpages, pretty urls etc. But one thing at a time. :)

My library will include the db connection class etc further on.

Am I on the right track or should I stick to playing the guitar?

Re: I think I finally got this MVC thing

Posted: Wed Sep 02, 2009 10:41 am
by Paul Arnold
Just looking at the code briefly, that seems far far better.
You're definitely grasping the concept now.

Re: I think I finally got this MVC thing

Posted: Wed Sep 02, 2009 11:38 am
by Eran
It looks nice, though the control process could stand improvement. Your index controller implements an action in the constructor - controllers are traditionally meant to hold several actions (ie, group actions), and you can only have one constructor.
For example, you can an Index controller that looks similar to the following:

Code: Select all

<?php
class IndexController {
    public function indexAction() {
        //index action logic, usually the default action
    }
 
    public function loginAction() {
        //login action logic
    }
}
 
$controller = new IndexController();
$controller -> login();
I think your next step should be familiarizing yourself with the front controller pattern. The front controller is the an entry point to your application, and is responsible for processing the request, finding the appropriate controller and invoking the relevant action. A very important pattern for MVC stacks.

Regarding autoload, you can definitely use it to simplify file loading if you keep consistent directory/file naming conventions (I really like PEAR/Zend's conventions).

Re: I think I finally got this MVC thing

Posted: Sat Sep 05, 2009 5:27 am
by josh
Writing your own stuff to learn is good and all but keep in mind how easy it is to get emotionally attached to a code base, and make sure you know what you're getting into

Not sure if this is where you are going with your experiments, but you should definitely learn an existing framework before you delve into coding your own. I found, personally, that before I knew how these frameworks worked I wanted to write my own really bad, after I had about a year of experience with MVC applications I no longer wished to code my own 8) Looking back if I had continued [ not using existing libraries ] I would not be where I am at in my career today

Looks like you have the basics down but just because you understand how a wheel and differential gear works doesnt mean you should try to build a Ferrari just yet. There's 1,000s of considerations you're probably overlooking, for instance how to factor repetitive view logic ( view helpers ), how to best factor cross cutting concerns in controllers ( action helpers ) etc... ( this is zend parlance, which is modeled after J2ee which would be another good thing to read up on before writing your own ). How to centralize URLs so that you can change your SEO urls from one place without updating 100000 hard coded <a> tags, etc.., etc...

The existing frameworks are designed to be barebone parts, kind of like k'nex or legos, that you can piece together. They are designed to be easy to extend and override. You should try to customize an existing low stack framework rather then starting 100% from scratch

another metaphor - if building a computer you dont solder your own ram, and build your own hard drives, you buy the parts and build to spec, before I would recommend someone to start trying to build a motherboard from scratch I'd recommend they build a few computers first and see if thats not enough work already.

Not sure if this is the direction you're going in but it can be easy to get too used to maintaining your homebrew framework, and forget that millions of man hours have gone into literally 1000s of existing frameworks. But... if you're going to do it anyways I would agree with the post above me about FC, and I would also read over my post carefully so you know what kinds of components the framework will ultimately need to be useful to you on a real project

If you are interested in learning more I would recommend POEAA by Martin Fowler, as well as some of his other books particularly refactoring. I would also recommend Xunit Test patterns but thats a slightly different topic ( and a really thick book I might add )

Re: I think I finally got this MVC thing

Posted: Mon Sep 07, 2009 2:23 am
by papa
I am acctually currently reading Fowler's book. Very interesting so far.

I guess I got a little impatient when trying Zend, but I felt that there was a lot of new information to take in at once, and there for I felt that writing an own simpler framework might be a good start but we'll see how this experiment is going.

As I've already programmed most of my application it would take me a while to re-factor it to the Zend framerwork as it's using it's own form classes etc. But using my own small framework would mean I can pretty easily make it work with the existing code.

I do understand your analogies and you're making a good point. So we'll see how this ends.. :)