Front controller advice

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

Front controller advice

Post by matthijs »

For a personal project I'm playing a bit with page and frontcontrollers. To keep things as simple as possible I first thought I'd go with some basic pages, each which would be a page controller. That's not too difficult, as they would basicly consist of some if() else () loops. (in fact just procedural, with some classes included for common functionality like validation and db access).

I was looking into MVC patterns but it soon became clear a full-blown OOP system with datamappers, filters etc is still a bit over my head :)

However, some time ago Arborint gave an example of a front controller. The way that code can be used makes it really easy. In fact, if I take this FrontController class, the only thing I would have to change ("refactor") from my procedural-coded normal pages is that instead of those pages I would have "actions".

Code: Select all

<?php
class FrontController{
var $action_dir;
var $default_action;
var $error_action;
var $action_param;
var $action = '';

function FrontController($action_dir='action', $default_action='home', $error_action='error', $action_param='action'){
    $this->action_dir = $action_dir;
    $this->default_action = $default_action;
    $this->error_action = $error_action;
    $this->action_param = $action_param;
}

function & commandFactory($action){
    $obj = null;
    $filename = $this->action_dir . $action . '.php';
    if (file_exists($filename)) {
        include($filename);
        if (class_exists($action)) {
              $obj =& new $action();
        }
    }
    return $obj;
}

function execute(){
    if (isset($_GET[$this->action_param])) {
         $this->action = preg_replace('/[^a-zZ-Z0-9\_\-]/', '', $_GET[$this->action_param]);
    } else {
         $this->action = $this->default_action;
    }
    $obj = & $this->commandFactory($this->action);
    if (! $obj) {
        $obj = & $this->commandFactory($this->error_action);
    }
    $obj->execute();
}

}
?>
index.php

Code: Select all

<?php
// configuration with basedir and database credentials
include 'config.inc.php';
include BASE_DIR . 'FrontController.php';

$fc =new FrontController('./', 'home', 'error');
$fc->execute();
?>
home.php

Code: Select all

<?php
class home{
function execute(){
    echo 'Home';        
}
}
?>
page.php

Code: Select all

<?php
class page{
function execute(){
    echo 'Page';        
}
}
?>
Works like a charm. When I go to index.php I get the home page, if I go to index.php?action=page I get the page result.
I started adding other actions and came up with this:

Code: Select all

<?php
/**
*     Action Contacts 
*     show a list of contacts
*
*/

include BASE_DIR . 'adodb/adodb.inc.php';

// this is the model class for contacts
// normally this would be a seperate file and included
class contactsmodel extends ADOConnection {
  
  var $db;
  var $result;

  function contactsmodel(&$db) {
      $this->db = &$db;
      $this->listcontacts();
  }
  function listcontacts() {
      $this->result = &$this->db->Execute("select * from contacts");
  }
  function fetch() {
      return $this->result->FetchRow();
  }	 
}

// this is the controller
class contacts {
 
  function execute(){

      $DB =& NewADOConnection('mysql');
      $DB->debug = true;
      $DB->Connect(dbHost, dbUser, dbPassword, dbName); 
      $view = &new contactsmodel($DB);

      // for now just echo out the results. normally this would go to a view
      while ($row = $view->fetch()) {
          echo '<pre>';
		      print_r($row);
          echo '</pre>';
      }
  }
 
}
?>
This code seems to work, as I can succesfully show a list of the data from the table contacts. Of course it's very basic, but I would like to start easy.

Now I have a few questions.
1) Is this the right direction I have taken so far? If not, what mistakes did I make? What would be the next (small) steps?

2) I set the error reporting to strict and receive the following errors/notices
Strict Standards: Declaration of ADORecordSet_array::FetchField() should be compatible with that of ADORecordSet::FetchField() in C:\apachefriends\xampp\htdocs\project\lib\adodb\adodb.inc.php on line 3526

Strict Standards: Non-static method ADOConnection::outp() should not be called statically in C:\apachefriends\xampp\htdocs\project\lib\adodb\adodb-lib.inc.php on line 943

Has this something to do with the fact I run this code on php5?
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

I can answer the strict standards question: Yes. If you want your code to evaluate STRICT, you'll probably have to break PHP4 compatibility. I'm fairly certain AdoDB works in PHP4. (I'm surprised, however, that it didn't spit out notices for the var declarations)

The rest may take a little bit.
matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

Post by matthijs »

Thanks for your reply. Good to know it's indeed the php version. For the moment I will not change the code, as I think it should be possible to stay compatible with 4. Maybe later on I can change it to php5 only code, but as I'm first just trying to get the basics right, that'll not be necessary now.
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

