Location: PHPKode > projects > SolarPHP > solar-system-1.1.1/solar/source/solar/Solar/Controller/Front.php
<?php
/**
 * 
 * Front-controller class to find and invoke a page-controller.
 * 
 * An example bootstrap "index.php" for your web root using the front
 * controller ...
 * 
 * {{code: php
 *     require 'Solar.php';
 *     Solar::start();
 *     $front = Solar::factory('Solar_Controller_Front');
 *     $front->display();
 *     Solar::stop();
 * }}
 * 
 * @category Solar
 * 
 * @package Solar_Controller
 * 
 * @author Paul M. Jones <hide@address.com>
 * 
 * @license http://opensource.org/licenses/bsd-license.php BSD
 * 
 * @version $Id: Front.php 4502 2010-03-08 16:20:08Z pmjones $
 * 
 */
class Solar_Controller_Front extends Solar_Base
{
    /**
     * 
     * Default configuration values.
     * 
     * @config array classes Base class names for page controllers.
     * 
     * @config array disable A list of app names that should be disallowed
     * and treated as "not found" if a URI maps to them.
     * 
     * @config string default The default page-name.
     * 
     * @config array routing Key-value pairs explicitly mapping a page-name to
     * a controller class (static mapping).
     * 
     * @config array rewrite Rewrite URIs according to these rules (dynamic
     * mapping).
     * 
     * @config array replace Replacement strings for rewrite rules.
     * 
     * @config bool explain Dump an explanation of the routing path when a
     * page is not found.
     * 
     * @var array
     * 
     */
    protected $_Solar_Controller_Front = array(
        'classes' => array('Solar_App'),
        'disable' => array(),
        'default' => null,
        'routing' => array(),
        'rewrite' => array(),
        'replace' => array(),
        'explain' => false,
    );
    
    /**
     * 
     * The default page name when none is specified.
     * 
     * @var array
     * 
     */
    protected $_default;
    
    /**
     * 
     * A list of page-controller names that should be disallowed; will be
     * treated as actions on the default controller instead.
     * 
     * @var array
     * 
     */
    protected $_disable = array();
    
    /**
     * 
     * The registered Solar_Uri_Rewrite object.
     * 
     * @var Solar_Uri_Rewrite
     * 
     * @see _rewrite()
     * 
     */
    protected $_rewrite = array();
    
    /**
     * 
     * Explicit page-name to controller class mappings.
     * 
     * @var array
     * 
     */
    protected $_routing = array();
    
    /**
     * 
     * The page-name key matched to the routing map, if any.
     * 
     * @var array
     * 
     * @see _getPageClass()
     * 
     */
    protected $_routing_key;
    
    /**
     * 
     * Stack of page-controller class prefixes.
     * 
     * @var Solar_Class_Stack
     * 
     */
    protected $_stack;
    
    /**
     * 
     * An array of explanations of the front-controller processing track.
     * 
     * @var array
     * 
     */
    protected $_explain;
    
    /**
     * 
     * Post-construction tasks to complete object construction.
     * 
     * @return void
     * 
     */
    protected function _postConstruct()
    {
        parent::_postConstruct();
        
        // set string var from config
        if ($this->_config['default']) {
            $this->_default = (string) $this->_config['default'];
        }
        
        // merge array vars from config
        $vars = array('disable', 'routing');
        foreach ($vars as $key) {
            if ($this->_config[$key]) {
                $var = "_$key";
                $this->$var = array_merge(
                    $this->$var,
                    (array) $this->_config[$key]
                );
            }
        }
        
        // set up a class stack for finding apps
        $this->_stack = Solar::factory('Solar_Class_Stack');
        $this->_stack->add($this->_config['classes']);
        
        // retain the registered rewriter
        $this->_rewrite = Solar_Registry::get('rewrite');
        
        // merge our rewrite rules
        if ($this->_config['rewrite']) {
            $this->_rewrite->mergeRules($this->_config['rewrite']);
        }
        
        // merge our rewrite replacement tokens
        if ($this->_config['replace']) {
            $this->_rewrite->mergeReplacements($this->_config['replace']);
        }
        
        // extended setup
        $this->_setup();
    }
    
    /**
     * 
     * Sets up the environment for all pages.
     * 
     * @return void
     * 
     */
    protected function _setup()
    {
    }
    
    /**
     * 
     * Fetches the output of a page/action/info specification URI.
     * 
     * @param Solar_Uri_Action|string $spec An action URI for the front
     * controller.  E.g., 'bookmarks/user/pmjones/php+blog?page=2'. When
     * empty (the default) uses the current URI.
     * 
     * @return string The output of the page action.
     * 
     */
    public function fetch($spec = null)
    {
        if ($spec instanceof Solar_Uri_Action) {
            // a uri object was passed directly
            $uri = $spec;
        } else {
            // spec is a uri string; if empty, uses the current uri
            $uri = Solar::factory('Solar_Uri_Action', array(
                'uri' => (string) $spec,
            ));
        }
        
        $this->_explain['fetch_uri'] = $uri->getFrontPath();
        
        // rewrite the uri path
        $this->_rewrite($uri);
        
        // get the page and class routing
        list($page, $class) = $this->_routing($uri);
        
        // do we have a page-controller class?
        if (! $class) {
            return $this->_notFound($page);
        }
        
        // instantiate the controller class and fetch its content
        $obj = Solar::factory($class);
        $obj->setFrontController($this);
        
        // was this the result of a static routing? if so, force the
        // page controller to use the route name.
        if ($this->_routing_key) {
            $obj->setController($this->_routing_key);
        }
        
        // done, fetch the page-controller results
        return $obj->fetch($uri);
    }
    
