SitePoint Sponsor

User Tag List

Page 9 of 16 FirstFirst ... 5678910111213 ... LastLast
Results 201 to 225 of 397
  1. #201
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by kyberfabrikken
    By discussing even the smallest nuts and bolts along the way, we allow ourselves to learn from each other. And even people who aren't directly contributing seem to enjoy following the thread from the side.
    Most certainly we are!

    Skeleton 2.5 is looking pretty swell already. However, in the case of Intercepting Filters, couldn't we do something like this instead:
    PHP Code:
    $controller = new FrontController(new ActionMapper);
    $controller->addFilter(new KilroyFilter); 
    It seems to me that it would be possible to construct a CoR like this, but with more clarity in the API. Is your solution partly from reluctance to introducing an addFilter method in the FC? Perhaps you could wrap the FC in a FilterManager, as I've seen done somewhere on these forums.

    Pardon me if you've been through this already. I did browse the thread through but can't remember everything, being human and all. Just my 2 cents.

  2. #202
    SitePoint Wizard DougBTX's Avatar
    Join Date
    Nov 2001
    Location
    Bath, UK
    Posts
    2,498
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    As promised, my attempt at Rails style mapping:

    code:
    PHP Code:
    class Mapper
      
    {
        
    // Example: '$controller/$action/$id/' stored
        // as array('$controller', '$action', '$id')
        
    var $patterns = array();
        
        
    // Example:
        // array('controller' => 'ServerPage', 'action' => 'show')
        
    var $defaults = array();
        
        
    // Stores a new pattern/default combo pair
        
    function connect($pattern$default = array()) {
          
    $this->patterns[] = $this->clean_explode($pattern);
          
    $this->defaults[] = $default;
        }
        
        
    // Takes 'foo/bar', matches it with a patterns,
        // then combins it with the defults to give a full request.
        
    function request($raw_path) {
          
    $path $this->clean_explode($raw_path);
          
    $request $this->create_request($path);
          if (isset(
    $request['controller'])) {
            
    // could do with some file include logic here.
            
    $controller $request['controller'].'Controller';
            return new 
    $controller($request); 
          } else {
            throw new 
    Exception('Controller not found.');
          }
        }
        
        
    // Removes trailing slashes after an explode.
        
    function clean_explode($raw_path) {
          
    $path explode('/'$raw_path);
          !
    end($path) && array_pop($path);
          return 
    $path;
        }
        
        
    // Compares a path (in array form) with the stored patterns.
        // Returns the index of the pattern, or throws an exception.
        
    function resolve($path) {
          
    $path_length count($path);
          foreach (
    $this->patterns as $index => $pattern) {
            if (
    $path_length <= count($pattern)) {
              foreach (
    $path as $i => $part) {
                if (
    $path[$i] != $pattern[$i] && $pattern[$i][0] != '$') {
                  continue 
    2;
                }
              }
              
    // passed the foreach tests, this is the one
              
    return $index;
            }
          }
          throw new 
    Exception('Path not found.');
        }
        
        
    // Takes the selected pattern, and combines it with the  
        // path data and the defaults to give a full request array.
        
    function create_request($path) {
          
    $index $this->resolve($path);
          
          
    // Fill in the blanks
          
    $request $this->defaults[$index];
          foreach(
    $this->patterns[$index] as $i => $unit) {
            if(
    $unit[0] == '$' && isset($path[$i])) {
              
    $request[substr($unit1)] = $path[$i];
            }
          }
          return 
    $request;
        }
      }
      
      class 
    BaseController
      
    {
        protected 
    $params;
        
        function 
    __construct($params = array()) {
          
    $this->params $params;
        }
        
        function 
    get_params() {
          return 
    $this->params;
        }
        
        function 
    execute() {
          
    // Untested.
          
          // This function could be overridden in the child
          // controllers to add pre and post filters.

        /*  if (isset($this->_params['action'])) {
            $action = $this->_params['action'];
            if ($action && method_exists($this, $action)) {
              return $this->$action();
            }
          } */
        
    }
        
      }
      class 
    ServerPageController extends BaseController {  }
      class 
    UserController extends BaseController {  } 
    tests:
    PHP Code:
    require_once('simpletest/unit_tester.php');
    require_once(
    'simpletest/reporter.php');

      class 
    TestOfSkeleton extends UnitTestCase
        
    {
        function 
    testProbe() {
          
    $map = new Mapper();
          
    $map->connect('$foo/$bar', array('controller' => 'ServerPage''action' => 'show'));
          
    $this->assertEqual($map->patterns[0], array('$foo''$bar'));
          
    $this->assertEqual($map->defaults[0], array('controller' => 'ServerPage''action' => 'show'));
          
          
    $map = new Mapper();
          
    $map->connect('$foo/$bar/', array('controller' => 'ServerPage''action' => 'show'));
          
    $this->assertEqual($map->patterns[0], array('$foo''$bar'));
          
    $this->assertEqual($map->defaults[0], array('controller' => 'ServerPage''action' => 'show'));
        }
        
        function 
    testCreateRequest() {
          
    $map = new Mapper();
          
    $map->connect('$foo/$bar', array('controller' => 'ServerPage''action' => 'show'));
          
    $r $map->create_request(array('alpha''beta'));
          
    $this->assertEqual($r, array('controller' => 'ServerPage''action' => 'show''foo' => 'alpha''bar' => 'beta'));
        }
        
        function 
    testSimpleMapping()
        {
          
    $map = new Mapper();
          
    $map->connect('$page/', array('controller' => 'ServerPage''action' => 'show'));
          
          
    $raw_request 'about/';
          
    $controller $map->request($raw_request);
          
    $this->assertIsA($controller'ServerPageController');
          
    $this->assertEqual($controller->get_params(), array('controller' => 'ServerPage''action' => 'show''page' => 'about'));
        }

        function 
    testSimpleMappingWithoutTrainlingSlash()
        {
          
    $map = new Mapper();
          
    $map->connect('$page/', array('controller' => 'ServerPage''action' => 'show'));
          
          
    $raw_request 'about';
          
    $controller $map->request($raw_request);
          
    $this->assertIsA($controller'ServerPageController');
          
    $this->assertEqual($controller->get_params(), array('controller' => 'ServerPage''action' => 'show''page' => 'about'));
        }
        
        function 
    testMultipleMapping()
        {
          
    $map = new Mapper();
          
    $map->connect('$page/', array('controller' => 'ServerPage''action' => 'show'));
          
    $map->connect('$controller/$action/$id');
          
          
    $raw_request 'about/';
          
    $controller $map->request($raw_request);
          
    $this->assertIsA($controller'ServerPageController');
          
    $this->assertEqual($controller->get_params(), array('controller' => 'ServerPage''action' => 'show''page' => 'about'));
        }
        
        function 
    testMultipleMappingAlternate()
        {
          
    $map = new Mapper();
          
    $map->connect('$page/', array('controller' => 'ServerPage''action' => 'show'));
          
    $map->connect('$controller/$action/$id');
          
          
    $raw_request 'user/show/John';
          
    $controller $map->request($raw_request);
          
    $this->assertIsA($controller'UserController');
          
    $this->assertEqual($controller->get_params(), array('controller' => 'user''action' => 'show''id' => 'John'));
        }
        
        function 
    testShortMultipleMapping()
        {
          
    $map = new Mapper();
          
    $map->connect('$page/', array('controller' => 'ServerPage''action' => 'show'));
          
    $map->connect('$controller/$action/$id');
          
          
    $raw_request 'user/list';
          
    $controller $map->request($raw_request);
          
    $this->assertIsA($controller'UserController');
          
    $this->assertEqual($controller->get_params(), array('controller' => 'user''action' => 'list'));
        }
        
      }
      
        
    $test = &new GroupTest("MVC Controller");
        
    $test->addTestCase(new TestOfSkeleton());

        
    $reporter = new HtmlReporter();
        
    $test->run($reporter); 
    Douglas
    Hello World

  3. #203
    SitePoint Wizard
    Join Date
    Aug 2004
    Location
    California
    Posts
    1,672
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    There are a lot of things to respond to here, but the main one I want to address is Captain Proton's suggestion do to this:
    PHP Code:
    $controller =& new FrontController(
                new 
    SomeOtherConcreteFilter(
                    new 
    SomeAuthenticationFilter(
                        new 
    SomeStatisticsTrackingFilterOrWhatever(
                            new 
    ActionMapper()
                        )
                    )
                )
            ); 
    Rather than:
    PHP Code:
    $controller =& new FrontController(
    $controller->addFilter(new SomeFilter($a));
    $controller->addFilter(new SomeOtherConcreteFilter($b$c$d);
    $controller->addFilter(new SomeAuthenticationFilter($f$g);
    $controller->addFilter(new SomeStatisticsTrackingFilterOrWhatever($h);
    $controller->addFilter(new ActionMapper('action/''home''action');
    $controller->execute(); 
    My concern about Captain Proton's suggestion (which was implemented in 2.5 by kyberfabrikken) is that it leads to extra classes like RequestProcessor and code like:
    PHP Code:
    class RequestMapper extends RequestProcessor
    {
        
    /**
          * @abstract
          */
        
    function & mapRequest(&$request) { }

        function 
    handleRequest(&$request, &$response) {
            
    $dispatcher =& $this->mapRequest($request);
            if (
    is_null($dispatcher)) {
                
    $this->handleNext($request$response);
            } else {
                
    $dispatcher->execute($request$response);
            }
        }
    // end class RequestMapper 
    The reason I don't like this is because you have suddenly turned Filters into Chain Managers that must be instansiated with and manage the Filter Chain. kyberfabrikken has them doing triple duty above based also on whether they dispatch! You can no longer instansiate the filters with useful configuration parameters. This double duty exhibits itself in the current code by the fact that the ActionMapper can only be the last link in the chain (because it cannot accept a next link). Filters should filter the Request and Response, not the Chain.

    This is why I don't think a Front Controller should be implemented using Chain of Responsiblity. The reality is that the FilterChain for a Front Controller is not like a switch statement. The chain really needs to always run all filters. The reason is that the goal is dispatch no matter what happens. So an access control filter does not need to break the chain or even do an redirect. It simply needs to do: $request->set('action', 'login') which does an internal redirect at the action level. This is why I proposed a simple Filter Chain back in #181 . My vote is to implement a Filter Chain, not a Chain of Responsiblity.

    Finally I would like to address the folks who want to implement Ruby on Rails style dispatching, to say that the functionality you are looking for is really in the next phase in the Application Controller. Action Pack uses a ':controller/:action/:id' style mapping and in this thread we have so far only been focused on routing the ':controller' part. Our Front Controller will dispatch a specified controller that will in turn deal with its own specific parameters telling it what to do (such as Ryan Wray's 'index', 'new', 'display', 'create').
    Christopher

  4. #204
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by arborint
    Finally I would like to address the folks who want to implement Ruby on Rails style dispatching, to say that the functionality you are looking for is really in the next phase in the Application Controller. Action Pack uses a ':controller/:action/:id' style mapping and in this thread we have so far only been focused on routing the ':controller' part. Our Front Controller will dispatch a specified controller that will in turn deal with its own specific parameters telling it what to do (such as Ryan Wray's 'index', 'new', 'display', 'create').
    I was in the belief that this was a job for the mapper to do. It would seem appropriate to implement RoR-style Routing there - I don't see how this could be done in a different layer. (I don't know how familiar you are with Routing, but here's a quick guide.)

    Let's say the Mapper receives the Request object as any filter would and sets controller, action and whatever parameters it can get from the given route. It would then be the FC's job to actually respond to the mapped Request, instantiating the controller and dispatching the request to it. Would that be bad?

    Nothing speaks like code:
    PHP Code:
    $fc = new FrontController;
    $fc->addFilter(new KilroyFilter);
    $fc->addFilter(new ActionPackMapper('routes.xml'));
    $fc->execute(new Request);

    class 
    FrontController {
       function 
    addFilter($filter) {
          
    $this->filters[] = $filter;
       }
       function 
    execute($request) {
          foreach(
    $this->filters as $filter)
             
    $filter->execute($request);

          
    $c = new $request->getController();
          
    $c->execute($request);
       }
    }

    class 
    Controller {
       function 
    execute($request) {
          
    $action $request->getAction();
          if(
    method_exists($this$action))
             
    $this->$action($request);
       }

    Now, the controller can do whatever it wants; it can dispatch the request to a method of its own or a separate Action. I figure this would bring in some flexibility, albeit at the cost of one extra class for those who decide to go by the Action way.

    I hope I'm not slipping into a direction you've been wanting to avoid.

  5. #205
    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)
    I think I pretty much agree with arborints latest statement about filters. It may be more flexible with a CoR-style implementation, but if the flexibility isn't used, it's just overhead.

    How are you other peoples view on this ?

    Quote Originally Posted by arborint
    Action Pack uses a ':controller/:action/:id' style mapping and in this thread we have so far only been focused on routing the ':controller' part.
    I disagree. Even if rails uses two parameters to identify the dispatcher, it makes no difference. They point to the dispatcher - that has nothing to do with the ApplicationController. If we used three parameters to identify the Dispatcher, it wouldn't change a thing either.

    The way I understand the FrontController pattern vs. the ApplicationController is this:
    The FrontController will always map the same incomming request to the same Dispatcher. The ApplicationController on the other hand maps one Dispatcher to another Dispatcher. It relies on internal state of the application to do this, and thus the same incomming Dispatcher may not always map to the same outgoing Dispatcher.
    You only need to use the ApplicationController if you rely on application-state to select the view, and if this only happens very seldom, you don't actually need to isolate the behaviour into a seperate class - it could just be handled internally by the Dispatcher. In fact - ApplicationController may be a bad thing in webapplications.

  6. #206
    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 Ezku
    Let's say the Mapper receives the Request object as any filter would and sets controller, action and whatever parameters it can get from the given route. It would then be the FC's job to actually respond to the mapped Request, instantiating the controller and dispatching the request to it. Would that be bad?
    (...)
    I hope I'm not slipping into a direction you've been wanting to avoid.
    You're definately not. That's a very interesting change you made. What you effecttively did was to remove the RequestMapper completely. Instead you allow for filters to manipulate the Request, and thus the mapping from Request to Dispatcher happens by simply writing changes into the Request itself. The only actual problem I could forsee with this should be that by changing the Request, one can't go back and retrieve the original input-data (since they might have changed). But I'm not sure if this is a good or a bad thing.

  7. #207
    SitePoint Guru BerislavLopac's Avatar
    Join Date
    Sep 2004
    Location
    Zagreb, Croatia
    Posts
    830
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by kyberfabrikken
    The only actual problem I could forsee with this should be that by changing the Request, one can't go back and retrieve the original input-data (since they might have changed). But I'm not sure if this is a good or a bad thing.
    Request might keep the original data (i.e. a copy of $_REQUEST) in a separate attribute...

  8. #208
    SitePoint Wizard DougBTX's Avatar
    Join Date
    Nov 2001
    Location
    Bath, UK
    Posts
    2,498
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by arborint
    Finally I would like to address the folks who want to implement Ruby on Rails style dispatching, to say that the functionality you are looking for is really in the next phase in the Application Controller. Action Pack uses a ':controller/:action/:id' style mapping and in this thread we have so far only been focused on routing the ':controller' part.
    PHP Code:
    $map = new Mapper();

    // In a config file
    $map->connect('$page/'
            array(
    'controller' => 'ServerPage''action' => 'show'));
    $map->connect('$controller/$action/$id');

    // At the top of the script
    $request_path 'user/list';
    $controller $map->request($request_path);
    $controller->execute(); 
    You even get lazy loading for free. The base controller can handle pre and post filters in the execute method.

    Douglas
    Hello World

  9. #209
    SitePoint Member
    Join Date
    Feb 2004
    Location
    Poland
    Posts
    9
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Allow me to go back to access control for a moment, because I believe it highlights a problem in this otherwise great design.
    Quote Originally Posted by arborint long time ago
    For example, say I want to centralize access control by adding a filter in front of the dispatcher. I might make sense to put access information about an action in the map along with the dispatch information. That means the access control filter would need to get to the mapper data too.
    I saw in this thread examples of mappers that don't include a map, but rather create a dispatcher basing on www.example.com/index.php?action=foo or www.example.com/index/foo. Mappers like this are interchangeable and lightweight. One should not be required to create and maintain a map, if this map is not necessary for the Mapper to perform its duty.

    It would be more natural to store this extra data (such as access control) for Intercepting Filters in a separate, single location and retrieve it after the Mapper has created a Dispatcher.
    However, it seems to me that this requires every Dispatcher to have a unique name. More than that, a single ServerPageDispatcher could vary its access control depending on which server page is included. So the current design does not support this kind of lookup.

    Does it make sense for the Mapper to return a name, which is then used to create the appropriate Dispatcher, along with access control (and possible other) stuff? It does sound like Action names, though...

  10. #210
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Ezku
    Nothing speaks like code:
    The rails-esque way of doing this would be

    PHP Code:
    $fc = new FrontController;
    $fc->addFilter(new KilroyFilter);
    $fc->addFilter(new ActionPackMapper('routes.xml'));
    $fc->execute(new Request);

    class 
    FrontController {
       function 
    addFilter($filter) {
          
    $this->filters[] = $filter;
       }
       function 
    execute($request) {
          foreach(
    $this->filters as $filter) {
             
    $filter->execute($request);
          }

          
    $controller = new $request->getController();
          
    $action $request->getAction();
          
          if(
    method_exists($controller$action)) {
             
    $controller->$action($request);
          } else {
              
    // raise an error here
          
    }
       }

    Personally, I would pass the request object in the constructors, but I'm not sure about advantages vs disadvantages in regards to that.

  11. #211
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Damn. I wrote a lengthy reply, but the forums were down and... Oh, well. Here's what I can remember having said.
    Quote Originally Posted by 33degrees
    The rails-esque way of doing this would be
    I'm aware of that. I wasn't trying to recreate Rails, but figure a flexible solution for this thread's purposes.
    Personally, I would pass the request object in the constructors, but I'm not sure about advantages vs disadvantages in regards to that.
    I'm not certain if I know what you mean. I assume $controller = {$request->getController()}($request); in which case I agree with you, but only when using RoR-style dispatching.

    As for now I'm in favor of the FC dispatching the Request to a Controller (which may be a PageController or an ActionDispatcher) via an execute method. We have the Mapper to tell the FrontController where to dispatch the request to, and the receiver could take whatever action; this would allow for both RoR-style PageController classes and more purist ActionDispatcher + Action combinations. Or perhaps we should have the Mapper instantiate the receiver? The FC's role would be reduced to merely executing the Controller after the Filters have been ran.
    PHP Code:
    $fc = new FrontController;
    $fc->addFilter(new KilroyFilter);
    $fc->addFilter(new Mapper);
    $fc->addFilter(new DoWhatEverWithMappedDataFilter);
    $fc->execute(new Request);

    class 
    Mapper {
        (...)
        function 
    execute($request) {
            (...)
            
    $request->setController(new ActionDispatcher('whatever''arguments');
        }
    }
    class 
    FrontController {
        (...)
        function 
    execute($request) {
            foreach(
    $this->filters as $f)
                
    $f->execute($request);
            
    $controller $request->getController();
            
    $controller->execute($request);
        }
    }
    class 
    Request {
        var 
    $controller;
        (...)
        function 
    setController($c) { $this->controller $c; }
        function 
    getController() { return $this->controller; }

    At this point the FC could probably be renamed to something more descriptive, but I'll leave that to you if this catches on. And again, do tell if I'm sidetracking.

  12. #212
    SitePoint Wizard
    Join Date
    Aug 2004
    Location
    California
    Posts
    1,672
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I think I pretty much agree with arborints latest statement about filters. It may be more flexible with a CoR-style implementation, but if the flexibility isn't used, it's just overhead.
    I think a straight Filter Chain is what is needed. I don't see CoR as flexiblity; I see it as an unnecesary feature. These filters filter the Request/Response not the chain. CoR is probably better applied in the Application controller.

    Regarding all of the Ruby on Rails dispatching I think the main point here is that this code is not intended to do ONLY RoR style, but also Struts and simple PHP style "routing". If you want a clean RoR specific implementation then this probably is not the codebase for you. We want to support multiple styles so this code will not produce as clean a RoR style controller as coding it RoR style directly. That said I think we will be able to do RoR style routing fine.
    Quote Originally Posted by kyberfabrikken
    I disagree. Even if rails uses two parameters to identify the dispatcher, it makes no difference. They point to the dispatcher - that has nothing to do with the ApplicationController. If we used three parameters to identify the Dispatcher, it wouldn't change a thing either.
    I think the fact that Rails uses two parameters by definition means they are combining controllers for their specific implementation. There is nothing wrong with this, but it is not expandable to other styles. Our Front Controller dispatches on a single action. Its job is to centralize the first step in the process. In many cases that is enough. But where there are more specifics to the "action" there needs to be a second "dispatch". That is done in the Application Controller. Let's be clear about what this second "dispatch" is. It is usally the state of the View which is very much an Application Controller thing. The main example is for CRUD type interfaces and the example given above was for just that ('index', 'new', 'display', 'create'). That's the Application Controller's job.

    Quote Originally Posted by cryonax
    I saw in this thread examples of mappers that don't include a map, but rather create a dispatcher basing on www.example.com/index.php?action=foo or www.example.com/index/foo. Mappers like this are interchangeable and lightweight. One should not be required to create and maintain a map, if this map is not necessary for the Mapper to perform its duty.
    It is not necessary, there will be multiple Action Mappers available. I think the point is that the external map is a very practical place to include data associated with each action in the map. Access control is an example of this kind of data. It centralizes everything into a single location which simplifies things on the build side. Java frameworks use centralized XML files, PHP can use a more lightweight version of these (because it has to be loaded every time). But it certainly is not required and if used can also be overridden by a specific controller as well.
    Christopher

  13. #213
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Ezku
    I wasn't trying to recreate Rails, but figure a flexible solution for this thread's purposes.
    Actually, I'm looking at the rails source right now, and the way you had done is the way they do it as well.

    From what I can understand of their program flow, it goes like this;

    • Dispatcher object acts as a front controller.
    • Dispatcher creates request and response objects, and passes the request to the mapper, which is called Route.
    • The Route object analyzes the request and returns the Controller.
    • The dispatcher calls the Controller's process method, with the Request and the Response as parameters.
    • The process method calls the action to be performed, and returns the modified Response object
    • Finally the Dispatcher calls the returned Response object's out method


    Pretty straightforward then, and not too different from what we've been doing. The interesting part is that filtering is handled at the Controller level, and not the Dispatcher (front controller) level. There are two chains, one pre and one post, and the Filters have access to the Controller object (and therefore the Request and Response objects).

    Here then, is a quick php equivalent I threw together, don't mind the mistakes

    PHP Code:
    $dispatcher = new Dispatcher;
    $dispatcher->dispatch();

    class 
    Route {
        function 
    recognize($request)
        {
            
    $controller_name $request->get_controller();
            return new 
    $controller_name;
        }    
        
    }

    class 
    Request {
        var 
    $request;
        var 
    $controller_var 'c';
        var 
    $action_var 'a';
        
        function 
    Request()
        {
            
    $this->request $_REQUEST;    
        }    

        function 
    get_controller()
        {
            return 
    $this->request[$this->controller_var];    
            
        }
        
        function 
    get_action()
        {
            return 
    $this->request[$this->action_var];    
            
        }
        
    }

    class 
    Response {
        var 
    $response;
        
        function 
    out()
        {
            print 
    $this->response;    
            
        }    
    }

    class 
    Dispatcher {
        function 
    dispatch($request) {
            
    $request = new Request;
            
    $response = new Response;
            
            
    $controller Route::recognize($request);
            
            
    $response $controller->process($request$response);
          
            
    $response.out();
        
        }
    }

    class 
    Controller {
        var 
    $pre_filters;
        var 
    $post_filters;
        
        function 
    addFilter($filter$type 'pre') {
            if (
    $type 'pre') {
                
    $this->pre_filters[] = $filter;
            } else {
                
    $this->post_filters[] = $filters;
            }
        }

        function 
    process($request$response) {
            
    $this->request $request;
            
    $this->response $response;
            
            foreach(
    $this->pre_filters as $filter) {
                
    $filter->execute($this);
            }
            
            
    $action $request->get_action();
            
            
    $this->$action();
            
            foreach(
    $this->post_filters as $filter) {
                
    $filter->execute($this);
            }
            
            return 
    $this->response;
        }
    }

    class 
    WeblogController extends Controller {
        function 
    WeblogController()
        {
            
    $this->addFilter(new AuthenticationFilter'pre');    
            
    $this->addFilter(new CompressionFilter'post');
            
        }
        
        function 
    index()
        {
            
    // do an action here, etc...
            
        
    }
        
        

    Any comments, ideas, suggestions?

  14. #214
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by 33degrees
    Pretty straightforward then, and not too different from what we've been doing. The interesting part is that filtering is handled at the Controller level, and not the Dispatcher (front controller) level. There are two chains, one pre and one post, and the Filters have access to the Controller object (and therefore the Request and Response objects).
    Indeed, we're not far from that. But as arborint said, we're here to create a flexible skeleton, not a PHP on Rails. Our skeleton should, however, be well suited for a RoRish approach if the developer so wishes.

    For a moment, my thoughts were on a Request object that would provide some rudimentary mapping, as in straight from $_GET-variables (aren't the requested controller and action a part of the request? :), and additional processing could be handled by filtering the FrontController. How's this sound on accounts of extensibility?

    I would like to contribute an actual code package based on what's been said, but I'm afraid I can't get myself around to coding PHP4 anymore. That's what you're after, right?

  15. #215
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    URL Building

    I know this is a ways off but I'm wondering how URL building would be done. Is this something the controllers/handlers would take care of? Is it possible that the handlers would return a specialized object to handle building the urls?

    // inside of the controller:
    $url =& $this->getUrl();
    $url->setParameter('do', 'logout');

    and then you sould set the url/link to the view etc...
    $this->response->set('logout_link', $url->getLink('logout'));

    The PHP Eclipse library had a cool URL class (I've modded one for path info also) which may give you an idea of what I'm talking about.

    - matt

  16. #216
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Ezku
    Indeed, we're not far from that. But as arborint said, we're here to create a flexible skeleton, not a PHP on Rails. Our skeleton should, however, be well suited for a RoRish approach if the developer so wishes.
    Again, I'm not advocating we neccesarily follow the Rails approach, I just wanted to add another perspective to this project. However, I do agree with the rails placement of the filter chain, and that is what I'm suggesting we try.

    For a moment, my thoughts were on a Request object that would provide some rudimentary mapping, as in straight from $_GET-variables (aren't the requested controller and action a part of the request? , and additional processing could be handled by filtering the FrontController. How's this sound on accounts of extensibility?
    The requested controller and action are definitely part of the request, but sometimes (as in the case of 'nice' urls) the mapping can be pretty complex, and we may want to implement different mappings, which is why I think the mappings should be done by a seperate object.

    Quote Originally Posted by Ezku
    I would like to contribute an actual code package based on what's been said, but I'm afraid I can't get myself around to coding PHP4 anymore. That's what you're after, right?
    I think we agreed that whatever final product we come up with would need both versions; but in the meantime, I think people should post whatever they're more comfortable with.

  17. #217
    SitePoint Member
    Join Date
    Feb 2004
    Location
    Poland
    Posts
    9
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by arborint
    It is not necessary, there will be multiple Action Mappers available. I think the point is that the external map is a very practical place to include data associated with each action in the map. Access control is an example of this kind of data. It centralizes everything into a single location which simplifies things on the build side. Java frameworks use centralized XML files, PHP can use a more lightweight version of these (because it has to be loaded every time). But it certainly is not required and if used can also be overridden by a specific controller as well.
    You are right, but the key word here is external. An external map associated with each action. Not with each URL. There is a difference between "index.php?action=foo maps to FooAction with some data associated" and "index.php?action=foo maps to FooAction, which has some data associated to it". What if "index.php?action=foo2 maps to FooAction with some different data associated"? That would be very dangerous.

    I strongly agree with the idea of a centralized map with access control (and other) data. The format (XML, PHP arrays, etc) does not matter. I am against tying this functionality to the mapper. In this way I cannot swap my mapper for another one (which provides, for example, a nicer URL scheme) unless I duplicate my whole access control map for the new one. Or am I wrong here?

  18. #218
    SitePoint Guru
    Join Date
    Oct 2001
    Posts
    656
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I am against putting access control (authorization) in anything but the Actions themselves, here's why. Say I have an Action for editing some news item. A user can only edit that item if it is his own. So in order to check for access control, I need to load the news item from the database and check that the news item's user id matches the user's id.

    There are other more complex situations than this one I can think of.

  19. #219
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by 33degrees
    Again, I'm not advocating we neccesarily follow the Rails approach, I just wanted to add another perspective to this project. However, I do agree with the rails placement of the filter chain, and that is what I'm suggesting we try.
    Thank you for that, the example did provide some interesting points. I do think we need two different filter chains, one in the FrontController and one in the Controller / Action.
    The requested controller and action are definitely part of the request, but sometimes (as in the case of 'nice' urls) the mapping can be pretty complex, and we may want to implement different mappings, which is why I think the mappings should be done by a seperate object.
    My point exactly - it can be pretty complex. Meaning, you don't have to have a Mapper at all if what you're doing is simple. Need routing? Create a Mapper to filter the request.
    Quote Originally Posted by Captain Proton
    I am against putting access control (authorization) in anything but the Actions themselves, here's why. Say I have an Action for editing some news item. A user can only edit that item if it is his own. So in order to check for access control, I need to load the news item from the database and check that the news item's user id matches the user's id.
    Since it's a Filter's job to do authentication and not authorization, is it not always left for an Action to check if the user has access to whatever? To me this seems rather obvious.

  20. #220
    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)
    RequestMappers as Filters
    The latest turn this thread took seems to be the following :
    Until now, the mechanism has been to isolate the requestmapping in an object - RequestMapper. The FrontController pass the Request to the RequestMapper, which returns a Dispatcher. The Dispatcher is executed.

    As a spinoff from a post by Ezku, an alternative structure has been proposed :
    Rather than having a seperate RequestMapper, the FrontController should assume that the name of the Dispatcher is always available as a certain property of the Request. ("action" for example). In order to support different mapping-schemes, the FrontController could use Filters, which for one thing could manipulate the Request. The consequence of this is that the former RequestMappers are now merged with other types of Filters.
    At first I thought that this second method was a simpler call than the previous, but after thinking about it for the past two days, I'm not so sure any more.
    The problem is that there is no longer any distinction between Filters and RequestMappers. While they have some common ground, I think that this makes the Filter a very broad entity. Having a seperate RequestMapper is a much clearer definition of responsibilities.
    My vote is therefore to keep the RequestMapper as it is.

    Implementing the filterchain
    That said, let's step back a few posts, to the discussion on how to provide an interface for Filters in the FrontController. It has been proposed that we should rather implement a stack (two stacks actually) than a CoR style filter-chain. The main concern seems to be bloat. A stack is much simpler, and doesn't impose the same responsibility on each filter. One thing that may have over-exposed this image is that I did implement RequestMappers on the same chain as the InterceptingFilters. As I explained before, the Mappers have a lot in common with the Filters, but they are different. I think therefore, that it may help to stick them on two different chains.
    The only real benifit of using a CoR style chain over a stack, is that the filters can break the chain, and even alter it. In reality I don't think this is that important or even appropriate. The programflow should be dealt with in the ApplicationController anyway.
    I would therefore conclude that we should try to go with a stack rather than a CoR, as arborint (and others) have suggested. Are there any strong opponents to this disposition ?

    Skeletons on Rails
    arborint already said it, but I just want to nodd my head acknowledgingly. RoR has a has a Dispatcher which consists of two parts - an object (called Controller) and a method (called an action). Since PHP doesn't allow to return a pointer to a function, this means that an intermediate object would be needed to support this. There has already been half a dozen suggestions of how to do this. Most of them suffers from the author insisting on putting that functionality into the FrontController itself. That is a bad idea, since it makes the setup unable to deal with a cleaner style of mapping, where only a single object is returned, rather than object+function. The RoR style Dispatcher should rather be enclosed in a object. I have already posted a suggestion to how this could be done in post #191.

    If we can conclude on a) to use a stack filterchain, and b) to keep the request->dispatcher mapping in the RequestMappers, I will update the code and post it back here. We can then go on and have a look at how to implement authorization ?

  21. #221
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by kyberfabrikken
    RequestMappers as Filters
    (...)
    Implementing the filterchain
    (...)
    Skeletons on Rails
    (...)
    If we can conclude on a) to use a stack filterchain, and b) to keep the request->dispatcher mapping in the RequestMappers, I will update the code and post it back here. We can then go on and have a look at how to implement authorization ?
    I concur. I have a PHP5 solution, based on what you said above. I think it's pretty elegant and extendable. Take a look.

    Note: The Mapper here does nothing fancy, it just returns a Dispatcher. In a normal situation you would want to implement some basic functionality in the Mapper and extend it in another class to create eg. a RoRish RouteMapper. This is a skeleton, alright. :)
    Attached Files Attached Files
    Last edited by Ezku; Jun 21, 2005 at 10:31.

  22. #222
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by kyberfabrikken
    RequestMappers as Filters
    The problem is that there is no longer any distinction between Filters and RequestMappers. While they have some common ground, I think that this makes the Filter a very broad entity. Having a seperate RequestMapper is a much clearer definition of responsibilities.
    My vote is therefore to keep the RequestMapper as it is.
    Although the other approach is workable, I'm inclined to agree with you here.

    Quote Originally Posted by kyberfabrikken
    Implementing the filterchain
    I would therefore conclude that we should try to go with a stack rather than a CoR, as arborint (and others) have suggested. Are there any strong opponents to this disposition ?
    1 Vote for stacks here. As Ezku said, I think it would be best to have to have filter stacks in both the Front Controller and (to use your terminology) the Dispatcher. As with RoR, being able to place a filter Pre, Post, or Around (which adds it to both the pre and post chains) would provide a lot of flexibility without too much complexity.

    Quote Originally Posted by kyberfabrikken
    Skeletons on Rails
    arborint already said it, but I just want to nodd my head acknowledgingly. RoR has a has a Dispatcher which consists of two parts - an object (called Controller) and a method (called an action). Since PHP doesn't allow to return a pointer to a function, this means that an intermediate object would be needed to support this.
    From my understanding of Rails, the method call is done in the process method of the Dispatcher object, and this is probably the best solution. This way, those who want RoR style dispatching simply use the appropriate Mapper and Dispatcher base class, and the Front Controller can stays as lean as possible.

    Quote Originally Posted by kyberfabrikken
    If we can conclude on a) to use a stack filterchain, and b) to keep the request->dispatcher mapping in the RequestMappers, I will update the code and post it back here. We can then go on and have a look at how to implement authorization ?
    Have we decided on wether to use a Response object? I vote for yes, as I think it's the best approach for implementing post filtering (ie compressing output)

  23. #223
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Another thing I believe we should keep in mind is a system for handling composite views; I've yet to come up with an elegant solution in my own projects, and I'm sure many of us would benefit greatly from having one included in this project.

  24. #224
    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)
    I had a look at the code contributed by Ezku.

    I would want to rename Mapper to RequestMapper. Mapper isn't very discriptive, it's a general pattern.

    RequestMapper shouldn't extend from Controller for two reasons. a) it isn't a Controller, in that it doesn't take over the controlflow. It's rather a utility for the FrontController.
    b) it has a different signature for the execute() method. The Controller's doesn't return anything, while the RequestMapper does. The RequestMapper also doesn't need the Response object.

    I like that you have introduced an abstract Controller class. Although I don't see why you have the event_initialize() method ? Descendants could just use their constructor for this.

    The interface Filter should probably have one method for pre and one for post processing. If not, a filter won't be able to execute at both places.

    The execute() method of Dispatcher relies on it's descendants to call runPreFilters() & runPostFilters(). This seems awkward. We could either use a CoR style filterchain around the Dispatcher, or the execute() method could declare final for Dispatcher and handle the filters from there. Eg :
    PHP Code:
    class Dispatcher extends Controller
    {
        public final function 
    execute(Request $requestResponse $response)
        {
            
    $this->runPreFilters($request$response);
            
    $this->execute($request$response);
            
    $this->runPostFilters($request$response);
        }

        abstract public function 
    run(Request $requestResponse $response);


  25. #225
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by 33degrees
    Another thing I believe we should keep in mind is a system for handling composite views; I've yet to come up with an elegant solution in my own projects, and I'm sure many of us would benefit greatly from having one included in this project.
    I was thinking of actions being able to call sub-actions, but somehow it doesn't seem to fit in our current design. Composite views are a very important point in an MVC design, so I'd be glad to hear good ideas here.


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
  •