controller, $this->dba, $this->request, $this->response, $this->models); $this->destroyRegistry(); } /** * Parse the path request variable and derive the * page controller from it, then return an array * of all of the elements in the path (without the * leading controller name). The Path request variable (index.php?path=) will * normally look like a folder path because of .htaccess mod_rewrite * @internal */ private function parsePath() { // Get the path and then split it up by the /'s // P.S. explode() does not work in this scenario // because explode() will get empty elements. $path = $this->request['path']; $path = preg_split('~/~', $path, -1, PREG_SPLIT_NO_EMPTY); // Shift off elements of the array if directories // in the controller dir exist for this path. $i = -1; while(isset($path[++$i])) { if(is_dir($this->controller_dir . $path[$i])) { $this->controller_dir .= array_shift($path) . '/'; } else { break; } } // Figure out what controller function should be // used. $page = empty($path) ? 'index' : array_shift($path); // Set the info. $this->page = $page; $this->path = $path; if(!empty($path)) { $this->action = $path[0]; } } /** * Create a connection to a database. This will first find the * database abstraction layer (eg: MySQLi, SQLite) using the Layers * class in the /layers/database/ directory, then using config data * try to connect to it. * @see Layers, Database * @internal */ private function connectToDatabase() { if(DBA_DRIVER != '') { $driver = strtolower(DBA_DRIVER); try { lazy_load(VK_DIR .'/database/database.php'); lazy_load(VK_DIR .'/database/drivers/'. $driver .'/driver.php'); lazy_load(VK_DIR .'/database/drivers/'. $driver .'/result.php'); lazy_load(VK_DIR .'/database/drivers/'. $driver .'/transaction.php'); lazy_load(VK_DIR .'/database/drivers/'. $driver .'/table.php'); } catch(LazyLoadException $e) { throw new ControllerException("Failed to load the database driver. Please check that it exists."); } $driver = ucfirst($driver) .'Driver'; // load the database into the registry $this->dba = new $driver(DBA_HOST, DBA_DATABASE, DBA_USER, DBA_PASS); } } /** * Parse the routes and figure out which one is the * one we're on. Routes are defined in PageController::$routes * in the form of /controller/function/(int)var/(str)var/(bool)var/(float)var * where all of the 'var' elements are the different types of variables * that can be passed in. Each 'var' is added as a key to the Request reference * object as if it were a $_REQUEST variable, where its value is the corresponding * value in the path (url) itself. * @see Request, PageController * @internal */ private function parseRoutes() { // Get the routes defined in the controllers and // setup some other variables while at it. $routes = $this->controller->routes; $matched = FALSE; // If there are parts to the path... if(!empty($this->path) && isset($routes[$this->path[0]])) { $vars = array(); $rparts = preg_split("~/~", $routes[$this->path[0]], -1, PREG_SPLIT_NO_EMPTY); $i = 0; while(isset($rparts[$i]) && isset($this->path[$i])) { $ppart = $this->path[$i]; $rpart = $rparts[$i]; if($rpart{0} == '(') { $type = ''; if(FALSE === ($ii = strpos($rpart, ')'))) { throw new ControllerException("Invalid route: missing closing ')'"); } $type = substr($rpart, 1, $ii - 1); $var = substr($rpart, $ii + 1); $callback = "rt_$type"; $vars[$var] = $callback($ppart); } ++$i; } $this->action = $this->path[0]; $this->vars = $vars; $matched = TRUE; } // add the variables that we found to the Request object $this->request->setArray($this->vars); } /** * Try to find, include, and instanciate a PageController class for this action. * @see PageController * @internal */ private function loadPageController($rec = FALSE) { $filename = $this->controller_dir . $this->page . '.php'; if(!file_exists($filename) || !is_readable($filename)) { // we'll try one last time with the index controller // and the $this->page being the action if(!$rec) { $this->action = $this->page; $this->page = 'index'; array_unshift($this->path, $this->action); return $this->loadPageController(TRUE); } else { $this->setNotFound(); } } require_once $filename; $class = camelize($this->page) . 'Controller'; if(!class_exists($class, FALSE)) { $this->setNotFound(); } $controller = new $class; //($this->dba, $this->request, $this->response); if(!$controller instanceof PageController) { $this->setNotFound(); } // reset for the default action if it's different if($this->action === 'index') { $this->action = $controller->default_action; } // more advanced stuff, first check if the action is callable if(!is_callable(array($controller, $this->action))) { $this->setNotFound(); // action is callable, does the method exists or are we using __call? } else { // callable, must be using __call, assume index route if(!method_exists($controller, $this->action)) { array_unshift($this->path, '*'); } } $this->controller = $controller; } /** * Now that the PageController has been loaded, run the appropriate action * function in it. The default action is 'index'. Pass a reference to the * Request and Response objects as the two action function arguments. * @see Request, Response * @internal */ private function runPageController() { if(!is_callable(array($this->controller, $this->action))) { throw new ControllerException("No controller function present for the action: {$this->action}", E_USER_ERROR); } call_user_func_array(array($this->controller, $this->action), array($this->request, $this->response)); } /** * Run any filters in the page controller. * @internal */ private function runFilters($which) { // get the filters $filters = $this->controller->$which; // loop the filters foreach($filters as $f) { try { lazy_load(VK_APP_DIR .'/filters/'. $f .'.php'); $class = camelize($f) .'Filter'; // can depend on parent catch ;) if(!class_exists($class, FALSE)) { throw new LazyLoadException; } // run the filter $filter = new $class($this->dba, $this->request, $this->response); $filter->run($this->request, $this->response); // not a pre/post filter, lets see if we're trying to load a library as a filter } catch(LazyLoadException $e) { // try to load up the library without cloning it try { $this->load($f, FALSE); // so, the library doesn't exist either } catch(LazyLoadException $e) { throw new ControllerException("Could not load filter/library [{$f}], please make sure that it exists."); } } } } /** * Call most of the ApplicationControllers functions to bring everything together * and run the action. */ public function run() { try { $this->connectToDatabase(); echo $this->page .' '. $this->action .'
'; $this->parsePath(); echo $this->page .' '. $this->action .'
'; $this->loadPageController(); echo $this->page .' '. $this->action .'
'; $this->parseRoutes(); echo $this->page .' '. $this->action .'
'; $this->runFilters('pre_filters'); $this->runPageController(); echo $this->page .' '. $this->action .'
'; $this->runFilters('post_filters'); $this->response->render($this->dba, $this->request); } catch(Exception $e) { fatal_error($e); } } /** * If the action was not found in the PageController, then a custom * 404 error page will be displayed. * @see ApplicationController::loadPageController() * @internal */ public function setNotFound() { header('Status: 404 Not Found'); $this->response->getFile('/errors/error_404.html'); exit; } } /** * The page controller represents a namespace of actions available * to users. The default action in a page controller will be PageController::index. * To pass variables to these actions, see PageController::$routes and * ApplicationController::parseRoutes(). The PageController has a "registry" that is * simply PageController::$registry and can be accessed via object overloading * with Overload. * @see Overload * @author Peter Goodman */ abstract class PageController extends Registry { /** * The datasource we are currently using. */ private $datasource = 'database'; /** * Get a PageControllers custom routes. If no custom routes exist (custom routes being * ones that include either special variables and/or more path elements after the action * name, eg: /controller_name/action_name/custom/custom/) * @see ApplicationController::parseRoutes() */ public $routes = array(); /** * Get an array of filters to execute before a controller's action is called. */ public $pre_filters = array(); /** * Get an array of filters to execute after a controller's action is called. */ public $post_filters = array(); /** * The default action to call for a controller. Normally it's 'index' but if any page controller * redefines this method, it can be changed to anything. * @return string */ public $default_action = 'index'; /** * PageController constructor, take in the database, request, response (template) and * layers reference objects. * @param mixed $dba A reference to the DatabaseDriver object. * @param mixed $request A reference to the Request object. * @param mixed $response A reference to the Response object. * @param mixed $layers A reference to the Layers object. * @see Database, Request, Response, Layers * @internal */ // DatabaseDriver $dba, Request $request, Response $response final public function __construct() { /** * @todo Find a nicer solution to loading different models. */ try { lazy_load(VK_DIR .'/models/database/model.php'); } catch(LazyLoadException $e) { throw new ControllerException("Could not load database model. Please make sure it exists."); } $this->models = array(); $this->models['database'] = new DatabaseModel; } /** */ final public function getFinder($name = '') { return $this->models[$this->datasource]->getFinder($name); } /** * Change the datasource, eg: database->authorize.net/google/whatever. */ final public function setDatasource($ds = 'database') { if(!isset($this->models[$ds])) { lazy_load(VK_DIR .'/models/'. $ds .'/model.php'); $class = camelize($ds) .'Model'; $this->models[$ds] = new $class; } $this->datasource = $ds; } /** * If the action was not found in the PageController, then a custom * 404 error page will be displayed. * @see ApplicationController::loadPageController() * @internal */ public function setNotFound() { header('Status: 404 Not Found'); $this->response->getFile('/errors/error_404.html'); exit; } } /** * Controllers for components. */ abstract class ComponentController extends PageController { abstract public function run(Request $request, Response $response); } /** * Controllers for filters. */ abstract class FilterController extends PageController { abstract public function run(Request $request, Response $response); }