Since it's a personal project, I'd say scrap PHP4 and go all-out OOP. I've always wanted to play around with Exceptions, Interfaces/Abstract and PPP (public/protected/private), but none of my personal servers have PHP5. If you still want PHP4, test with E_ALL.
matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

Post by matthijs »

Yeah I could do that. If I would like to put it on a live server I'd have to find a host with php5 but that shouldn't be a problem. And besides that, it'll take a while (as you can see by the code :) ) so by the time I finish this project php5 will be common (hopefully?)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

I really like how you have utterly simplified the thing. I have been rewriting my Skeleton code to be compatable with the Zend Framework. Their controllers are suffering from a little bout of Extreme Complexity. My goal was to make it so I could mix and match stuff from the ZF with Skeleton stuff that was actually Extremely Simple like your Front Controller above. Perhaps we could pool resources? We could put it under CVS on SourceForge where we could both work on it.
(#10850)
matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

Post by matthijs »

Uhm, Arborint, are you joking with me? The FrontController class is the one you gave yourself here. It's only the Action Contacts I added myself.

So, I would be happy to pool resources with you, but I'm afraid I'll have to do some catching up first :)

But you're right, the way this FrontController works is indeed Extreme Simple.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

No, not joking. Of course I recognized the code. But you have kept after the Front Controller idea. Have you looked at the Zend Framework?
(#10850)
matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

Post by matthijs »

No, not joking
ah, ok :)

So the code I wrote is kind of in the right direction? I'm not asking someone to rewrite it, just some pointers to what should be elsewere, what could be different or what to look into. Then I'll work out the details myself.

I am looking at the Zend Framework, yes. Looks very interesting. I've installed a seperate testserver with the zend framework and I'm going to play with that as well.

But as I'm only just starting with this I realised I have to take it one (small) step at a time. Also, I would like to write at least some or most code myself, to aid in the learning experience. As you have advised to me in the past, in learning OOP it's important to code, get into trouble and then realise how to solve that. I have read many many threads on the advanced forums here and on sitepoint about design patterns, MVC, front controllers etc. I can understand almost everything in these discussions. I can see how these patterns solve certain problems. But then to put something together yourself is something different. It's like the difference between reading a foreign language and speaking it. So that's why I decided to just start coding and see where it goes.
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

The FC (and RequestMapper classes) in use on one of my projects is...um...aborint sourced...;) It's still one of the simplest, easily adaptable versions I've seen to date.

I agree with the DIY experience. I wrote a simple framework (more collection) of classes back a few months ago for an open source webgame and have been tinkering with it ever since on and off. I found it an invaluable learning experience... It won't be competing with Zend any day soon however...:) I did it mainly because webgames have a heavy lean towards performance, and I wanted an OOP framework that could provide that - even if it meant compromising here and there.
matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

Post by matthijs »

So I'm not the only one who found the code useful/insightful? :) Simple and easily adaptable are key here I think. Studying the code from the skeleton is very interesting.

As a related question (I can hijack my own thread can't I?), how would you layout the directories and files in a app? I was thinking like this:

Code: Select all

/lib/
      framework classes like FormController, Validator, Iterator, etc

/app/actions/
/app/view/
/app/model/
/app/controller/

/html/index.php
           include a config file with path settings etc
           instantiates the FrontController
             which starts an action, which includes the necessary view, model and controller
And any thoughts on my first atempt at a mvc design?
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

Here's my current layout for projects

Code: Select all

/3rdParty            # Third Party libraries and their associated wrappers for the framework, will mirror structure from /lib
/app                 # applications directory, all application specific code falls inside here
/ext                 # extensions to the framework
/lib                 # the bulk of the framework and it's child packages
/lib/Core            # the creamy neuget of the framework
/lib/Core/Property   # property handling and set up classes
/lib/Core/Type       # Type classes used throughout the framework (framework is type strict)
/lib/Database        # Database interaction classes
/lib/Internet        # Internet interaction classes
/lib/Language        # Language classes and language specific strings
/lib/Pattern         # Standard OOP pattern implementations
/lib/Render          # Render target classes
/lib/Render/HTML     # HTML render target handling
/lib/Render/PDF      # PDF render target handling
/lib/Render/XML      # XML render target handling
/lib/Render/XUL      # XUL render target handling
/lib/Security        # Security classes
/lib/Security/Hash   # Hashing algorithm classes
/lib/System          # OS specific interaction classes
/lib/System/IO       # Input/output interaction classes
/tests
That's just the structure now, I expect a lot more directories in the future.
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

MVC design...

KISS - Keep It Simple Stupid. ;) Problem with MVC is that it's composed of a lot of little pieces. It's often easy to lose sight of that. Conversely it's just as easy to lose sight of the big picture. A good approach is to create each unit as simply as possible, and as re-useable as possible - paying specific attention to creating a fixed standard API. Aborint's FC skeleton for example is highly generic - I could plug into any MVC with minor changes assuming the rest of the structure of the framework follows a similar API. Half the problems I find turn up when the API needs changes, or some little bit of functionality needing a hack to another class in order to work. Such changes are warning signs often - classes shouldn't care about other classes, they should only care about whether the interface to another class is as expected or not.

