* @copyright Copyright (c) 2008-9 TypeOneError Studios (http://www.typeoneerror.com) * @license MIT ~ http://www.opensource.org/licenses/mit-license.php * @version $Id$ * @link http://typeoneerror.com/asra * @category Asra * @package Core */ /** * @see Asra_Constants_Lang */ require_once "Asra/Constants/Lang.php"; /** * @see Asra_Core_Loader */ require_once "Asra/Core/Loader.php"; /** * @see Asra_Utils_Arrays */ require_once "Asra/Utils/Arrays.php"; /** * @see Asra_Utils_String */ require_once "Asra/Utils/Strings.php"; /** * Bootstrap.php * * Handles parsing the url and initializing the * proper classes and api functionality. * * @todo add additional params to config * @todo autoload config and settings? * * @author Ben Borowski * @copyright Copyright (c) 2008-9 TypeOneError Studios (http://www.typeoneerror.com) * @license MIT ~ http://www.opensource.org/licenses/mit-license.php * @link http://typeoneerror.com/asra * @category Asra * @package Core */ class Asra_Core_Bootstrap { //--------------------------------------- // PRIVATE VARIABLES //--------------------------------------- /** * Path to the application directory that stores * the "cache", "controllers", and "log" directories. * * @var string */ private $__applicationDirectory; /** * Query param used in .htaccess (gets rewritten to index.php?asra_url=). * * @var string */ private $__asraUrl = Asra_Constants_Lang::ASRA_URL; /** * Path to file cache directory. * * @var string */ private $__cacheDirectory; /** * Switch to activate caching or not. * * @var bool */ private $__cacheOutput = false; /** * Path to controller directory. * * @var string */ private $__controllerDirectory; /** * Default method to call in controller if one isn't defined. * * @var string */ private $__defaultMethod = Asra_Constants_Lang::DEFAULT_METHOD; /** * Turn on full error reporting if true. Don't use this in a * production environment. * * @var bool */ private $__fullErrorReporting = false; /** * Path to log directory. * * @var string */ private $__logDirectory; /** * Output queries to /log/trace.log ... be careful! * Not to be used in production site. * * @var bool */ private $__logQueries = false; /** * If this is set to true, the developer must create a * controller to access data. This is HIGHLY recommended. * * @var bool */ private $__requireController = true; /** * Default timezone. * * @var string */ private $__timezone = Asra_Constants_Lang::TIMEZONE; //--------------------------------------- // PROTECTED VARIABLES //--------------------------------------- /** * Application controller * * @var Asra_Core_Api */ protected $_api; /** * Saved configuration object * * @var object */ protected $_config; /** * URL delimiters for words and actions. For example, both * "sample-table" and "sample.table" map to a controller called * "SampleTable" by default. * * @var array */ protected $_delimiters = array("-", "."); /** * Store errors generated by the system. * * @var array */ protected $_errors = array(); /** * Stored parts of the URL once split. * * @var array */ protected $_parts; /** * Save a few application settings. * * @var object */ protected $_settings; /** * Saved full access path. * * @var string */ protected $_url; //--------------------------------------- // CONSTRUCTOR //--------------------------------------- /** * Constructor function, calls initialization method. * * @return Asra_Core_Bootstrap */ public function __construct() { // Arrays::print_array($_SERVER); $this->__registerAutoLoad(); return $this; } //--------------------------------------- // METHODS //--------------------------------------- /** * Dispatch call; initializes api and begins output generation. * * @return Asra_Core_Bootstap */ public function dispatch() { $this->_dispatch(); return $this; } /** * Dispatch call, initializes api and begins output generation. * * @return void */ protected function _dispatch() { // -- errors and parsing errors if ($this->__fullErrorReporting) { error_reporting(E_ALL); set_error_handler(array($this, "errorHandler"), E_ALL); } else { error_reporting(E_ERROR | E_PARSE); set_error_handler(array($this, "errorHandler"), E_ERROR | E_PARSE); } // -- set default timezone date_default_timezone_set($this->__timezone); // -- set url params if (isset($_GET[$this->__asraUrl])) { if (strlen($_GET[$this->__asraUrl])) { $this->_url = trim($_GET[$this->__asraUrl]); $this->__parseURL(); } } // -- initialize the api $this->_api = Asra_Core_Api::getInstance(); // -- apply some settings to the api class $this->_api->setCacheOutput($this->__cacheOutput); // -- parse the url further to get type if (count($this->_parts)) { $type = strtolower(trim($this->_parts[0])); $this->_api->setExportType($type); if ($this->_api->getExportType() == $type) { $this->_settings->type = $type; array_shift($this->_parts); } else { $this->_api->setExportType(Asra_Export_Registrar::$defaultType); } // -- set the cache path $cachePath = trim(implode(".", $this->_parts), "."); //if (!strlen($cachePath)) $cachePath = "root"; $this->_api->cachePath = $cachePath; } // -- initialize the api $this->_api->init($this->getOption('debug'), $this->_config); // -- if no paths are set show api info if(!count($this->_parts)) { $this->_api->setExportType(Asra_Export_Registrar::$defaultType); $this->_doTheRoot(); return; } // -- grab the class name based on the controller url $classFile = strtolower($this->_parts[0]); // -- find the class name based on the file name $className = $this->_formatControllerName($classFile); // -- grab the class name based on the controller url $classFile = Asra_Utils_Strings::underscore($className); // -- parse the methods/params if (!isset($this->_parts[1]) || empty($this->_parts[1])) { // -- use default method $method = $this->__defaultMethod; $params = null; if (isset($this->_parts[1])) unset($this->_parts[1]); } else { $method = $this->_formatMethodName($this->_parts[1]); $params = array_slice($this->_parts, 2); } /** * if application dir was not set, set it to "app" * in same directory as the index file */ if (!$this->getApplicationDirectory() && !$this->getControllerDirectory()) { //$file = $_SERVER['DOCUMENT_ROOT']; $file = getcwd(); $this->setApplicationDirectory($file . "/app"); } if (!$this->getControllerDirectory()) { $controllers = $this->getApplicationDirectory() . 'controllers'; } else { $controllers = $this->getControllerDirectory(); } if (!file_exists($controllers) || !is_dir($controllers)) { require_once 'Asra/Core/Exception.php'; throw new Asra_Core_Exception("Controller directory expected in {$controllers}"); } else { $this->setControllerDirectory($controllers); } if ($this->getCacheDirectory()) { $this->_api->setCacheDir($this->getCacheDirectory()); } else { if ($this->__cacheOutput) { require_once 'Asra/Core/Exception.php'; throw new Asra_Core_Exception("Cache directory was not specified with Bootstrap::setCacheDirectory() but attempt to turn caching on was made"); } } if ($this->getLogDirectory()) { $this->_api->setLogDir($this->getLogDirectory()); $this->_api->setLogQueries($this->__logQueries); } else { if ($this->__logQueries) { require_once 'Asra/Core/Exception.php'; throw new Asra_Core_Exception("Log directory was not specified with Bootstrap::setLogDirectory() but attempt to turn logging on was made"); } } // -- where is that file? $file = $this->getControllerDirectory() . $className . '.php'; //echo "className = {$className}
"; //echo "method = {$method}
"; //echo "file = {$file}
"; // -- did user create a controller? if (file_exists($file)) { // -- include and init the class require $file; // -- make sure the class was named correctly if (class_exists($className)) { $controller = new $className(); $controller->setAPI($this->_api); // -- check for a method call and call it if it's there if (method_exists($controller, $method)) { $classMethods = get_class_methods($controller); $classVars = get_object_vars($controller); if (is_callable(array($controller, $method))) { call_user_func_array(array($controller, $method), $params); } else { $this->_api->error("Method `{$method}` can not be called in `{$className}` class", Asra_Constants_Lang::ERR_CLASS_METHOD); } } else { $this->_api->error("Method `{$method}` was not defined in `{$className}` class", Asra_Constants_Lang::ERR_CLASS_METHOD); } } else { $this->_api->error("Controller class `{$className}` was not defined in `{$file}`", Asra_Constants_Lang::ERR_CLASS_DEF); } } else { // -- eh, try to dump it if (!$this->__requireController) { $tableApprox = Asra_Utils_Strings::underscore($className); $this->_api->dump($tableApprox); } else { $this->_api->error("Expecting controller class named `{$className}` to be defined in `{$file}`", Asra_Constants_Lang::ERR_REQUIRE_CONTROLLER); } } } /** * Show some system info if the root of the api is reached. * * @todo Should probably add a switch for disabling system info display. * * @return void */ protected function _doTheRoot() { $type = $this->_api->getExportType(); $array = array(); $array[] = array( 'msg' => Asra_Constants_Lang::WELCOME, 'version' => Asra_Constants_Lang::VERSION, 'type' => $type, ); //Asra_Utils_Arrays::pr($array); $this->_api->printOut($array); } /** * Prints a blank result set for errors. * * @return void */ protected function _emptyExport() { $this->_api->printOut(array()); } /** * Capture errors for display in api. * * @param int $errno Error code. * @param string $errstr The error message. * @param string $errfile File the error occurred in. * @param int $errline The line the error occurred on. * @return void */ public function errorHandler($errno, $errstr, $errfile, $errline) { $this->_errors[] = array( 'errno' => $errno, 'errstr' => $errstr, 'errfile' => $errfile, 'errline' => $errline, ); if (!is_null($this->_api)) $this->_api->printOut($this->_errors); else Asra_Utils_Arrays::pr($this->_errors); exit(); } /** * Formats the controller name. * *

For example, $sample_table becomes $SampleTable

* * @param string $name Name to format * @return string The formatted controller name */ protected function _formatControllerName($name) { $name = Asra_Utils_Strings::camelize($name); return $name; } /** * Formats the method name. * *

For example, "method.name" and "method-name" become "methodName"

* * @param string $name Name to format * @return string The formatted method name */ protected function _formatMethodName($name) { $name = Asra_Utils_Strings::camelize($name, false); return $name; } /** * Return path to application directory * * @return string */ public function getApplicationDirectory() { return $this->__applicationDirectory; } /** * Return the ASRA url used in the htaccess file. * * @return string */ public function getAsraUrl() { return $this->__asraUrl; } /** * Return path to cache directory. * * @return string */ public function getCacheDirectory() { return $this->__cacheDirectory; } /** * Is caching on? * * @return bool True if caching is enabled. */ public function getCacheOutput() { return $this->__cacheOutput; } /** * Return path to controller directory. * * @return string */ public function getControllerDirectory() { return $this->__controllerDirectory; } /** * Return the default controller method. * * @return string */ public function getDefaultMethod() { return $this->__defaultMethod; } /** * Is Error reporting on? * * @return bool */ public function getFullErrorReporting() { return $this->__fullErrorReporting; } /** * Return path to log directory. * * @return string */ public function getLogDirectory() { return $this->__logDirectory; } /** * Is logging enabled? * * @return bool */ public function getLogQueries() { return $this->__logQueries; } /** * Get a setting for this app. * * @return mixed */ public function getOption($key) { if (isset($this->_settings->$key)) { return $this->_settings->$key; } return null; } /** * Is a controller required to output results. * * @return bool */ public function getRequireController() { return $this->__requireController; } /** * Return the current timezone. * * @return string */ public function getTimezone() { return $this->__timezone; } /** * Return the final url. * * @return string */ public function getUrl() { return $this->_url; } /** * Load a database configuration file. This file is grouped * settings per server/environment. Primarily it is used for * database configuration at a minimum. * * @param string $file Path to the file * @return array|null Config array or null if load fails */ public function loadConfig($file) { if (empty($file) || !is_string($file) || !strlen($file)) { throw new InvalidArgumentException("Config file does not appear to be a valid path."); } $this->_config = Asra_Core_Loader::loadConfig($file, "Database configuration"); return $this->_config; } /** * Load settings configuration file. * * @param string $file Path to file * @return object|null Config array or null if load fails */ public function loadSettings($file) { if (empty($file) || !is_string($file) || !strlen($file)) { throw new InvalidArgumentException(); } $this->_settings = (object) Asra_Core_Loader::loadConfig($file, "Settings configuration"); $this->__parseSettings(); return $this->_settings; } /** * Parse the settings file loaded and apply them to the Bootstrap. * *

* Each setting in the .ini file gets mapped to a function of similar * name in the Bootstrap. A method called setMyVariable() would use a * parameter called "my_variable" in the .ini file. *

* * @return void */ private function __parseSettings() { if ($this->_settings) { $settings = $this->_settings; foreach ($settings as $setting => $value) { $method = 'set' . Asra_Utils_Strings::camelize($setting, true); if (method_exists($this, $method)) { if (is_callable(array($this, $method))) { call_user_func_array(array($this, $method), array($value)); } else { require_once "Asra/Core/Exception.php"; throw new Asra_Core_Exception("{$method} is not a callable setting"); } } else { require_once "Asra/Core/Exception.php"; throw new Asra_Core_Exception("{$method} is not a legal setting"); } } } } /** * Parses the url into parts * * @return Asra_Core_Bootstrap */ private function __parseURL() { //if (substr($this->_url, -1) == '/') $this->_url = substr($this->_url, 0, -1); rtrim($this->_url, "/"); $this->_parts = explode('/', $this->_url); if (strtolower($this->_parts[0]) == 'debug') { $this->_settings->debug = true; array_shift($this->_parts); } $i = 0; while ($i < count($this->_parts)) { if (empty($this->_parts[$i])) { unset($this->_parts[$i]); } $i++; } return $this; } /** * Begin auto load of classes. * * @return Asra_Core_Bootstrap */ private function __registerAutoLoad() { Asra_Core_Loader::init(); return $this; } /** * Set the application root directory. * *

* If other directory setting have not been set, it will also default * those to their constant settings. *

* * @return Asra_Core_Bootstrap */ public function setApplicationDirectory($value) { $app = Asra_Utils_Strings::addTrailingSlash($value); if (!file_exists($app) || !is_dir($app)) { require_once 'Asra/Core/Exception.php'; throw new Asra_Core_Exception("App directory expected in {$app}"); } $this->__applicationDirectory = $app; if (is_null($this->__controllerDirectory)) { $this->setControllerDirectory($this->__applicationDirectory . Asra_Constants_Lang::CONTROLLERS); } if (is_null($this->__cacheDirectory)) { $this->setCacheDirectory($this->__applicationDirectory . Asra_Constants_Lang::CACHE); } if (is_null($this->__logDirectory)) { $this->setLogDirectory($this->__applicationDirectory . Asra_Constants_Lang::LOG); } return $this; } /** * Set the get url used in htaccess. * * @param string $value Name of parameter used in the get url. * @return Asra_Core_Bootstrap */ public function setAsraUrl($value) { if (is_string($value) && strlen($value)) { $this->__asraUrl = $value; } return $this; } /** * Set the cache directory. * * @param string $value A full directory path. * @return Asra_Core_Bootstrap */ public function setCacheDirectory($value) { if (is_string($value) && strlen($value)) { $this->__cacheDirectory = Asra_Utils_Strings::addTrailingSlash($value); } return $this; } /** * Enable caching or not. * * @param bool $value * @return Asra_Core_Bootstrap */ public function setCacheOutput($value) { if (!is_bool($value)) { $value = (bool) $value; } $this->__cacheOutput = $value; return $this; } /** * Set the controller directory. * * @param string $value The path to the controller directory. * @return Asra_Core_Bootstrap */ public function setControllerDirectory($value) { if (is_string($value) && strlen($value)) { $this->__controllerDirectory = Asra_Utils_Strings::addTrailingSlash($value); } return $this; } /** * Set the default method name to call if no action is specfied in the url. * * @param string $value Default method name. * @return Asra_Core_Bootstrap */ public function setDefaultMethod($value) { if (is_string($value) && strlen($value)) { $this->__defaultMethod = $value; } return $this; } /** * Toggle full error reporting. * * @param bool $value Active state of error reporting. * @return Asra_Core_Bootstrap */ public function setFullErrorReporting($value) { if (!is_bool($value)) { $value = (bool) $value; } $this->__fullErrorReporting = $value; return $this; } /** * Set the log directory. * * @param string $value A full directory path. * @return Asra_Core_Bootstrap */ public function setLogDirectory($value) { if (is_string($value) && strlen($value)) { $this->__logDirectory = $value; } return $this; } /** * Toggle query logging. * * @param bool $value * @return Asra_Core_Bootstrap */ public function setLogQueries($value) { if (!is_bool($value)) { $value = (bool) $value; } $this->__logQueries = $value; return $this; } /** * Toggle require controller setting. * *

* If this is true then the developer must * create controllers to access database. This * is HIGHLY recommended. *

* * @param bool $value * @return Asra_Core_Bootstrap */ public function setRequireController($value) { if (!is_bool($value)) { $value = (bool) $value; } $this->__requireController = $value; return $this; } /** * Set the timezone. * * @param string $value * @return Asra_Core_Bootstrap */ public function setTimezone($value) { if (is_string($value) && strlen($value)) { $this->__timezone = $value; } return $this; } }