    /**
     * 
     * Displays the output of an page/action/info specification URI.
     * 
     * @param string $spec A page/action/info spec for the front
     * controller. E.g., 'bookmarks/user/pmjones/php+blog?page=2'.
     * 
     * @return string The output of the page action.
     * 
     */
    public function display($spec = null)
    {
        echo $this->fetch($spec);
    }
    
    /**
     * 
     * Rewrite the URI path according to a dynamic ruleset.
     * 
     * @param Solar_Uri $uri The URI to rewrite in place.
     * 
     * @return void
     * 
     */
    protected function _rewrite($uri)
    {
        // does it match a rewrite rule?
        $newpath = $this->_rewrite->match($uri);
        if ($newpath) {
            $uri->setPath($newpath);
            $this->_explain['rewrite_rule'] = $this->_rewrite->explain();
            $this->_explain['rewrite_uri'] = $uri->getFrontPath();
        } else {
            $this->_explain['rewrite_rule'] = $this->_rewrite->explain();
        }
    }
    
    /**
     * 
     * Checks the URI to see what page name and controller class we should
     * route to.
     * 
     * @param Solar_Uri $uri The URI to route on.
     * 
     * @return void
     * 
     */
    protected function _routing($uri)
    {
        // first path-element is the page-name.
        $page = strtolower(reset($uri->path));
        if (empty($page)) {
            // page-name is blank. get the default class.
            // remove the empty element from the path.
            $class = $this->_getPageClass($this->_default);
            array_shift($uri->path);
            $this->_explain['routing_page']  = "empty, using default page '{$this->_default}'";
        } elseif (in_array($page, $this->_disable)) {
            // page-name is disabled. get the default class.
            // leave existing elements in the path.
            $class = $this->_getPageClass($this->_default);
            $this->_explain['routing_page'] = "'$page' disabled, using default page '{$this->_default}'";
        } else {
            // look for a controller for the requested page.
            $class = $this->_getPageClass($page);
            if (! $class) {
                // no class for the page-name. get the default class.
                // leave existing elements in the path.
                $class = $this->_getPageClass($this->_default);
                $this->_explain['routing_page'] = "no class for page '$page', using default page '{$this->_default}'";
            } else {
                // found a class. don't need the page-name any more, so
                // remove it from the path.
                array_shift($uri->path);
                $this->_explain['routing_page'] = $page;
            }
        }
        
        $this->_explain['routing_class'] = $class;
        $this->_explain['routing_uri']   = $uri->getFrontPath();
        return array($page, $class);
    }
    
    /**
     * 
     * Finds the page-controller class name from a page name.
     * 
     * @param string $page The page name.
     * 
     * @return string The related page-controller class picked from
     * the routing, or from the list of available classes.  If not found,
     * returns false.
     * 
     */
    protected function _getPageClass($page)
    {
        if (! empty($this->_routing[$page])) {
            // found an explicit route
            $this->_routing_key = $page;
            $class = $this->_routing[$page];
        } else {
            // no explicit route
            $this->_routing_key = null;
            
            // try to find a matching class
            $page = str_replace('-',' ', strtolower($page));
            $page = str_replace(' ', '', ucwords(trim($page)));
            $class = $this->_stack->load($page, false);
        }
        return $class;
    }
    
    /**
     * 
     * Executes when fetch() cannot find a related page-controller class.
     * 
     * Generates an "HTTP 1.1/404 Not Found" status header and returns a
     * short HTML page describing the error.
     * 
     * @param string $page The name of the page not found.
     * 
     * @return string
     * 
     */
    protected function _notFound($page)
    {
        $content[] = "<html><head><title>Not Found</title></head><body>";
        $content[] = "<h1>404 Not Found</h1>";
        $content[] = "<p>"
                   . htmlspecialchars("Page controller class for '$page' not found.")
                   . "</p>";
        
        if ($this->_config['explain']) {
            $content[] = "<h2>Track</h2>";
            $content[] = "<dl>";
            foreach ($this->_explain as $code => $text) {
                $content[] = "<dt><code>{$code}:</code></dt>";
                $content[] = "<dd><code>"
                           . ($text ? htmlspecialchars($text) : "<em>empty</em>")
                           . "</code></dd>";
            }
            $content[] = "</dl>";
            
            $content[] = "<h2>Page Class Prefixes</h2>";
            $content[] = '<ol>';
            foreach ($this->_stack->get() as $class) {
                $content[] = "<li>$class*</li>";
            }
            $content[] = '</ol>';
        }
        
        $content[] = "</body></html>";
        
        $response = Solar_Registry::get('response');
        $response->setStatusCode(404);
        $response->content = implode("\n", $content);
        
        return $response;
    }
}
Return current item: SolarPHP