pluf2

pluf2 Git Source Tree


Root/src/Pluf/Dispatcher.php

<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of Plume Framework, a simple PHP Application Framework.
# Copyright (C) 2001-2007 Loic d'Anterroches and contributors.
#
# Plume Framework is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Plume Framework is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
# ***** END LICENSE BLOCK ***** */

class Pluf_Dispatcher
{
    /**
     * The unique method to call.
     *
     * @param string Query string ('')
     */
    public static function dispatch($query='')
    {
		$query = preg_replace('#^(/)+#', '/', '/'.$query);
        $req = new Pluf_HTTP_Request($query);
        $middleware = array();
        foreach (Pluf::f('middleware_classes', array()) as $mw) {
            $middleware[] = new $mw();
        }
        $skip = false;
        foreach ($middleware as $mw) {
            if (method_exists($mw, 'process_request')) {
                $response = $mw->process_request($req);
                if ($response !== false) {
                    // $response is a response
                    $response->render($req->method != 'HEAD' and !defined('IN_UNIT_TESTS'));
                    $skip = true;
                    break;
                }    
            }
        }
        if ($skip === false) {   
            $response = self::match($req);
            if (!empty($req->response_vary_on)) {
                $response->headers['Vary'] = $req->response_vary_on;
            }
            $middleware = array_reverse($middleware);
            foreach ($middleware as $mw) {
                if (method_exists($mw, 'process_response')) {
                    $response = $mw->process_response($req, $response);
                }    
            }
            $response->render($req->method != 'HEAD' and !defined('IN_UNIT_TESTS'));
        }
        return array($req, $response);
    }

    /**
     * Match a query against the actions controllers.
     *
     * @param Pluf_HTTP_Request Request object
     * @return Pluf_HTTP_Response Response object
     */
    public static function match($req)
    {
        // Order the controllers by priority
        foreach ($GLOBALS['_PX_views'] as $key => $control) {
            $priority[$key] = $control['priority'];
        }
        array_multisort($priority, SORT_ASC, $GLOBALS['_PX_views']);
        foreach ($GLOBALS['_PX_views'] as $key => $ctl) {
            $match = array();
            if (preg_match($ctl['regex'], $req->query, $match)) {
                try {
                    $req->view = $ctl;
                    $m = new $ctl['model']();
                    if (isset($m->{$ctl['method'].'_precond'})) {
                        // Here we have preconditions to respects. If
                        // the "answer" is true, then ok go ahead, if
                        // not then it a response so return it or an
                        // exception so let it go.
                        $preconds = $m->{$ctl['method'].'_precond'};
                        if (!is_array($preconds)) {
                            $preconds = array($preconds);
                        }
                        foreach ($preconds as $precond) {
                            $res = call_user_func(explode('::', $precond), $req);
                            if ($res !== true) {
                                return $res;
                            }
                        } 
                    }
                    if (!isset($ctl['params'])) {
                        return $m->$ctl['method']($req, $match);
                    } else {
                        return $m->$ctl['method']($req, $match, $ctl['params']);
                    }
                } catch (Pluf_HTTP_Error404 $e) {
                    // Need to add a 404 error handler
                    // something like Pluf::f('404_handler', 'class::method')
                } catch (Exception $e) {
                    if (Pluf::f('debug', false) == true) {
                        return new Pluf_HTTP_Response_ServerErrorDebug($e);
                    } else {
                        return new Pluf_HTTP_Response_ServerError($e);
                    }
                }
            }
        }
        return new Pluf_HTTP_Response_NotFound(sprintf(__('The page <em>%s</em> was not found on the server.'), htmlspecialchars($req->query)));
    }

    /**
     * Load the controllers.
     *
     * @param string File including the views.
     * @param string Possible prefix to add to the views.
     * @return bool Success.
     */
    public static function loadControllers($file, $prefix='')
    {
        if (file_exists($file)) {
            if ($prefix == '') {
                $GLOBALS['_PX_views'] = include $file;
            } else {
                $GLOBALS['_PX_views'] = Pluf_Dispatcher::addPrefixToViewFile($prefix, $file);
            }
            return true;
        }
        return false;
	}


    /**
     * Register an action controller.
     *
     * - The class must provide a "standalone" action method
     * class::actionmethod($request, $match)
     * - The priority is to order the controller matches. 
     * 5: Default, if the controller provides some content
     * 1: If the controller provides a control before, without providing
     * content, note that in this case the return code must be a redirection.
     * 8: If the controller is providing a catch all case to replace the
     * default 404 error page.
     *
     * @param string Class name providing the action controller
     * @param string The method of the plugin to be called
     * @param string Regex to match on the query string
     * @param int Priority (5)
     * @return void
     */
    public static function registerController($model, $method, $regex, $priority=5)
    {
        if (!isset($GLOBALS['_PX_views'])) {
            $GLOBALS['_PX_views'] = array();
        }
        $GLOBALS['_PX_views'][] = array('model' => $model,
                                        'regex' => $regex,
                                        'priority' => $priority,
                                        'method' => $method);
    }

    /**
     * Add the controllers of an application with a given prefix.
     *
     * Suppose you have a new app you want to use within another
     * existing application, you may need to change the base URL not
     * to conflict with the existing one. For example you want to have
     * domain.com/forum-a/ and domain.com/forum-b/ to use 2 forums at
     * the same time. 
     *
     * This method do that, it takes a typical "view" file and rewrite
     * the regex to append the prefix. Note that you should use the
     * 'url' tag in the template and use Pluf_HTTP_URL_reverse in the
     * views to not hardcode the urls or this will not work.
     *
     * @param string Prefix, for example '/alternate'.
     * @param string File with the views.
     * @return array Prefixed views.
     */
    static public function addPrefixToViewFile($prefix, $file)
    {
        if (file_exists($file)) {
            $views = include $file;
        } else {
            throw new Exception('View file not found: '.$file);
        }
        return Pluf_Dispatcher::addPrefixToViews($prefix, $views);
    }

    /**
     * Add a prefix to an array of views. 
     *
     * You can use it for example to not hardcode that in your CMS the
     * blog is located as /blog but is configured in the configuration
     * of the CMS, that way in French this could be /carnet.
     *
     * @param string Prefix, for example '/alternate'.
     * @param array Array of the views.
     * @return array Prefixed views.
     */
    static public function addPrefixToViews($prefix, $views)
    {
        $res = array();
        foreach ($views as $view) {
            $view['regex'] = '#^'.$prefix.substr($view['regex'], 2);
            $res[] = $view;
        }
        return $res;
    }
}

Archive Download this file

Branches

Number of commits:
Page rendered in 0.08632s using 11 queries.