SitePoint Sponsor

User Tag List

Page 15 of 16 FirstFirst ... 5111213141516 LastLast
Results 351 to 375 of 384
  1. #351
    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 DougBTX
    @kyberfabrikken: could you edit the the first post in this thread to point to the posts with the latest code in them? I know it would be more helpful to me, as someone who's not been keeping up with development, if I could find all the current code in one place

    Thanks!
    I would, but which is that ? We're drifting in several directions at the moment.

  2. #352
    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 kyberfabrikken
    I would, but which is that ? We're drifting in several directions at the moment.
    That must be why I couldn't find it then...
    Hello World

  3. #353
    SitePoint Member
    Join Date
    Apr 2005
    Posts
    3
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Ezku
    Well, here's a release then. Managed to shave off a few bugs while I was at it.

    Some features, like the EventHandler+Delegate combo, aren't included. (I used them to automate Session committing by having the Response host an EventHandler that has an onResponse event.) I can update this to include them if you want to, though.
    I took a look to the skeleton 2.5.1 and by regarding the routing system (example_routing.php), I don't see how to load a form (for example the form in example_form.php)
    It is possible with this routing system by clicking on an url like "index.php/form" to load the form in example_form.php. How would you do that ?

  4. #354
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by tharos
    I took a look to the skeleton 2.5.1 and by regarding the routing system (example_routing.php), I don't see how to load a form (for example the form in example_form.php)
    It is possible with this routing system by clicking on an url like "index.php/form" to load the form in example_form.php. How would you do that ?
    Now that I had the time to look at the code -- I thought this was trivial.

    What the Router does is map the given URL to parameters and then create a Controller based on them. The latter part is missing in 2.5.1, so you'll have to do it yourself. Take a look at how the Controller is initialized in example_form.php:
    PHP Code:
    require_once('action_form/form.php');

    $controller = new form;
    $controller->execute($context = new Context); 
    You'll have to modify the Router so that it can create and return an instance of the class form when it recieves parameters that map into 'controller' => 'form'. The FrontController will then take care of executing it.

  5. #355
    SitePoint Zealot Overunner's Avatar
    Join Date
    Mar 2004
    Location
    Sweden
    Posts
    180
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I have a question regarding the Context-object. As far as I've understood, it solves the problem of not having to pass the Request, Response (perhaps Session) instance to each class who needs them. Instead, we pass the Context-object which contains a single instance of the three classes I mentioned. By doing this, we minimize the number of arguments required to 'execute'...say an Action.
    PHP Code:
    class XyzAction extends Action
    {
      function 
    execute(Context $context)
      {
        
    $request  $context->getRequest();
        
    $response $context->getResponse();

        
    // Do something with the model...
      
    }

    But I wonder if the Context-object actually is needed. Why not implement the Request/Response(/Session)-classes as singletons and fetch them when they are needed? E.g:
    PHP Code:
    class XyzAction extends Action
    {
      function 
    execute()
      {
        
    $request Request::getInstance();
        
    $response Response::getInstance();

        
    // Do something with the model...
      
    }

    That way, we don't have to pass anything at all to the Action. This makes the singleton-classes 'global' (and we all know that globals are evil ). But in this case, I think it's logical to have them global, since they are needed almost everywhere in the application.

  6. #356
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Overunner
    That way, we don't have to pass anything at all to the Action. This makes the singleton-classes 'global' (and we all know that globals are evil ). But in this case, I think it's logical to have them global, since they are needed almost everywhere in the application.
    Nothing explicitly prevents you from doing that, but as you mention, globals are seen as bad practice. I tend to concur. It just seems a lot cleaner to pass the Context around. (Btw, I personally object to having getter methods in Context. It gets ugly really quick. $this->getContext()->getRequest()->setParameter('foo', 'bar') - ew!)

    Context is by no means meant to host just Request and Response - it's supposed to encapsulate all kinds of common functionality. Take a look at the Context class in Vanilla, a great OS forum software.

  7. #357
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    To get back to where we dropped off, here's a PageController that I've built:
    PHP Code:
    abstract class PageController implements IHandler
    {
        const 
    action_default 'default';
        const 
    action_prefix 'action_';
        
        
    /**
         * Template root in relation to PATH_FRAMEWORK
         */
        
    const template_root 'app/Template/';
        
        
    /**
         * @var    boolean    PageController initialization status
         */
        
    private $initialized 0;
        
        
    /**
         * @var    array    PageController
         */
        
    private $children = array();
        
        
    /**
         * @var    object    Model associated with Page
         */
        
    protected $model NULL;
        
        
    /**
         * @var object    View associated with Page
         */
        
    public $view NULL;
        
        
    /**
         * @var    object    Context
         */
        
    public $context NULL;
        
        
    /**
         * Events:
         *     onStartup(self):
         *         - Upon initialization (once only)
         *         - Context available.
         *         - Use to modify defaults, templates, root, helpers
         *     onInit(self):
         *         - Upon initialization (once only)
         *         - View and ViewHelpers created and set
         *         - Use to initialize Model (see init()) and set dynamic defaults
         *     onActionStart(self, action):
         *         - Before fetching results for an action
         *     onActionFinish(self, action):
         *         - After fetching results for an action
         *     onPartial(self|child, action):
         * @var    object    EventHandler
         */
        
    public $events NULL;
        
        
    /**
         * @var    string    template directory in relation to PageController::template_root
         */
        
    public $root '';
        
        
    /**
         * Templates to add by default
         * @var    array    template in relation to this::$root
         */
        
    public $templates = array();
        
        
    /**
         * A ViewHelper to store results from partial actions
         * @var    object    DataSpace
         */
        
    public $slots = array();
        
        
    /**
         * Default values for the View - shared with other PCs
         * @var    object    DataSpace created from default value
         */
        
    public $defaults = array();
        
        
    /**
         * Names of ViewHelpers to be used with the View.
         * In addition to HtmlHelper, these are included by default:
         *     'page'        =>    hosting PageController
         *     'context'    =>    Context
         *     'request'    =>    Request
         *     'slots'        =>    DataSpace
         * @var    object    DataSpace created from default value
         */
        
    public $helpers = array('html');
        
        public function 
    __construct()
        {
            
    $this->events    = new EventHandler($this);
            
    $this->defaults    = new DataSpace($this->defaults);
            
    $this->helpers    = new DataSpace($this->helpers);
            
    $this->slots    = new DataSpace;
        }
        
        
    /**
         * Placeholder. Triggered onInit(self). View and ViewHelpers created.
         * This is the place to instantiate your Model.
         */
        
    public function init() {}
        
        
    /**
         * An action can produce content in three ways:
         * 1)    echo / print / use the View's render method
         * 2)    return a string
         * 3)    add a template to the View
         * 
         * All three can be used simultaneously and they are rendered in the order above.
         */
        
    public function action_default($request)
        {
            echo    
    '<h1>Page not found</h1>',
                    
    '<p>No action responded to <code>'.$request->action.'</code>.';
        }
        
        public function 
    execute(Context $context)
        {
            
    /**
             * Initialize PageController
             */
            
    $this->initialize($context);
            
            
    /**
             * Determine action
             */
            
    if(!method_exists($thisself::action_prefix.($action $context->command->action)))
            {
                
    $action self::action_default;
            }
            
            
    /**
             * Trigger onActionStart
             */
            
    $this->events->onActionStart($this$action);
            
            
    /**
             * Process action:
             *     results = output buffer + return value + rendered templates
             */
            
    ob_start();
            
    $content $this->{self::action_prefix.$action}($context->request);
            
    $content ob_get_clean().$content;
            
    $content .= $this->view->renderTemplates();
            
            
    /**
             * Pass results to Response
             */
            
    $context->response->setContent($content);
            
            
    /**
             * Trigger onActionFinish
             */
            
    $this->events->onActionFinish($this$action);
            
        }
        
        
    /**
         * Execute an action in a child controller
         * 
         * @param    string    action
         * @param    string    controller, optional (defaults to self)
         * @param    array    additional command parameters, optional (accepts DataSpace)
         * @return    string    rendered results
         */
        
    public function partial($action$controller NULL$options = array())
        {
            
    $context $this->context;
            
    /**
             * Get Command initial state
             */
            
    $command $context->command->all();
            
            
    /**
             * Fetch target controller
             */
            
    switch(true)
            {
                case empty(
    $controller):
                    
    $controller $this;
                break;
                case empty(
    $this->children[$controller]):
                    
    $this->children[$controller] = $context->factory->controller->$controller();
                
    // Fall through
                
    default:
                    
    $context->command->controller $controller;
                    
    $controller $this->children[$controller];
                    
    $controller->defaults->merge(array_merge($this->view->all(), $controller->defaults->all()));
            }
            
            
    $context->command->merge($options);
            
    $context->command->action $action;
            
    $this->events->onPartial($controller$action);
            
            
    /**
             * Execute target, cache the results
             */
            
    $context->response->startBuffer();
            
    $controller->execute($context);
            
    $results $context->response->flushBuffer();
            
            
    /**
             * Synchronise View data, Restore Command to its initial state
             */
            
    $this->view->import($controller->view->all());
            
    $context->command->import($command);
            
            
            return 
    $results;
        }
        
        
    /**
         * Identical to partial(), but outputs results instead of returning them
         * 
         * @see    PageController::partial()
         */
        
    public function forward($action$controller NULL$options = array())
        {
            echo 
    $this->partial($action$controller$options);
        }
        
        
    /**
         * View::render() shorthand.
         */
        
    public function render($template)
        {
            return 
    $this->view->render($template);
        }
        
        
    /**
         * Initialize PageController, prepare for execution
         * @param    object    Context
         */
        
    private function initialize(Context $context)
        {
            if(!
    $this->initialized)
            {
                
    /**
                 * Startup
                 */
                
    $this->context $context;
                
    $this->events->onInit = new Delegate($this'init');
                
    $this->events->onStartup();
                
                
    /**
                 * Initialization sequence
                 */
                
    $this->initView();
                
    $this->initHelpers($context);
                
    $this->events->onInit();
                
                
    /**
                 * Prevent from initializing again
                 */
                
    $this->initialized 1;
            }
        }
        
        
    /**
         * Handle View initialization
         */
        
    private function initView()
        {
            
    $this->view = new View(PATH_FRAMEWORK.'/'.self::template_root.$this->root);
            
    $this->view->merge($this->defaults);
            
    /**
             * To uncover possible logical errors easier;
             * defaults play no role after initialization
             */
            
    unset($this->defaults);
                
            foreach(
    $this->templates as $template)
            {
                
    $this->view->addTemplate($template);
            }
        }
        
        
    /**
         * Handle ViewHelper initialization
         */
        
    private function initHelpers($context)
        {
            
    $this->transformHelpers($context);
            
            foreach(
    $this->helpers as $name => $helper)
            {
                
    $this->view->setHelper($name$helper);
            }
            
            
    $this->view->setHelper('page'$this);
            
    $this->view->setHelper('context'$context);
            
    $this->view->setHelper('request'$context->request);
            
    $this->view->setHelper('slots'$this->slots);
        }
        
        
    /**
         * Transform PageController::helpers
         * from    { name(optional) => helper instance or name }
         * to    { name => helper instance }
         */
        
    private function transformHelpers($context)
        {
            
    $helpers = array();
            
            foreach(
    $this->helpers as $name => $helper)
            {
                if(
    is_int($name))
                {
                    
    $name is_object($helper) ? str_ireplace('helper'''get_class($helper)) : $helper;
                }
                if(!
    is_object($helper))
                {
                    
    $helper $context->factory->helper->$helper();
                }
                
    $helpers[$name] = $helper;
            }
            
            
    $this->helpers->import($helpers);
        }

    Thus far I have found it great. Here's an AppController that works as a base class for other controllers in the application:
    PHP Code:
    class AppController extends PageController
    {
        
    /**
         * @var    object    BreadcrumbHelper
         */
        
    public $crumbs NULL;
        public function 
    __construct()
        {
            
    parent::__construct();
            
    $this->events->onStartup = new Delegate($this'initBreadcrumbHelper');
        }
        
        public function 
    initBreadcrumbHelper()
        {
            
    $this->crumbs $this->context->factory->helper->Breadcrumb();
            
    $this->helpers->crumbs $this->crumbs;
        }

    And here's a really simple example on possible use:
    PHP Code:
    class NewsController extends AppController
    {
        public 
    $templates = array('news.tpl');
        
        public function 
    action_index($request)
        {
            
    $this->slots->content $this->render('News/index.tpl');
        }
        
        public function 
    action_view($request)
        {
            if(!
    $request->has('id'))
            {
                
    /**
                 * Should forward to an error page, but I'm too lazy so I'll do this instead.
                 */
                
    $this->forward('index');
            }
            else
            {
                
    /**
                 * Fetch data from Model to View here
                 */
                
    $this->slots->content $this->render('News/view.tpl');
            }
        }

    ...where 'news.tpl' then has a line that says <?php echo $slots->content; ?>. I hope the code is self-explanatory, as I can't really conjure up anything reasonable to say.

  8. #358
    SitePoint Zealot
    Join Date
    Jul 2005
    Posts
    194
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Do you happen to have the EventHandler code, or do you want to share it? Looks interesting to support events

  9. #359
    SitePoint Wizard
    Join Date
    Aug 2004
    Location
    California
    Posts
    1,672
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I think any EventHandler code that might have briefly been in there has been stripped out. Perhaps Ezku or kyberfabrikken still have some. By EventHandler I am assuming that you are talking about some code where you register conditions and code is executed when the condition is true. That is what the FlowController/AppControllers do in a more general way.
    Christopher

  10. #360
    SitePoint Zealot
    Join Date
    Jul 2005
    Posts
    194
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I happen to found the EventHandler-code on some website. I think I amm gonna dive into the child controller and/or forward/partial methods of EzKu seems interesting to use when you want to trigger a new action. I.e. listview-action when you find out in the view-action that you are missing a record-id.

    Currently I used a header( 'Location...) for this, i.e. when a visitor is not logged it would redirect to hrader( 'Location: /account/login' );
    Last edited by wdeboer; Sep 6, 2005 at 16:51.

  11. #361
    SitePoint Zealot
    Join Date
    Jul 2005
    Posts
    194
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Ezku, may I ask you how do you have implementated the view-class? IT looks like you got some standard View-class. If you look at PageController::initView() you are instanciating a View-class with the template-directory as parameter. The idea is to override the initView method and initiate a Model/View-class itself ?

  12. #362
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by arborint
    I think any EventHandler code that might have briefly been in there has been stripped out. Perhaps Ezku or kyberfabrikken still have some. By EventHandler I am assuming that you are talking about some code where you register conditions and code is executed when the condition is true. That is what the FlowController/AppControllers do in a more general way.
    As it is currently, my EventHandler+Delegate combination is something else. It's merely to dynamically introduce hook methods - what I mean is that the events don't return a value nor are they reacted upon by the PageController. I looked into some implementations where they are used to something like what you described, but the whole idea seemed unfitting in this context.

    I've actually posted the code here before, but here we go:
    PHP Code:
    interface IDelegate
    {
        
    /**
         * Invoke subordinate
         * @param    array    arguments, optional
         * @return    mixed    subordinate return value
         */
        
    public function invoke($args = array());

    PHP Code:
    /**
     * Calls a method on an object upon invocation. A kind of refined callback.
     * 
     * @author        Ezku (dmnEe0@gmail.com)
     * @since        Jul 12, 2005
     */
    class Delegate implements IDelegate
    {
        private 
    $subordinate NULL;
        private 
    $method NULL;
        
        public function 
    __construct(&$subordinate$method)
        {
            
    $this->subordinate =& $subordinate;
            
    $this->method $method;
        }
        
        public function 
    invoke($args = array())
        {
            
    $this->subordinate =& Handle::resolve(&$this->subordinate);
            
    $callback = array(&$this->subordinate$this->method);
            return 
    call_user_func_array($callback$args);
        }

    PHP Code:
    /**
     * Implements event handling: attaching Delegates to events and triggering them
     * 
     * @author        Ezku (dmnEe0@gmail.com)
     * @since        Jul 12, 2005
     * 
     * @see            http://www.binarycloud.com/index.php/Drafts/NewEventsProposal
     * @see            http://www.akadia.com/services/dotnet_delegates_and_events.html
     */
    class EventHandler
    {
        
    /**
         * @var array    Events
         */
        
    private $events = array();
        
        
    /**
         * @var    array    default event triggering arguments
         */
        
    private $arguments = array();
        
        
    /**
         * Arguments passed to the EventHandler constructor will be set as default arguments
         * for any events triggered and can be overridden by defining them when triggering
         * said events: $events->onEvent($argument, $another);
         */
        
    public function __construct()
        {
            
    $this->arguments func_get_args();
        }
        
        
    /**
         * Attach a Delegate to an event
         * @param    string    event
         * @param    object    IDelegate
         */
        
    public function attach($eventIDelegate $delegate)
        {
            if (empty(
    $this->events[$event]))
            {
                
    $this->events[$event] = new Event;
            }
            
    $this->events[$event]->attach($delegate);
        }
        
        
    /**
         * Trigger event, invoking all attached events
         * @param    string    event
         * @param    mixed    argument, optional
         * ...
         * @return    int        number of events invoked
         * @see        __construct()
         */
        
    public function trigger($event)
        {
            
    $arguments $this->arguments;
            if (
    func_num_args() > 1)
            {
                
    $arguments func_get_args();
                
    array_shift($arguments);
            }
            
            if (isset(
    $this->events[$event]))
            {
                return 
    $this->events[$event]->trigger($arguments);
            }
            return 
    0;
        }
        
        
    /**
         * Shortcut for EventHandler::attach($event, $delegate):
         *     EventHandler->$event = $delegate;
         */
        
    public function __set($event$delegate)
        {
            
    $this->attach($event$delegate);
        }
        
        
    /**
         * Shortcut for EventHandler::trigger($event, $argument, $another):
         *     EventHandler->$event($argument, $another)
         */
        
    public function __call($event$args)
        {
            
    array_unshift($args$event);
            return 
    call_user_func_array(array($this'trigger'), $args);
        }
    }

    class 
    Event
    {
        private 
    $listeners = array();
        
        public function 
    attach(IDelegate $del)
        {
            
    $this->listeners[] = $del;
        }
        
        public function 
    trigger($args = array())
        {
            foreach(
    $this->listeners as $listener)
            {
                
    $listener->invoke((array) $args);
            }
            return 
    count($this->listeners);
        }

    And here are the unit tests:
    PHP Code:
    class EventHandlerTest extends UnitTestCase
    {
        public function 
    testDefaults()
        {
            
    $events = new EventHandler('default argument');
            
    $handler = new StubDelegate;
            
            
    $events->attach('event'$handler);
            
    $events->trigger('event');
            
            
    $this->assertEqual($handler->args, array('default argument'));
        }
        
        public function 
    testOverridingDefaults()
        {
            
    $events = new EventHandler('default argument');
            
    $handler = new StubDelegate;
            
            
    $events->attach('event'$handler);
            
    $events->trigger('event''overriding argument');
            
            
    $this->assertEqual($handler->args, array('overriding argument'));
            
            
    $events->trigger('event''triggering again');
            
    $this->assertEqual($handler->args, array('triggering again'));
        }
        
        public function 
    testShortcuts()
        {
            
    $events = new EventHandler('default argument');
            
    $handler = new StubDelegate;
            
            
    $events->event $handler;
            
    $events->event();
            
            
    $this->assertEqual($handler->args, array('default argument'));
        }
    }

    class 
    StubDelegate implements IDelegate
    {
        public 
    $args = array();
        
        public function 
    invoke($args = array())
        {
            
    $this->args $args;
        }

    PHP Code:
    class DelegateTest extends UnitTestCase
    {
        public function 
    testInvoke()
        {
            
    $target = new DelegateTarget;
            
    $delegate = new Delegate($target'method');
            
    $argument 'argument';
            
    $delegate->invoke(array($argument));
            
    $this->assertCopy($target->argument$argument);
        }
        
        public function 
    testParameterReferences()
        {
            
    $target = new DelegateTarget;
            
    $delegate = new Delegate($target'method');
            
    $argument 'argument';
            
    $delegate->invoke(array(&$argument));
            
    $this->assertReference($target->argument$argument);
        }
    }

    class 
    DelegateTarget
    {
        public 
    $argument = array();
        
        public function 
    method(&$argument)
        {
            
    $this->argument =& $argument;
        }

    Do note that if you find a version of this code somewhere (Mureakuha, wdeboer? ), it is definitely outdated. There was a critical bug in the Delegate that would crash or produce bizarre results - something to do with PHP5 object reference handling.

    I polished the PageController class a bit, so here you go with the View class attached:
    PHP Code:
    <?php
    /**
     * Base PageController
     * 
     * @author        Ezku (dmnEe0@gmail.com)
     * @since        Aug 26, 2005
     */
    abstract class PageController implements IHandler
    {
        const 
    action_default 'default';
        const 
    action_prefix 'action_';
        
        
    /**
         * Template root in relation to PATH_APP
         */
        
    const template_root 'Template/';
        
        
    /**
         * PageController storage - they are never created twice per page
         * @var    array    PageController
         */
        
    private static $children = array();
        
        
    /**
         * @var    boolean    PageController initialization status
         */
        
    private    $initialized 0;
        
        
    /**
         * @var    boolean render templates after action?
         */
        
    private $autorender true;
        
        
    /**
         * @var    object    Model associated with Page
         */
        
    protected $model NULL;
        
        
    /**
         * @var object    View associated with Page
         */
        
    private $view NULL;
        
        
    /**
         * @var    object    Context
         */
        
    public $context NULL;
        
        
    /**
         * Events:
         *     onStartup(self):
         *         - Upon initialization (once only)
         *         - Context available.
         *         - Use to modify defaults, templates, root, helpers
         *     onInit(self):
         *         - Upon initialization (once only)
         *         - View and ViewHelpers created and set
         *         - Use to initialize Model (see init()) and set dynamic defaults
         *     onActionStart(self, action):
         *         - Before fetching results for an action
         *     onActionFinish(self, action):
         *         - After fetching results for an action
         * @var    object    EventHandler
         */
        
    public $events NULL;
        
        
    /**
         * @var    string    template directory in relation to PageController::template_root
         */
        
    public $root '';
        
        
    /**
         * Templates to add by default
         * @var    array    template in relation to this::$root
         */
        
    public $templates = array();
        
        
    /**
         * A ViewHelper to store results from component actions
         * @var    object    DataSpace
         */
        
    public $slots = array();
        
        
    /**
         * View data - shared with other PCs
         * @var    object    DataSpace created from default value
         */
        
    public $data = array();
        
        
    /**
         * Names of ViewHelpers to be used with the View.
         * In addition to HtmlHelper, these are included by default:
         *     'page'        =>    current pagecontroller
         *     'context'    =>    Context
         *     'request'    =>    Request
         *     'slots'        =>    DataSpace
         * @var    object    DataSpace created from default value
         */
        
    public $helpers = array('html');
        
        public function 
    __construct()
        {
            
    $this->events    = new EventHandler($this);
            
    $this->data        = new DataSpace($this->data);
            
    $this->helpers    = new DataSpace($this->helpers);
            
    $this->slots    = new DataSpace;
        }
        
        
    /**
         * Placeholder. Triggered onInit(self). View and ViewHelpers created.
         * This is the place to instantiate your Model.
         */
        
    public function init() {}
        
        
    /**
         * An action can produce content in three ways:
         * 1)    echo / print
         * 2)    return a string
         * 3)    add a template to the View
         * 
         * If there are templates to be rendered in the stack after the action has been
         * processed, the action's results (buffer + return value concatenated) will be placed 
         * to $slots->action followed by rendering the stack. Otherwise the results are passed
         * to the Response directly.
         * 
         * An action is always called with the following arguments:
         * @param    object    Request
         * @param    object    DataSpace (View data)
         */
        
    public function action_default($request$data)
        {
            echo    
    '<h1>Page not found</h1>',
                    
    '<p>No action responded to <code>'.$request->action.'</code>.';
        }
        
        public function 
    execute(Context $context)
        {
            
    /**
             * Initialize PageController
             */
            
    $this->initialize($context);
            
            
    /**
             * Determine action
             */
            
    $action $context->command->action;
            if(!
    method_exists($thisself::action_prefix.$action))
            {
                
    $action self::action_default;
            }
            
    $this->events->onActionStart($this$action);
            
            
    /**
             * Process action
             */
            
    ob_start();
            
    $result $this->{self::action_prefix.$action}($context->request$this->data);
            
    $buffer ob_get_clean();
            
            
    /**
             * If there are templates to render, place action results to a slot
             */
            
    if ($this->view->countTemplates() && $this->autorender)
            {
                
    $this->slots->action $buffer.$result;
                
    $content $this->renderTemplates();
                if (empty(
    $content))
                {
                    
    $content $buffer.$result;
                }
            }
            else
            {
                
    $content $buffer.$result;
            }
            
            
    /**
             * Pass results to Response
             */
            
    $context->response->setContent($content);
            
    $this->events->onActionFinish($this$action);
            
        }
        
        
    /**
         * Prevent templates from being rendered after action
         * @return    boolean    previous autorender value
         */
        
    protected function autorender_disable()
        {
            
    $value $this->autorender;
            
    $this->autorender false;
            return 
    $value;
        }
        
        
    /**
         * Enable templates to be rendered after action
         * @return    boolean    previous autorender value
         */
        
    protected function autorender_enable()
        {
            
    $value $this->autorender;
            
    $this->autorender true;
            return 
    $value;
        }
        
        
    /**
         * TODO:    Does this make any sense?
         * 
         * View::addTemplate() shorthand. Automatically turns on autorender.
         * Discouraged; most likely you should use render() instead.
         */
        
    protected function addTemplate($template)
        {
            
    $this->autorender_enable();
            
    $this->view->addTemplate($template);
        }
        
        
    /**
         * Outputs a rendered template
         * @param    string    filename
         */
        
    protected function render($template)
        {
            echo 
    $this->view->render($template$this->data);
        }
        
        
    /**
         * Returns template render results in a string
         * @param    string    filename
         */
        
    protected function render_to_string($template)
        {
            return 
    $this->view->render($template$this->data);
        }
        
        
    /**
         * To be used by actions.
         * @return    boolean    action was called internally?
         */
        
    protected function is_internal()
        {
            return (
    $this->context->command->is_internal == true);
        }
        
        
    /**
         * Identical to component_as_string(), but outputs results instead of returning them
         * 
         * @see    PageController::component_as_string()
         */
        
    protected function component($target$options = array())
        {
            echo 
    $this->component_as_string($target$options);
        }
        
        
    /**
         * Execute an action in a child controller
         * 
         * @param    string    target: action, controller.action, module.controller.action
         * @param    array    additional command parameters, optional (accepts DataSpace)
         * @return    string    rendered results
         */
        
    protected function component_as_string($target$options = array())
        {
            
    /**
             * Parse target
             */
            
    $action $target;
            
    $controller NULL;
            if(
    strstr($target'.'))
            {
                
    $target explode('.'$target);
                
    $action array_pop($target);
                
    $controller join('.'$target);
            }
            
            
    /**
             * Get Command initial state
             */
            
    $context $this->context;
            
    $command $context->command->all();
            
            
    /**
             * Fetch Controller
             */
            
    if(empty($controller))
            {
                
    $controller $this;
            }
            else
            {
                
    $context->command->controller $controller;
                
    $controller $this->fetchController($controller);
            }
            
            
    /**
             * Mess with the Command
             */
            
    $context->command->merge($options);
            
    $context->command->action $action;
            
    $context->command->is_internal true;
            
            
    /**
             * Execute target, cache the results
             */
            
    $context->response->startBuffer();
            
    $controller->execute($context);
            
    $results $context->response->flushBuffer();
            
            
    /**
             * Synchronise View data, Restore Command to its initial state
             */
            
    $this->data->import($controller->data);
            
    $context->command->import($command);
            
            
            return 
    $results;
        }
        
        
    /**
         * @param    string    PageController name
         * @param    object    Context
         */
        
    private function fetchController($controller)
        {
            switch(
    true)
            {
                case empty(
    $controller):
                    
    $controller $this;
                break;
                case empty(
    self::$children[$controller]):
                    
    self::$children[$controller] = $this->context->factory->controller->$controller();
                
    // Fall through
                
    default:
                    
    $controller self::$children[$controller];
                    
    $controller->data->merge($this->data->merge($controller->data));
            }
            return 
    $controller;
        }
        
        
    /**
         * View::renderTemplates() shorthand
         * @return    string    render results
         */
        
    private function renderTemplates()
        {
            return 
    $this->view->renderTemplates($this->data);
        }
        
        
    /**
         * Initialize PageController, prepare for execution
         * @param    object    Context
         */
        
    private function initialize(Context $context)
        {
            if(!
    $this->initialized)
            {
                
    /**
                 * Startup
                 */
                
    $this->context $context;
                
    $this->events->onInit = new Delegate($this'init');
                
    $this->events->onStartup();
                
                
    /**
                 * Initialization sequence
                 */
                
    $this->initView();
                
    $this->initHelpers($context);
                
    $this->events->onInit();
                
                
    /**
                 * Prevent from initializing again
                 */
                
    $this->initialized 1;
            }
        }
        
        
    /**
         * Handle View initialization
         */
        
    private function initView()
        {
            
    $this->view = new View(PATH_APP.'/'.self::template_root.$this->root);
                
            foreach(
    $this->templates as $template)
            {
                
    $this->view->addTemplate($template);
            }
        }
        
        
    /**
         * Handle ViewHelper initialization
         */
        
    private function initHelpers($context)
        {
            
    $this->transformHelpers($context);
            
            foreach(
    $this->helpers as $name => $helper)
            {
                
    $this->view->setHelper($name$helper);
            }
            
            
    $this->view->setHelper('page'$this);
            
    $this->view->setHelper('context'$context);
            
    $this->view->setHelper('request'$context->request);
            
    $this->view->setHelper('slots'$this->slots);
        }
        
        
    /**
         * Transform PageController::helpers
         * from    { name(optional) => helper instance or name }
         * to    { name => helper instance }
         */
        
    private function transformHelpers($context)
        {
            
    $helpers = array();
            
            foreach(
    $this->helpers as $name => $helper)
            {
                if(
    is_int($name))
                {
                    
    $name is_object($helper) ? str_ireplace('helper'''get_class($helper)) : $helper;
                }
                if(!
    is_object($helper))
                {
                    
    $helper $context->factory->helper->$helper();
                }
                
    $helpers[$name] = $helper;
            }
            
            
    $this->helpers->import($helpers);
        }
    }
    ?>
    PHP Code:
    /**
     * Renders files with ViewHelpers
     * 
     * @author        Ezku (dmnEe0@gmail.com)
     * @since        Aug 26, 2005
     */
    class View
    {
        
    /**
         * @var    array    ViewHelpers
         */
        
    private $helpers = array();
        
        
    /**
         * @var    array    template file stack
         */
        
    private $templates = array();
        
        
    /**
         * @var    string    root path for templates
         */
        
    private $root NULL;
        
        
    /**
         *     onTemplateUnavailable(template, self)
         * 
         * @var    object    EventHandler
         */
        
    public $events NULL;
        
        
    /**
         * @var    string root path for templates, optional
         */
        
    public function __construct($root '')
        {
            
    $this->vars = new DataSpace;
            
    $this->root $root;
            
            
    $this->events = new EventHandler($this);
        }
        
        
    /**
         * Add a Template to the stack
         * @param    string    filename in relation to the root
         */
        
    public function addTemplate($template)
        {
            
    $this->templates[] = $template;
        }
        
        
    /**
         * Clear template stack
         */
        
    public function clearTemplates()
        {
            
    $this->templates NULL;
            
    $this->templates = array();
        }
        
        
    /**
         * Render template stack into a string
         * @param    object    DataSpace
         * @return    string    results
         */
        
    public function renderTemplates($data)
        {
            
    extract($this->helpers);
            
    ob_start();
            foreach(
    $this->templates as $template)
            {
                if(
    is_readable($path $this->root.$template))
                {
                    include(
    $path);
                }
                else
                {
                    
    $this->events->onTemplateUnavailable($template$this);
                }
            }
            
    $this->clearTemplates();
            return 
    ob_get_clean();
        }
        
        
    /**
         * @return    int    number of templates in stack
         */
        
    public function countTemplates()
        {
            return 
    count($this->templates);
        }
        
        
    /**
         * Render a template
         * @param    string    filename in relation to the root
         * @param    object    DataSpace
         * @return    string    results
         */
        
    public function render($template$data)
        {
            
    extract($this->helpers);
            
    ob_start();
            if(
    is_readable($path $this->root.$template))
            {
                include(
    $path);
            }
            else
            {
                
    $this->events->onTemplateUnavailable($template$this);
            }
            return 
    ob_get_clean();
        }
        
        
        
    /**
         * @param    string    name to access with in the templates
         * @param    object    ViewHelper
         */
        
    public function setHelper($name$helper)
        {
            
    $this->helpers[$name] = $helper;
        }

    Some of the most obvious changes are forward() -> component() and partial() -> component_as_string(). This was made in order to be more railesque. Their workings were also changed:
    PHP Code:
    $this->component('Controller.Action'$command_parameters
    The code is by no means complete, but a lot better than the previous version. At this point I guess I should give a note on the auxiliary classes: Command is merely a DataSpace in the Context and is loaded with the current Action and Controller names. If there are any oddities or glitches, do note.

    I hope the sudden surge in code output didn't offend anyone
    Last edited by Ezku; Sep 9, 2005 at 11:42. Reason: revised PageController a bit

  13. #363
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Oh, and since the usage has changed, here are some example classes:
    PHP Code:
    abstract class AppController extends PageController
    {
        
    /**
         * @var    object    BreadcrumbHelper
         */
        
    public $crumbs NULL;
        public function 
    __construct()
        {
            
    parent::__construct();
            
    $this->events->onStartup = new Delegate($this'initBreadcrumbHelper');
            
    $this->events->onActionStart = new Delegate($this'checkXhr');
        }
        
        public function 
    initBreadcrumbHelper()
        {
            
    $this->crumbs $this->context->factory->helper->Breadcrumb();
            
    $this->helpers->crumbs $this->crumbs;
        }
        
        
    /**
         * Disables autorender if Request is coming from Ajax
         */
        
    public function checkXhr()
        {
            !
    $this->context->request->is_xhr() or $this->autorender_disable();
        }

    CheckXhr is something I find rather nice. When used correctly, it can be an incredible help in using Ajax. It takes advantage of the is_xhr() method in Request that can tell if the HTTP request is coming from the Prototype JS library - a feature copied right off Rails. You'll see my point here:
    PHP Code:
    class NewsController extends AppController
    {
        public 
    $root 'News/';
        public 
    $templates = array('../news.tpl');
        public 
    $data = array('section' => 'news');
        
        public function 
    action_list()
        {
            
    $this->render('list.tpl');
        }
        
        public function 
    action_view($request)
        {
            if(!
    $request->has('id'))
            {
                
    $this->component('list');
            }
            else
            {
                
    $this->render('view.tpl');
            }
        }

    Now, if it's a normal request, the actions render normally and then the result is placed into the slot variable "action", which the autorendered template 'news.tpl' then has an echo statement for. But when the request is coming from an Ajax library - the autorender phase is skipped, resulting in the template contents being output directly (actually they're placed in the Response, but that's not the point here). This means that the output can be used eg. to replace an HTML element's contents with no to minimal tinkering. Tell me that isn't cool

  14. #364
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by wdeboer
    Ezku, may I ask you how do you have implementated the view-class? IT looks like you got some standard View-class. If you look at PageController::initView() you are instanciating a View-class with the template-directory as parameter. The idea is to override the initView method and initiate a Model/View-class itself ?
    No, I'm afraid not. You'll see that initView() is a private method. See my post above, it has an example on defining a root path (relative to the default template directory) for templates. The default template directory in turn depends on the constants PATH_APP (your Rails-fashioned application directory with /Model/, /Controller/ etc.) and PageController::template_root.

  15. #365
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    By the way, as I've come this far in getting the actions working, I'd like to take another look at the validation part. To tell the truth I'm not very satisfied in using a specified Logger class for only that purpose (although I'll probably need to have the Context host a message logger of some kind anyway). Anyone else interested?

    How did you implement yours, arborint?

  16. #366
    SitePoint Zealot
    Join Date
    Jul 2005
    Posts
    194
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Yes, I have been using your EventHandler-code that you posted on some Finish (probably) website (http://mureakuha.com/koodikirjasto/791). Anyway, I tried your new code only I happen to have problems with the Handle::resolve() method. It always returns NULL, causing the call_user_func_array() in the new Delegate-class to be failing.

    This is always for example the var_dump of $callback in:

    Code:
    array(2) { [0]=>  &NULL [1]=>  string(8) "checkXhr" }
    Maybe you also changed the Handle-class since the one on the website above?

  17. #367
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by wdeboer
    Maybe you also changed the Handle-class since the one on the website above?
    Oh, indeed. It used to replace the object directly instead of returning it. Here's the modified code:
    PHP Code:
        /**
         * Resolves a Handle; replaces a Handle instance with its identified class
         * @param    object    passed by reference
         * @return    object
         */
        
    static public function resolve(&$handle)
        {
            if (
    $handle instanceof self)
            {
                
    $class $handle->getClass();
                
    $file $handle->getFile();
                
                if (!
    class_exists($class) && !empty($file) && is_readable($file))
                {
                    require_once(
    $file);
                }
                
    $handle call_user_func_array(array(new ReflectionClass($class), 'newInstance'), $handle->getArgs());
            }
            return 
    $handle;
        } 
    Frankly, you oughta been able to tell that by looking at Handle::resolve and the corresponding code in Delegate.

  18. #368
    SitePoint Zealot
    Join Date
    Jul 2005
    Posts
    194
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Uh Uh, it's late

  19. #369
    SitePoint Wizard
    Join Date
    Aug 2004
    Location
    California
    Posts
    1,672
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Ezku
    By the way, as I've come this far in getting the actions working, I'd like to take another look at the validation part. To tell the truth I'm not very satisfied in using a specified Logger class for only that purpose (although I'll probably need to have the Context host a message logger of some kind anyway). Anyone else interested?

    How did you implement yours, arborint?
    I didn't go the Error Logger path as you recall. I created an object for each parameter to hold rules, filters, error messages, form info, etc. Also, I implemented a generic Locator rather than a Context. It's really just a DataSpace for objects. I will try to get my examples working an post the code I have used for a couple of small sites.

    The one thing that might interest you is I split the Response into a root Response class and then a ResponseChild class that you can attach to build a tree. All the headers, redirects, content, render methods get gathered up from the children when you want to output the Response.
    Christopher

  20. #370
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by arborint
    I didn't go the Error Logger path as you recall.
    (If I'm reading you correctly: A slight lapsus linguae on my part. I was referring to your implementation of the validator. If not, ignore this. )

    Now that you say that, I remember not fancying your implementation that much either. Just all too massive for what I have in mind. I seem to have a real problem here - nothing is elegant enough. :/

    I'll probably have to take a peek at Rails to see how it's done there. In the meanwhile, I'd like to hear how you people do it. Links to good resources would be appreciated.

    Quote Originally Posted by arborint
    The one thing that might interest you is I split the Response into a root Response class and then a ResponseChild class that you can attach to build a tree. All the headers, redirects, content, render methods get gathered up from the children when you want to output the Response.
    Do tell us more about that. How is it used in the actions?

  21. #371
    SitePoint Wizard
    Join Date
    Aug 2004
    Location
    California
    Posts
    1,672
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Ezku
    (If I'm reading you correctly: A slight lapsus linguae on my part. I was referring to your implementation of the validator. If not, ignore this. )

    Now that you say that, I remember not fancying your implementation that much either. Just all too massive for what I have in mind. I seem to have a real problem here - nothing is elegant enough. :/
    Yes, you are reading me correctly. As I recall you gathered up the error messages in a Logger class, while I created an object for each parameter that held things like that. To be quite honest I find my implementation quite ugly. It has alway felt like overkill. But I have not found anything better and it makes many things easier. It seems "too massive" in the Input Controller, but for an inherited Form Controller is seems more appropriate because you can throw all kinds of field information in there.

    It really just creates a Value Object for each parameter you are interested in so you have a container to hold all the data for it throught the entire process. So it deals nicely with increasing complexity and keeps everything related to a parameter together. Come up with something better and I will jump ship.
    Quote Originally Posted by Ezku
    Do tell us more about that. How is it used in the actions?
    It's really just the Response class from kyberfabrikken but with children added. The children can contain either content data or be decorated with an object with a render() function. So if your View objects implement a render() method you can just add a child Response to the Response passed to you, then attach the View to the child Respose object and return. The root walks the tree and gathers up the non-null headers and redirects, and the content/render().
    Christopher

  22. #372
    SitePoint Zealot
    Join Date
    Jul 2005
    Posts
    194
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I happen to have a question about how to suppport multiple languages, I am currently using the Controller/View code made by Ezku. This works nice, only I am a bit in the dark how I should support multiple languages. AS you might would expect every language would need his own translated template. Anyone got any idea how I should handle this?

    Because the View::render()-method doesn't know which language is wanted, the current solution I have is that I send a array( 'language' => 'en' ) along with through the $data parameter of render(). Doesn't really look "clean" to me.

    If anyone has any suggestions please let me know

  23. #373
    SitePoint Wizard
    Join Date
    Aug 2004
    Location
    California
    Posts
    1,672
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I'd be interested to know where people have found where it is best to deal with this. Should it be in the Template, View or Response?
    Christopher

  24. #374
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Proberly off topic slightly but I have noted that on one occasion that I had to serve up page layouts dependable on the language, which I feel should be taken into consideration as well

  25. #375
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by wdeboer
    I happen to have a question about how to suppport multiple languages, I am currently using the Controller/View code made by Ezku. This works nice, only I am a bit in the dark how I should support multiple languages. AS you might would expect every language would need his own translated template. Anyone got any idea how I should handle this?
    Glad someone liked it! Well, my intention was to have a Dictionary (yet again just a DataSpace with a name :) in the Context for that purpose, like in Vanilla. If you really need to serve a different template according to the language chosen - well, depends on the template file structure, really, but I'd look into plugging the AppController so that the template root directory is changed automagically.

    Something along the lines of this - sorry, I don't have the code here so I can't check if I'm being consistent :D
    PHP Code:
    class AppController extends PageController
    {
       
    // ...
       
    public function __construct() {
          
    // ...
          
    $this->events->onStartup = new Delegate($this'ApplyLanguageRoot');
       }
       public function 
    ApplyLanguageRoot() {
          
    $language $this->context->language $this->context->language 'en';
          
    $this->root $language.'/'.$this->root;
       }

    Now, if you're getting language-dependant templates in five lines of code, I'd say that's not too bad. :p


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
  •