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);
}