The worst thing happens when you realise your framework is starting to form a hierarchy of required classes. When you have a class with an inheritence tree that's more than 3 levels deep than it's usually a problem (for me at least - if a class will not work in another structure without major edits then it's not re-useable, and hence it's not doing its job of saving me from future maintenance work. A class should (ideal != reality all the time) do one thing, and do it well. If it's doing two things, than its a signpost to separate the functional areas into two new classes.

Now my own experience is far from perfect, but at the moment my framework includes maybe 40-50 files (1 class per file), and consumes about 1.6 MB of RAM per request. Not saying aim here - my needs were specific to a PHP open source webgame where performance is limited to a user's shared hosting specs - usually no opcode cache, limited memory, and where even seemingly minor memory footprints and processing improvements can translate to a significant gain given the resource limitations.

Another tip (questionably) is not to overextend yourself. If you need database abstraction, then use a third party library. Otherwise you'll be stuck with the maintenance bill for your custom library...;). My favourite terms at times are "BSD" and "LGPL". Do take a stab at reviewing such libs though - I remember reviewing ADOdb-Lite before switching from ADOdb and finding about 4 bugs that affected me. I then reviewed ADOdb for similarities and found two of the bugs were common. Result - I was using a PostgreSQL database with an abstraction library that failed to properly escape my SQL queries...<shudder>. Definitiely review even if it's a quick read though - a new set of eyes might spot something so obvious that others have simply ignored, or forgotten about, or just plain missed. Even pro developers are still human...:).
matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

Post by matthijs »

Wow, thanks Feyd and Maugrim. Those replies are very elaborate.

Feyd, that layout looks good. Although I think I'll start a bit smaller then that.

Maugrim, that's some good advice. The problem of all the pieces of a "framework" depending on eachother and therefore make the framework or the pieces in fact quite useless (from the point of view of reusability/extensibility etc) is something I immediately noticed when I started coding some classes.

About the KISS - indeed. That's what I liked about the Frontcontroller of Arborint. The actions are not hardcoded in the frontcontroller, but are completely independent. Just write a new class/action, throw it in thedirectory of actions and you're ready to use it.

I'll keep your advice in mind when I go on.

I also came across this thread by Voostind. Although I don't understand every example he gave, it seems he's talking about the same issues as you are, isn't it?
.. What if the text files have a different format? Well, then I replace the DataFileReader. What if I need to print the menu in a different layout? Well, then I replace the MenuPrinter. What if I stop using text files, and want to use a real database instead? Well, then I simply execute a query on a database and use a QueryIterator to process the results ...
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post by Maugrim_The_Reaper »

To an extent yes - it's all in the API. If I wanted to store data for example, than the class which needs a data store object shouldn't care about how the data is stored (it's not its purpose in existing) or even what stores it - it should only need to know the API to store data. Once you get that concept, than you can write several classes - each with the same API - which store data in different ways.

Common one is storing information from a database - you could store it as a serialised array/XML/pure PHP. But you can write it differently - Database/Memory/File. So you have a few decisions - the format of the data, and the method of storing the data. Getting these all working together in combination answers a lot of common OOP questions IMO. The leap is knowing all those classes share an API - since they have the same API, they are interchangeable, since they're interchangeable the class using them doesn't need to know what or who they are - it'll delegate the decision to someone who knows better - you.

If you keep delaying and delegating that decision you end up with either specifically setting which ones to use, or using a secondary class to make the decision based on a configuration variable of some sort. Maybe a Factory class - something you can call which will figure out everything by itself (probably not re-useable - but the point is the other 6 classes are reuseable ;)).

If that sounds complex - it won't in a few weeks after you've dug into an MVC framework. It quickly becomes intuitive and second nature once you recognise its a common re-occuring pattern.
Post Reply