├── Library ├── Core │ ├── Config.php │ ├── Controller.php │ ├── Database.php │ ├── Dispatcher.php │ ├── Event.php │ ├── Format.php │ ├── Front.php │ ├── Model.php │ ├── Notice.php │ ├── Profiler.php │ ├── Request.php │ ├── Route.php │ ├── Router.php │ ├── Store.php │ ├── Store │ │ ├── Apc.php │ │ ├── Cookie.php │ │ ├── File.php │ │ ├── Memcache.php │ │ ├── Request.php │ │ ├── Session.php │ │ └── StorageInterface.php │ ├── Validate.php │ ├── View.php │ └── ViewHelper.php ├── MyProject │ ├── Cache │ │ └── .gitignore │ ├── Controller │ │ ├── Error.php │ │ └── Index.php │ ├── EventListener.php │ ├── Layout │ │ └── default.phtml │ ├── Model │ │ └── User.php │ ├── View │ │ ├── Helper │ │ │ ├── Profiler.php │ │ │ ├── Route.php │ │ │ ├── Safe.php │ │ │ ├── Test.php │ │ │ └── Url.php │ │ ├── Partial │ │ │ ├── Profiler.phtml │ │ │ ├── ProfilerItem.phtml │ │ │ └── test.phtml │ │ └── Script │ │ │ ├── Error │ │ │ └── notFound.phtml │ │ │ └── Index │ │ │ ├── error.phtml │ │ │ └── index.phtml │ └── config.ini ├── autoloader.php └── global.php ├── README.md ├── Tests ├── ConfigTest.php ├── FormatTest.php ├── ModelTest.php ├── ProfilerTest.php ├── RequestTest.php ├── RouterTest.php ├── StoreTest.php ├── ValidateTest.php ├── ViewHelperTest.php └── ViewTest.php ├── Web ├── .htaccess ├── assets │ ├── css │ │ └── styles.css │ ├── img │ │ ├── profiler-bg.png │ │ └── profiler.png │ └── js │ │ └── scripts.js └── index.php ├── composer.json └── mit-licence.txt /Library/Core/Config.php: -------------------------------------------------------------------------------- 1 | 13 | * Core\Config::set('foo', 'Hello World!'); 14 | * echo Core\Config::get('foo'); 15 | * 16 | * 17 | * @copyright Copyright (c) 2012-2013 Christopher Hill 18 | * @license http://www.opensource.org/licenses/mit-license.php The MIT License 19 | * @author Christopher Hill 20 | * @package MVC 21 | */ 22 | class Config 23 | { 24 | /** 25 | * The holder for all of the config variables. 26 | * 27 | * @access private 28 | * @var array 29 | * @static 30 | */ 31 | private static $_store = array(); 32 | 33 | /** 34 | * Load the applications config file. 35 | * 36 | * @access public 37 | * @param string $projectName The project that we are working with. 38 | * @static 39 | */ 40 | public static function load($projectName) { 41 | self::$_store = parse_ini_file( 42 | dirname(__FILE__) . '/../' . $projectName . '/config.ini', // Inside the users application 43 | true // Parse into sections 44 | ); 45 | } 46 | 47 | /** 48 | * Set a config variable. 49 | * 50 | * We do not want to overwrite configurations by mistake, so if a config 51 | * variable exists then by default we will throw an exception. The user 52 | * can force the overwrite if the really want to. 53 | * 54 | * @access public 55 | * @param string $section The section to write to 56 | * @param string $variable The config to write to. 57 | * @param mixed $value The value that we wish to give the config variable. 58 | * @param boolean $forceOverwrite Whether we want to overwrite existing config variables. 59 | * @return mixed True if success, Exception if error. 60 | * @throws Exception If the user is overwriting config variable without forcing. 61 | * @static 62 | */ 63 | public static function set($section, $variable, $value, $forceOverwrite = false) { 64 | if (isset(self::$_store[$section][$variable]) && ! $forceOverwrite) { 65 | throw new \Exception("{$variable} already exists, cannot overwrite. " 66 | . "To force this, please pass the boolean true as the forth parameter."); 67 | } 68 | 69 | self::$_store[$section][$variable] = $value; 70 | return true; 71 | } 72 | 73 | /** 74 | * Get a config variable. 75 | * 76 | * If the config variable does not exist then the $default is returned. 77 | * 78 | * @param string $section The section to search in. 79 | * @param string $variable The config to return. 80 | * @param mixed $default Return this if the config variable is not found. 81 | * @return mixed 82 | * @static 83 | */ 84 | public static function get($section, $variable, $default = null) { 85 | return isset(self::$_store[$section][$variable]) 86 | ? self::$_store[$section][$variable] 87 | : $default; 88 | } 89 | 90 | /** 91 | * Remove a variable from the config. 92 | * 93 | * @access public 94 | * @param string $section The section to write to 95 | * @param string $variable The config to write to. 96 | * @return boolean 97 | * @static 98 | */ 99 | public static function remove($section, $variable) { 100 | if (isset(self::$_store[$section][$variable])) { 101 | unset(self::$_store[$section][$variable]); 102 | return true; 103 | } 104 | 105 | return false; 106 | } 107 | } -------------------------------------------------------------------------------- /Library/Core/Controller.php: -------------------------------------------------------------------------------- 1 | 13 | * @package MVC 14 | */ 15 | class Controller 16 | { 17 | /** 18 | * The controller that we are managing. 19 | * 20 | * @access public 21 | * @var Controller 22 | */ 23 | public $child; 24 | 25 | /** 26 | * Instance of the view. 27 | * 28 | * @access public 29 | * @var View 30 | */ 31 | public $view; 32 | 33 | /** 34 | * The constructor for the controller. 35 | * 36 | * @access public 37 | */ 38 | public function __construct() { 39 | // Which caching interface should we use? 40 | $cacheInterface = 'Core\\Store\\' . Config::get('cache', 'interface'); 41 | // Get the view instance 42 | $this->view = new View(new $cacheInterface); 43 | } 44 | 45 | /** 46 | * Change the layout from the default. 47 | * 48 | * If you do not want your View Script to use a layout then pass in boolean 49 | * false. To set a new layout then pass in the layouts file name without 50 | * the .phtml extention. Your layouts are located in your MyProject/Layout 51 | * directory. For example: 52 | * 53 | * 54 | * $this->view->setLayout('default'); 55 | * 56 | * 57 | * @access public 58 | * @param boolean|string $layout Which layout we wish to use. 59 | * @return boolean 60 | * @throws Exception If the layout does not exist. 61 | */ 62 | public function setLayout($layout) { 63 | // If boolean false then do not output a layout 64 | if ($layout === false) { 65 | $this->view->layout = false; 66 | return true; 67 | } 68 | 69 | // The location of the layout 70 | $templateUrlLayout = Config::get('path', 'base') . Config::get('path', 'project') 71 | . 'Layout/' . $layout . '.phtml'; 72 | 73 | // Check the layout actually exists before we set it 74 | if (! file_exists($templateUrlLayout)) { 75 | throw new \Exception("{$layout} layout does not exist."); 76 | } 77 | 78 | // The layout exists 79 | $this->view->layout = $layout; 80 | } 81 | 82 | /** 83 | * Whilst in controller context, move to another controller/action. 84 | * 85 | * When forwarding to a new action or controller/action, the URL will not 86 | * change, this is an internal redirect. 87 | * 88 | * Forwarding to an action in the same controller 89 | * 90 | * 91 | * $this->forward('newAction'); 92 | * 93 | * 94 | * Forwarding to a new controller and action. 95 | * 96 | * 97 | * $this->forward('newAction', 'newController'); 98 | * 99 | * 100 | * @access public 101 | * @param string $action The action we wish to forward to. 102 | * @param string $controller The controller we wish to forward to. 103 | * @throws Exception From the Dispatcher if the controller/action does not exist. 104 | * @return boolean Returns false to signify not to render previous actions. 105 | */ 106 | public function forward($action = 'index', $controller = '') { 107 | // Reregister the action in the profile 108 | Profiler::deregister('Action', $this->view->action); 109 | 110 | // Set which controller has called this function. If it is the same as 111 | // .. the user specified controller then we do not need to instantiate 112 | // .. a whole new controller, we can simply forward to its action 113 | $controllerCaller = str_replace( 114 | Config::get('settings', 'project') . '\\Controller\\', 115 | '', 116 | get_called_class() 117 | ); 118 | 119 | // Calling an action in the same controller 120 | if ($controller == '' || $controller == $controllerCaller) { 121 | Dispatcher::loadAction($this->child, $action); 122 | } 123 | 124 | // Not the same controller, so dispatch to a new controller 125 | else { 126 | Profiler::deregister('Controller', $this->view->controller); 127 | Dispatcher::loadController($controller, $action); 128 | } 129 | 130 | return false; 131 | } 132 | 133 | /** 134 | * Redirect the user to a new page. 135 | * 136 | * Unlike the forward() function, this will perform a header redirect, 137 | * causing the URL to change. 138 | * 139 | * @access public 140 | * @param string $url The URL that we wish to redirect to. 141 | */ 142 | public function redirect($url) { 143 | header('Location: ' . $url); exit(); 144 | } 145 | } -------------------------------------------------------------------------------- /Library/Core/Database.php: -------------------------------------------------------------------------------- 1 | 10 | * @package MVC 11 | */ 12 | class Database 13 | { 14 | /** 15 | * The connection to the database. 16 | * 17 | * @access protected 18 | * @var \PDO 19 | */ 20 | protected $_connection; 21 | 22 | /** 23 | * The query that we have just run. 24 | * 25 | * @access protected 26 | * @var \PDOStatement 27 | */ 28 | protected $_statement; 29 | 30 | /** 31 | * Connect to the database if we have not already. 32 | * 33 | * @access private 34 | */ 35 | private function connect() { 36 | // Get the connection details 37 | $host = Config::get('db', 'host'); 38 | $database = Config::get('db', 'database'); 39 | $username = Config::get('db', 'username'); 40 | $password = Config::get('db', 'password'); 41 | 42 | try { 43 | // Connect to the database 44 | $this->_connection = new \PDO( 45 | "mysql:host={$host};dbname={$database};charset=utf8", 46 | $username, 47 | $password 48 | ); 49 | } catch(\PDOException $e) { 50 | if (Config::get('settings', 'environment') == 'Dev') { 51 | var_dump($e); 52 | } 53 | 54 | die('

Sorry, we were unable to complete your request.

'); 55 | } 56 | } 57 | 58 | /** 59 | * Execute an SQL statement on the database. 60 | * 61 | * @access protected 62 | * @param string $sql The SQL statement to run. 63 | * @param array $data The data to pass into the prepared statement. 64 | * @param boolean $reset Whether we should reset the model data. 65 | * @return boolean 66 | */ 67 | protected function run($sql, $data = array(), $reset = true) { 68 | // If we do not have a connection then establish one 69 | if (! $this->_connection) { 70 | $this->connect(); 71 | } 72 | 73 | // Prepare, execute, reset, and return the outcome 74 | $this->_statement = $this->_connection->prepare($sql); 75 | $result = $this->_statement->execute($data); 76 | if ($reset) { 77 | $this->reset(); 78 | } 79 | return $result; 80 | } 81 | } -------------------------------------------------------------------------------- /Library/Core/Dispatcher.php: -------------------------------------------------------------------------------- 1 | 13 | * @package MVC 14 | */ 15 | class Dispatcher 16 | { 17 | /** 18 | * Load a controller and it's respective action. 19 | * 20 | * If no action is passed in then we use the action as defined by the URL. If 21 | * no action is specified in the URL then we use the default og 'index'. 22 | * 23 | * @access public 24 | * @param string $controllerName Name of the controller we wish to load. 25 | * @param string $action Name of the action we wish to load. 26 | * @static 27 | */ 28 | public static function loadController($controllerName, $action = '') { 29 | // Work out the full namespace path for this controller 30 | $controller = Config::get('settings', 'project') . '\\Controller\\' . $controllerName; 31 | 32 | // If the controller does not exist the autoloader will throw an Exception 33 | try { 34 | // Try and create a new instance of the controller 35 | Profiler::register('Core', 'Dispatcher'); 36 | Profiler::register('Controller', $controllerName); 37 | $controller = new $controller(); 38 | $controller->request = new Request(); 39 | 40 | // Let the Core controller be aware of its child 41 | $controller->child = $controller; 42 | 43 | // Inform the event listener a controller has been initialised 44 | Event::trigger('initController', array('controller' => $controller)); 45 | Profiler::deregister('Core', 'Dispatcher'); 46 | 47 | // If the controller has an init function then call it first 48 | if (is_callable(array($controller, 'init'))) { 49 | Profiler::register('Controller', 'init'); 50 | $controller->init(); 51 | Profiler::deregister('Controller', 'init'); 52 | } 53 | 54 | // Controller fully initialised, now run its action 55 | $action = $action ?: Request::get('action'); 56 | } catch (\Exception $e) { 57 | // If this is the error controller then there is no hope 58 | if ($controllerName == 'Error') { 59 | die('Sorry, an error occurred whilst processing your request.'); 60 | } 61 | 62 | // Controller does not exist, forward to the error controller 63 | Profiler::deregister('Core', 'Dispatcher'); 64 | Profiler::deregister('Controller', $controllerName); 65 | Dispatcher::loadController('Error', 'notFound'); 66 | } 67 | 68 | // Load the action 69 | Dispatcher::loadAction($controller, $action); 70 | } 71 | 72 | /** 73 | * Load a controllers action, and ask the View to render it. 74 | * 75 | * @access public 76 | * @param object $controller Controller object that we want to load the action for. 77 | * @param string $action Name of the action we wish to load. 78 | * @static 79 | */ 80 | public static function loadAction($controller, $action) { 81 | // Start the profiler 82 | Profiler::register('Core', 'Dispatcher'); 83 | 84 | // In order to have pretty URL's we allow the basic routing to contain 85 | // .. dashes in their action names which will be removed here. It allows 86 | // .. /index/hello-world to be routed to /index/helloworld. 87 | $action = str_replace('-', '', $action); 88 | $actionExists = is_callable(array($controller, $action . 'Action')); 89 | $controllerName = str_replace( 90 | Config::get('settings', 'project') . '\\Controller\\', 91 | '', 92 | get_class($controller) 93 | ); 94 | 95 | // Make sure that the controller has the action 96 | if (! $actionExists) { 97 | // If this is the error controller then there is no hope 98 | if ($controllerName == 'Error') { 99 | die('Sorry, an error occurred whilst processing your request.'); 100 | } 101 | 102 | // Controllers can have their own error function for finer grain control 103 | else if ($action != 'error') { 104 | Profiler::deregister('Core', 'Dispatcher'); 105 | Dispatcher::loadAction($controller, 'error'); 106 | } 107 | 108 | // Controller does not have an error function, run Error controller 109 | else { 110 | Profiler::deregister('Core', 'Dispatcher'); 111 | Profiler::deregister('Controller', $controllerName); 112 | Dispatcher::loadController('Error', 'notFound'); 113 | } 114 | } 115 | 116 | // We are able to call the controllers action 117 | Event::trigger('initAction', array( 118 | 'controller' => $controller, 119 | 'action' => $action 120 | )); 121 | Profiler::deregister('Core', 'Dispatcher'); 122 | Profiler::register('Action', $action); 123 | 124 | // Set the controller and action that we are heading to 125 | $controller->view->controller = $controllerName; 126 | $controller->view->action = $action; 127 | 128 | // Call and render this action 129 | if ($controller->{$action . 'Action'}() !== false) { 130 | $controller->view->render(); 131 | } 132 | } 133 | } -------------------------------------------------------------------------------- /Library/Core/Event.php: -------------------------------------------------------------------------------- 1 | 11 | *
  • A request is initialised.
  • 12 | *
  • A controller is initialised.
  • 13 | *
  • An action has been called.
  • 14 | *
  • We are about to shutdown (page has been rendered).
  • 15 | * 16 | * 17 | * @copyright Copyright (c) 2012-2013 Christopher Hill 18 | * @license http://www.opensource.org/licenses/mit-license.php The MIT License 19 | * @author Christopher Hill 20 | * @package MVC 21 | */ 22 | class Event 23 | { 24 | /** 25 | * Trigger an event call to the application. 26 | * 27 | * @access public 28 | * @param string $event The event to trigger within the appliction. 29 | * @param array $params Parameters to pass to the application. 30 | * @static 31 | */ 32 | public static function trigger($event, $params) { 33 | // Start the profiler 34 | Profiler::register('Core', 'Event.' . $event); 35 | 36 | // Create a reference to the users event listener 37 | $listener = Config::get('settings', 'project') . '\\EventListener'; 38 | 39 | // Call the appropriate event listener function 40 | $listener::$event($params); 41 | 42 | // Stop the profiler 43 | Profiler::deregister('Core', 'Event.' . $event); 44 | } 45 | } -------------------------------------------------------------------------------- /Library/Core/Format.php: -------------------------------------------------------------------------------- 1 | 10 | * @package MVC 11 | */ 12 | class Format 13 | { 14 | /** 15 | * Make sure that anything outputted to the browser is safe. 16 | * 17 | * @access public 18 | * @param string $string The string that we want to make safe to output. 19 | * @return string 20 | */ 21 | public function safeHtml($string) { 22 | return htmlspecialchars($string, ENT_QUOTES, 'UTF-8', false); 23 | } 24 | 25 | /** 26 | * Strips all invalid characters out of a URL. 27 | * 28 | * @access public 29 | * @param string $url The URL we need to parse. 30 | * @return string 31 | */ 32 | public function parseUrl($url) { 33 | return preg_replace('/[^a-z0-9-]/', '', 34 | strtolower(str_replace(' ', '-', $url)) 35 | ); 36 | } 37 | } -------------------------------------------------------------------------------- /Library/Core/Front.php: -------------------------------------------------------------------------------- 1 | 12 | * @package MVC 13 | */ 14 | class Front 15 | { 16 | /** 17 | * The name of the project that we are now going to run. This is also the 18 | * name of the directory that contains the application. 19 | * 20 | * @access private 21 | * @var string 22 | */ 23 | private $_projectName; 24 | 25 | /** 26 | * Provides information on routes that the application accepts and understands. 27 | * 28 | * If no router is defined then we simply fall back to /controller/action URL. 29 | * 30 | * @access private 31 | * @var Core\Router 32 | */ 33 | private $_router; 34 | 35 | /** 36 | * Initialises the application configuration and runs the router. 37 | * 38 | * @access public 39 | * @param string $projectName The name of the project the user wishes to run. 40 | * @param Core\Router $router The routes the users application requires. 41 | */ 42 | public function __construct($projectName, $router = null) { 43 | // Start the profiler 44 | Profiler::start(); 45 | Profiler::register('Core', 'Front'); 46 | 47 | // Load the configuration for this project 48 | Profiler::register('Core', 'Config'); 49 | Config::load($projectName); 50 | Profiler::deregister('Core', 'Config'); 51 | Profiler::register('Core', 'Request'); 52 | Request::setUrl(); 53 | Profiler::deregister('Core', 'Request'); 54 | 55 | // Set the project information 56 | $this->_projectName = $projectName; 57 | $this->_router = $router ?: new Router(); 58 | 59 | // And route 60 | $this->route(); 61 | } 62 | 63 | /** 64 | * Route, and pass to the Dispatcher to run our controller/action. 65 | * 66 | * @access private 67 | */ 68 | private function route() { 69 | $this->_router->route(); 70 | } 71 | } -------------------------------------------------------------------------------- /Library/Core/Model.php: -------------------------------------------------------------------------------- 1 | 10 | * // 1. Object-orientated inserting: 11 | * $user = new Model\User(); 12 | * $user->name = 'Chris'; 13 | * $user->email = 'cjhill@gmail.com'; 14 | * $user->save(); 15 | * 16 | * // 2. Pass in an array of data: 17 | * $user = new Model\User(); 18 | * $user->insert(array('name' => 'Chris', 'email' => 'cjhill@gmail.com')); 19 | * 20 | * 21 | * Selecting 22 | * --------- 23 | * 24 | * // 1. Select a single user very quickly: 25 | * $user = new Model\User(1); 26 | * 27 | * // 2. Advanced query selecting: 28 | * $users = new Model\User(); 29 | * $users->where('active', '=', 1)->where('name', '=', 'Dave')->limit(10)->find(); 30 | * 31 | * // 3. How many users the query found: 32 | * echo 'I found ' . $users->rowCount() . ' users.'; 33 | * 34 | * // 4. Loop over the found users: 35 | * while ($user = $users->fetch()) { 36 | * echo 'Hello, ' . $user->name; 37 | * } 38 | * 39 | * 40 | * Updating 41 | * -------- 42 | * 43 | * 1. Updating a user programatically: 44 | * $user = new Model\User(1); 45 | * $user->name = 'Dave'; 46 | * $user->save(); 47 | * 48 | * 2. Passing in an array of data: 49 | * $user = new Model\User(1); 50 | * $user->save(array('name' => 'Dave')); 51 | * 52 | * 3. Advanced updating: 53 | * $user = new Model\User(); 54 | * $user->where('id', '=', array(1, 2))->limit(2)->update(array('name' => 'Dave')); 55 | * 56 | * 57 | * Deleting 58 | * -------- 59 | * 60 | * // 1. Simple deletion: 61 | * $user = new Model\User(); 62 | * $user->delete(1); 63 | * 64 | * // 2. Advanced deletion: 65 | * $user = new Model\User(); 66 | * $user->where('id', '=', 1)->limit(1)->delete(); 67 | * 68 | * 69 | * Running your own queries 70 | * ------------------------ 71 | * 72 | * // 1. In your User Model, for instance: 73 | * $this->run('SELECT * FROM user WHERE name = :name', array(':name' => 'Chris')); 74 | * $user = $this->fetch(); 75 | * echo 'Hello, ' . $user->name; 76 | * 77 | * 78 | * @copyright Copyright (c) 2012-2013 Christopher Hill 79 | * @license http://www.opensource.org/licenses/mit-license.php The MIT License 80 | * @author Christopher Hill 81 | * @package MVC 82 | * 83 | * @todo Some kind of config schema. 84 | */ 85 | class Model extends Database 86 | { 87 | /** 88 | * The primary key for the table. 89 | * 90 | * This can (and should) be overridden by the extending class. 91 | * 92 | * @access protected 93 | * @var string 94 | */ 95 | protected $_primaryKey = 'id'; 96 | 97 | /** 98 | * Which columns we want to select. 99 | * 100 | * To mitigate SQL errors we always append the table name to the start of 101 | * the field name, whether or not one is supplied. If no table name is 102 | * passed in then we use the default table the the extended class declared. 103 | * 104 | * @access private 105 | * @var array 106 | */ 107 | private $_select = array(); 108 | 109 | /** 110 | * The tables that we wish to select data from. 111 | * 112 | * @access private 113 | * @var array 114 | */ 115 | private $_from = array(); 116 | 117 | /** 118 | * The clause conditions for where and having to apply to the query. 119 | * 120 | * @access private 121 | * @var array 122 | */ 123 | private $_clause = array(); 124 | 125 | /** 126 | * The having conditions to apply. 127 | * 128 | * @access private 129 | * @var array 130 | */ 131 | private $_having = array(); 132 | 133 | /** 134 | * How our queries should be grouped. 135 | * 136 | * @access private 137 | * @var array 138 | */ 139 | private $_group = array(); 140 | 141 | /** 142 | * How we should order the returned rows. 143 | * 144 | * @access private 145 | * @var array 146 | */ 147 | private $_order = array(); 148 | 149 | /** 150 | * How we should limit the returned rows. 151 | * 152 | * @access private 153 | * @var array 154 | */ 155 | private $_limit = array(); 156 | 157 | /** 158 | * Data that has been passed to the row to insert/update. 159 | * 160 | * @access private 161 | * @var array 162 | */ 163 | private $_store = array(); 164 | 165 | /** 166 | * Data that will be passed to the query. 167 | * 168 | * @access private 169 | * @var array 170 | */ 171 | private $_data; 172 | 173 | /** 174 | * Whether, after running a query, we should reset the model data. 175 | * 176 | * @access private 177 | * @var boolean 178 | */ 179 | private $_resetAfterQuery = true; 180 | 181 | /** 182 | * Setup the model. 183 | * 184 | * If you want to load a row automatically then you can pass an int to this 185 | * function, or to load multiple rows then you can pass an array or ints. 186 | * 187 | * 188 | * // Load a single user row 189 | * $user = new MyProject\Model\User(1); 190 | * 191 | * 192 | * @access public 193 | * @param mixed $id The ID to load automatically. 194 | */ 195 | public function __construct($id = null) { 196 | if ($id) { 197 | $this->where($this->_primaryKey, '=', $id) 198 | ->limit(1) 199 | ->find(); 200 | $this->_store = $this->fetch(\PDO::FETCH_ASSOC); 201 | } 202 | } 203 | 204 | /** 205 | * Whether we should reset the query data after we have run the query. 206 | * 207 | * @access public 208 | * @param boolean $reset 209 | * @return Model For chainability. 210 | */ 211 | public function setReset($reset = true) { 212 | $this->_resetAfterQuery = $reset; 213 | return $this; 214 | } 215 | 216 | /** 217 | * Add a row to the SELECT section. 218 | * 219 | * Note: The table name will always be prefixed to the field name to try and 220 | * mitigate errors . If none is supplied then we assume you are using the 221 | * table name that is declared in the extending class. 222 | * 223 | * @access public 224 | * @param string $field The field name. 225 | * @param string $as The name of the field that is supplied to you. 226 | * @return Model For chainability. 227 | */ 228 | public function select($field, $as = null) { 229 | $this->_select[] = array('field' => $field, 'as' => $as); 230 | return $this; 231 | } 232 | 233 | /** 234 | * Add a table to the query. 235 | * 236 | * Note: If no table is supplied then we will use the table defined in the 237 | * extending class. If you are not joining other tables then you do not need 238 | * to call this function. 239 | * 240 | * @access public 241 | * @param string $table A table that is part of the statement. 242 | * @param string $joinType How to join the table ('left', 'right', etc.). 243 | * @param string $tableField First table join column. 244 | * @param string $joinField Second table join column. 245 | * @return Model For chainability. 246 | */ 247 | public function from($table, $joinType = null, $tableField = null, $joinField = null) { 248 | $this->_from[] = array( 249 | 'table' => $table, 250 | 'joinType' => $joinType, 251 | 'tableField' => $tableField, 252 | 'joinField' => $joinField 253 | ); 254 | return $this; 255 | } 256 | 257 | /** 258 | * Add a where condition to the statement. 259 | * 260 | * 261 | * // 1. A simple condition: 262 | * ->where('name', '=', 'Chris') 263 | * 264 | * // 2. An IN condition: 265 | * ->where('name', 'IN', array('Chris', 'John', 'Smith')); 266 | * 267 | * // You can also use the equals operator for this! 268 | * ->where('name', '=', array('Chris', 'John', 'Smith')); 269 | * 270 | * // 3. Multiple where's: 271 | * ->where('name', '=', 'Chris')->where('email', '=', 'cjhill@gmail.com') 272 | * 273 | * 274 | * @access public 275 | * @param string $field The field we wish to test. 276 | * @param string $operator How we wish to test the field (=, >, etc.) 277 | * @param string|array $value The value to test the field against. 278 | * @param string $joiner How to join the where clause to the next. 279 | * @return Model For chainability. 280 | */ 281 | public function where($field, $operator, $value, $joiner = null) { 282 | $this->_clause[] = array( 283 | 'field' => $field, 284 | 'operator' => $operator, 285 | 'value' => $value, 286 | 'joiner' => $joiner 287 | ); 288 | return $this; 289 | } 290 | 291 | /** 292 | * Add a having condition to the statement. 293 | * 294 | * @access public 295 | * @param string $field The field we wish to test. 296 | * @param string $operator How we wish to test the field (=, >, etc.) 297 | * @param string|array $value The value to test the field against. 298 | * @param string $joiner How to join the where clause to the next. 299 | * @param int $brace How many braces to open or close. 300 | * @return Model For chainability. 301 | */ 302 | public function having($field, $operator, $value, $joiner = null, $brace = 0) { 303 | $this->_having[] = array( 304 | 'field' => $field, 305 | 'operator' => $operator, 306 | 'value' => $value, 307 | 'joiner' => $joiner, 308 | 'brace' => $brace 309 | ); 310 | return $this; 311 | } 312 | 313 | /** 314 | * Group by a field. 315 | * 316 | * @access public 317 | * @param string $field The field that we want to join on. 318 | * @return Model For chainability. 319 | */ 320 | public function group($field) { 321 | $this->_group[] = $field; 322 | return $this; 323 | } 324 | 325 | /** 326 | * Whether to open or close a brace. 327 | * 328 | * @access public 329 | * @param string $status Either 'open' or 'close'. 330 | * @param string $joiner Either 'AND' or 'OR'. 331 | * @return Model For chainability. 332 | */ 333 | public function brace($status, $joiner = null) { 334 | $this->_clause[] = ($status == 'open' ? '(' : ')') 335 | . ($joiner ? " {$joiner} " : ''); 336 | } 337 | 338 | /** 339 | * How to order the returned results. 340 | * 341 | * @access public 342 | * @param string $field The field we wish to order by. 343 | * @param string $direction Either 'ASC' or 'DESC'. 'ASC' by default. 344 | * @return Model For chainability. 345 | */ 346 | public function order($field, $direction = 'ASC') { 347 | $this->_order[] = array('field' => $field, 'direction' => $direction); 348 | return $this; 349 | } 350 | 351 | /** 352 | * How to limit the returned results. 353 | * 354 | * @access public 355 | * @param int $limit How many results to return. 356 | * @param int $start The offset to start the results from. 357 | * @return Model For chainability. 358 | */ 359 | public function limit($limit, $start = null) { 360 | $this->_limit = array('limit' => $limit, 'start' => $start); 361 | return $this; 362 | } 363 | 364 | /** 365 | * Insert a row into the table. 366 | * 367 | * @access public 368 | * @param array $data The data to insert into the table. 369 | */ 370 | public function insert($data = array()) { 371 | // If we have been supplied from data then add it to the store. 372 | foreach ($data as $field => $value) { 373 | $this->$field = $value; 374 | } 375 | 376 | // If the insert was successful then add the primary key to the store 377 | if ($this->run($this->build('insert'), $this->_store, $this->_resetAfterQuery)) { 378 | $this->{$this->_primaryKey} = $this->_connection->lastInsertId(); 379 | } 380 | } 381 | 382 | /** 383 | * Select some records from a table. 384 | * 385 | * @access public 386 | */ 387 | public function find() { 388 | $this->run($this->build('select'), $this->_data, $this->_resetAfterQuery); 389 | } 390 | 391 | /** 392 | * Update a row in the table. 393 | * 394 | * @access public 395 | * @param array $data The data to update the table with. 396 | */ 397 | public function update($data = array()) { 398 | // If we have been supplied from data then add it to the store. 399 | foreach ($data as $field => $value) { 400 | $this->$field = $value; 401 | } 402 | 403 | // If the where clause is empty then assume we are updating via primary key 404 | if (! $this->_clause) { 405 | $this->where($this->_primaryKey, '=', $this->{$this->_primaryKey}); 406 | } 407 | 408 | // If the insert was successful then add the primary key to the store 409 | $this->run($this->build('update'), $this->_data, $this->_resetAfterQuery); 410 | } 411 | 412 | /** 413 | * Shorthand for the insert and update functions. 414 | * 415 | * @access public 416 | * @param array $data The data to insert or update. 417 | */ 418 | public function save($data = array()) { 419 | $this->{$this->_primaryKey} 420 | ? $this->update($data) 421 | : $this->insert($data); 422 | } 423 | 424 | /** 425 | * Delete rows from the table. 426 | * 427 | * @access public 428 | * @param int $id The ID of the row we wish to delete. 429 | */ 430 | public function delete($id = null) { 431 | // Is there an ID that we need to delete? 432 | if ($id) { 433 | $this->where($this->_primaryKey, '=', $id); 434 | } 435 | 436 | $this->run($this->build('delete'), $this->_data, $this->_resetAfterQuery); 437 | } 438 | 439 | /** 440 | * Piece together all of the sections of the query. 441 | * 442 | * @access public 443 | * @param string $type What type of query we wish to build. 444 | * @return string The SQL that has been generated. 445 | */ 446 | public function build($type) { 447 | switch ($type) { 448 | case 'insert' : $sql = $this->buildInsert(); break; 449 | case 'select' : $sql = $this->buildSelect(); break; 450 | case 'update' : $sql = $this->buildUpdate(); break; 451 | case 'delete' : $sql = $this->buildDelete(); break; 452 | } 453 | 454 | return $sql; 455 | } 456 | 457 | /** 458 | * Build an insert statement. 459 | * 460 | * @access private 461 | * @return string 462 | */ 463 | private function buildInsert() { 464 | $keys = array_keys($this->_store); 465 | $fields = implode(', ', $keys); 466 | $values = implode(', :', $keys); 467 | 468 | return "INSERT INTO {$this->_table} ({$fields}) VALUES (:{$values})"; 469 | } 470 | 471 | /** 472 | * Build a select statement. 473 | * 474 | * @access private 475 | * @return string 476 | */ 477 | private function buildSelect() { 478 | return "SELECT {$this->buildFragmentSelect()} 479 | FROM {$this->buildFragmentFrom()} 480 | {$this->buildFragmentWhere()} 481 | {$this->buildFragmentWhere('HAVING')} 482 | {$this->buildFragmentGroup()} 483 | {$this->buildFragmentOrder()} 484 | {$this->buildFragmentLimit()}"; 485 | } 486 | 487 | /** 488 | * Build an update statement. 489 | * 490 | * @access private 491 | * @return string 492 | */ 493 | private function buildUpdate() { 494 | return "UPDATE {$this->buildFragmentFrom()} 495 | SET {$this->buildFragmentUpdate()} 496 | {$this->buildFragmentWhere()} 497 | {$this->buildFragmentLimit()}"; 498 | } 499 | 500 | /** 501 | * Build a delete statement. 502 | * 503 | * @access private 504 | * @return string 505 | */ 506 | private function buildDelete() { 507 | return "DELETE FROM {$this->buildFragmentFrom()} 508 | {$this->buildFragmentWhere()} 509 | {$this->buildFragmentLimit()}"; 510 | } 511 | 512 | /** 513 | * Build the SELECT portion of the statement. 514 | * 515 | * @access private 516 | * @return string 517 | */ 518 | private function buildFragmentSelect() { 519 | // If there are no fields to select from then just return them all 520 | if (empty($this->_select)) { 521 | return '*'; 522 | } 523 | 524 | // Container for the fields we wish to select 525 | $fields = array(); 526 | 527 | // Loop over each field that we want to return and build its SQL 528 | foreach ($this->_select as $select) { 529 | $as = $select['as'] 530 | ? " AS '{$select['as']}'" 531 | : ''; 532 | 533 | 534 | 535 | $fields[] = "{$select['field']} {$as}"; 536 | } 537 | 538 | return implode(', ', $fields); 539 | } 540 | 541 | /** 542 | * Build the FROM portion of the statement. 543 | * 544 | * @access private 545 | * @return string 546 | */ 547 | private function buildFragmentFrom() { 548 | // If there are no fields to select from then just return them all 549 | if (empty($this->_from)) { 550 | return $this->_table; 551 | } 552 | 553 | // Container for the tables we wish to use 554 | $tables = array(); 555 | 556 | // Loop over each table and build its SQL 557 | foreach ($this->_from as $from) { 558 | $tables[] = $from['tableField'] && $from['joinField'] 559 | ? "{$from['joinType']} JOIN {$from['table']} ON {$from['tableField']} = {$from['joinField']}" 560 | : $from['table']; 561 | } 562 | 563 | return implode(', ', $tables); 564 | } 565 | 566 | /** 567 | * Build the SET portion of the statement. 568 | * 569 | * @access private 570 | * @return string 571 | */ 572 | private function buildFragmentUpdate() { 573 | // Container for the fields that will be updated 574 | $fields = array(); 575 | 576 | foreach ($this->_store as $field => $value) { 577 | // We do not want to update the primary key 578 | if ($field == $this->_primaryKey) { 579 | continue; 580 | } 581 | 582 | $fields[] = "{$field} = :{$field}"; 583 | $this->_data[$field] = $value; 584 | } 585 | 586 | return implode(', ', $fields); 587 | } 588 | 589 | /** 590 | * Build the WHERE portion of the statement. 591 | * 592 | * Note: So we do not interfere with any field names we label our prepared 593 | * variables prefixed with "__where_". 594 | * 595 | * @access private 596 | * @param string $type Whether this is a WHERE or HAVING clause. 597 | * @return string 598 | * @todo Allow for OR's. 599 | */ 600 | private function buildFragmentWhere($type = 'WHERE') { 601 | // If there are no conditions then return nothing 602 | if ($type == 'HAVING' && empty($this->_having)) { 603 | return ''; 604 | } else if (empty($this->_clause)) { 605 | return ''; 606 | } 607 | 608 | // Container for the where conditions 609 | $sql = ''; 610 | $sqlClauses = ''; 611 | $clauses = $type == 'HAVING' ? $this->_having : $this->_clause; 612 | $clauseType = strtolower($type); 613 | 614 | // Loop over each where condition and build its SQL 615 | foreach ($clauses as $clauseIndex => $clause) { 616 | // Are we opening or closing a brace? 617 | if (! is_array($clause)) { 618 | $sqlClauses .= $clause; 619 | continue; 620 | } 621 | 622 | // The basic perpared variable name 623 | $clauseVar = "__{$clauseType}_{$clauseIndex}"; 624 | 625 | // Reset the SQL for this single clause 626 | $sql = ''; 627 | 628 | // We are dealing with an IN 629 | if (is_array($clause['value'])) { 630 | // We need to create the condition as :a, :b, :c 631 | $clauseIn = array(); 632 | 633 | // Loop over each value in the array 634 | foreach ($clause['value'] as $index => $value) { 635 | $clauseIn[] = ":{$clauseVar}_{$index}"; 636 | $this->_data["{$clauseVar}_{$index}"] = $value; 637 | } 638 | 639 | // The SQL for this IN 640 | $sql .= "{$clause['field']} IN (" . implode(', ', $clauseIn) . ")"; 641 | } 642 | 643 | // A simple where condition 644 | else { 645 | $sql .= "{$clause['field']} {$clause['operator']} :{$clauseVar}"; 646 | $this->_data[$clauseVar] = $clause['value']; 647 | } 648 | 649 | // Add any joiner (AND, OR< etc) that the user has added 650 | $sql .= $clause['joiner'] ? " {$clause['joiner']} " : ''; 651 | 652 | // And add to the where clause 653 | $sqlClauses .= $sql; 654 | } 655 | 656 | return "{$type} {$sqlClauses}"; 657 | } 658 | 659 | /** 660 | * Build the GROUP BY portion of the statement. 661 | * 662 | * @access private 663 | * @return string 664 | */ 665 | private function buildFragmentGroup() { 666 | return ! empty($this->_group) 667 | ? 'GROUP BY ' . implode(', ', $this->_group) 668 | : ''; 669 | } 670 | 671 | /** 672 | * Build the ORDER BY portion of the statement. 673 | * 674 | * @access private 675 | * @return string 676 | */ 677 | private function buildFragmentOrder() { 678 | // If there are no order by's then return nothing 679 | if (empty($this->_order)) { 680 | return ''; 681 | } 682 | 683 | // Container for the order by's 684 | $orders = array(); 685 | 686 | // Loop over each order by and build its SQL 687 | foreach ($this->_order as $order) { 688 | $orders[] = "{$order['field']} {$order['direction']}"; 689 | } 690 | 691 | return 'ORDER BY ' . implode(', ', $orders); 692 | } 693 | 694 | /** 695 | * Build the LIMIT portion of the statement. 696 | * 697 | * @access private 698 | * @return string 699 | */ 700 | private function buildFragmentLimit() { 701 | // If there is no limit then return nothing 702 | if (empty($this->_limit)) { 703 | return ''; 704 | } 705 | 706 | // If there is a start then add that as well... 707 | if (! is_null($this->_limit['start'])) { 708 | return "LIMIT {$this->_limit['start']}, {$this->_limit['limit']}"; 709 | } 710 | 711 | // ... otherwise just return the simple limit 712 | return "LIMIT {$this->_limit['limit']}"; 713 | } 714 | 715 | /** 716 | * Get how many rows the statement located. 717 | * 718 | * @access public 719 | * @return int|boolean int if statement was successful, boolean false otherwise. 720 | */ 721 | public function rowCount() { 722 | return $this->_statement 723 | ? $this->_statement->rowCount() 724 | : false; 725 | } 726 | 727 | /** 728 | * Get the next row of the located results. 729 | * 730 | * @access public 731 | * @param \PDO $method In what format the dataset should be returned. 732 | * @return mixed Array if statement was successful, boolean false otherwise. 733 | */ 734 | public function fetch($method = \PDO::FETCH_OBJ) { 735 | return $this->_statement 736 | ? $this->_statement->fetch($method) 737 | : false; 738 | } 739 | 740 | /** 741 | * Reset the query ready for the next one to avoid contamination. 742 | * 743 | * Note: This function is called everytime we have run a query automatically. 744 | * 745 | * @access public 746 | */ 747 | public function reset() { 748 | $this->_select = array(); 749 | $this->_from = array(); 750 | $this->_clause = array(); 751 | $this->_having = array(); 752 | $this->_group = array(); 753 | $this->_order = array(); 754 | $this->_limit = array(); 755 | $this->_data = array(); 756 | $this->_store = array(); 757 | } 758 | 759 | /** 760 | * Set a variable for the row. 761 | * 762 | * Note: This is only used for inserting and updating statements. It will 763 | * also update any previous value the field had. 764 | * 765 | * @access public 766 | * @param string $variable The field to manipulate. 767 | * @param mixed $value The field's value. 768 | * @magic 769 | */ 770 | public function __set($variable, $value) { 771 | $this->_store[$variable] = $value; 772 | } 773 | 774 | /** 775 | * Get a field value. 776 | * 777 | * Note: This is only used for inserting and updating statements. For all 778 | * other statements you can use the fetch() function. 779 | * 780 | * @access public 781 | * @param string $field The name of the field. 782 | * @return string|boolean String if exists, boolean false otherwise. 783 | */ 784 | public function __get($field) { 785 | return isset($this->_store[$field]) 786 | ? $this->_store[$field] 787 | : false; 788 | } 789 | } -------------------------------------------------------------------------------- /Library/Core/Notice.php: -------------------------------------------------------------------------------- 1 | 10 | * @package MVC 11 | */ 12 | class Notice 13 | { 14 | /** 15 | * What type of notice this is. 16 | * 17 | * @access private 18 | * @var string 19 | */ 20 | private $_status = 'info'; 21 | 22 | /** 23 | * The title of the notice. 24 | * 25 | * @access private 26 | * @var string 27 | */ 28 | private $_title; 29 | 30 | /** 31 | * The introduction of the notice. 32 | * 33 | * @access private 34 | * @var string 35 | */ 36 | private $_intro; 37 | 38 | /** 39 | * The bullet points for the notice. 40 | * 41 | * @access private 42 | * @var array 43 | */ 44 | private $_list; 45 | 46 | /** 47 | * The CSS class to assign the notice. 48 | * 49 | * @access private 50 | * @var string 51 | */ 52 | private $_class; 53 | 54 | /** 55 | * Set the title of the notice. 56 | * 57 | * Note: The notice can either be "success", "error", or "info". 58 | * 59 | * @access public 60 | * @param string $status What type of notice this is. 61 | * @return Core\Notice For chainability. 62 | */ 63 | public function setStatus($status) { 64 | $this->_status = $status; 65 | } 66 | 67 | /** 68 | * Set the title of the notice. 69 | * 70 | * @access public 71 | * @param string $title What will be displayed at the top of the notice. 72 | * @return Core\Notice For chainability. 73 | */ 74 | public function setTitle($title) { 75 | $this->_title = $title; 76 | } 77 | 78 | /** 79 | * Set the introduction of the notice. 80 | * 81 | * @access public 82 | * @param string $intro What be displayed just below the title. 83 | * @return Core\Notice For chainability. 84 | */ 85 | public function setIntro($intro) { 86 | $this->_intro = $intro; 87 | } 88 | 89 | /** 90 | * Add a bullet point list to the notice. 91 | * 92 | * @access public 93 | * @param string|array $items The items to place into a list. 94 | * @return Core\Notice For chainability. 95 | * @recurvive 96 | */ 97 | public function setList($items) { 98 | // If an array then loop over each item and add it to the list 99 | if (is_array($items)) { 100 | foreach ($items as $item) { 101 | $this->setList($item); 102 | } 103 | } 104 | 105 | // A single item to add 106 | else { 107 | $this->_list[] = $item; 108 | } 109 | 110 | return $this; 111 | } 112 | 113 | /** 114 | * Any classes to assign to the notice. 115 | * 116 | * @access public 117 | * @param string $class CSS classes to assign to the notice. 118 | * @return Core\Notice For chainability. 119 | */ 120 | public function setClass($class) { 121 | $thi->_class = $class; 122 | } 123 | 124 | /** 125 | * Generate the HTML notice. 126 | * 127 | * @access public 128 | * @return string 129 | */ 130 | public function generate() { 131 | // Start to build the elements of the notice 132 | $status = $this->_status; 133 | $title = $this->_title ? "

    {$this->_title}

    " : ''; 134 | $intro = $this->_intro ? "

    {$this->_intro}

    " : ''; 135 | $class = $this->_class ? 'notice ' . $this->_class : ''; 136 | $list = $this->_list ? '
    • ' . implode('
    • ', $this->_list) . '
    ' : ''; 137 | 138 | return '
    ' . $title . $intro . $list . '
    '; 139 | } 140 | } -------------------------------------------------------------------------------- /Library/Core/Profiler.php: -------------------------------------------------------------------------------- 1 | 15 | * @package MVC 16 | * 17 | * @see /Library/MyProject/View/Helper/Profiler.php 18 | */ 19 | class Profiler 20 | { 21 | /** 22 | * A stack of traces that have occurred through the running of the application. 23 | * 24 | * 25 | * array( 26 | * array( 27 | * 'type' => 'Core', 28 | * 'name' => 'Request', 29 | * 'start' => 123.1, 30 | * 'end' => 123.2, 31 | * 'mem' => 1.0 32 | * ), 33 | * array( 34 | * 'type' => 'Controller', 35 | * 'name' => 'Index', 36 | * 'start' => 321.5, 37 | * 'end' => 321.8, 38 | * 'mem' => 2.5 39 | * ) 40 | * ) 41 | * 42 | * 43 | * @access private 44 | * @var array 45 | * @static 46 | */ 47 | private static $_stack = array(); 48 | 49 | /** 50 | * The time we started profiling the application. 51 | * 52 | * @access private 53 | * @var float 54 | * @static 55 | */ 56 | private static $_requestStart; 57 | 58 | /** 59 | * The time we finished profiling the application. 60 | * 61 | * @access private 62 | * @var float 63 | * @static 64 | */ 65 | private static $_requestEnd; 66 | 67 | /** 68 | * Start the profiler. 69 | * 70 | * @access public 71 | * @static 72 | */ 73 | public static function start() { 74 | self::$_requestStart = microtime(true); 75 | } 76 | 77 | /** 78 | * Stop the profiling. 79 | * 80 | * @access public 81 | * @static 82 | */ 83 | public static function stop() { 84 | self::$_requestEnd = microtime(true); 85 | } 86 | 87 | /** 88 | * The start of a trace. 89 | * 90 | * We add new traces to the start of the array so that when we deregister 91 | * them we shouldn't, hopefully, have to go through as many iterations. 92 | * 93 | * @access public 94 | * @param string $type The type of code that is running. 95 | * @param string $name How we will reference the trace in the stack. 96 | * @static 97 | */ 98 | public static function register($type, $name) { 99 | // Profile this request? 100 | if (! Config::get('profiler', 'enable', true)) { 101 | return false; 102 | } 103 | 104 | array_unshift(self::$_stack, array( 105 | 'type' => $type, 106 | 'name' => $name, 107 | 'start' => microtime(true), 108 | 'mem' => memory_get_usage() 109 | )); 110 | } 111 | 112 | /** 113 | * The end of a trace. 114 | * 115 | * @access public 116 | * @param string $type The type of code that is running. 117 | * @param string $name How we will reference the trace in the stack. 118 | * @static 119 | */ 120 | public static function deregister($type, $name) { 121 | // Profile this request? 122 | if (! Config::get('profiler', 'enable', true)) { 123 | return false; 124 | } 125 | 126 | // Get the time here to get a more accurate trace 127 | $microtime = microtime(true); 128 | 129 | // And begin the loop 130 | foreach (self::$_stack as $traceId => $trace) { 131 | if ($trace['name'] == $name) { 132 | self::$_stack[$traceId]['end'] = $microtime; 133 | self::$_stack[$traceId]['mem'] = 134 | memory_get_usage() - self::$_stack[$traceId]['mem']; 135 | break; 136 | } 137 | } 138 | } 139 | 140 | /** 141 | * Return the stack of traces we recorded for this request. 142 | * 143 | * @access public 144 | * @return array 145 | * @static 146 | */ 147 | public static function getProfilerData() { 148 | return array( 149 | 'requestStart' => self::$_requestStart, 150 | 'requestEnd' => self::$_requestEnd, 151 | 'stack' => array_reverse(self::$_stack) 152 | ); 153 | } 154 | } -------------------------------------------------------------------------------- /Library/Core/Request.php: -------------------------------------------------------------------------------- 1 | 10 | * @package MVC 11 | */ 12 | class Request 13 | { 14 | /** 15 | * The URL that the user has visited. 16 | * 17 | * @access private 18 | * @var string 19 | */ 20 | private static $_url; 21 | 22 | /** 23 | * A single entry point to the $_GET superglobal 24 | * 25 | * @access private 26 | * @var array 27 | * @static 28 | */ 29 | private static $_get; 30 | 31 | /** 32 | * A single entry point to the $_POST superglobal 33 | * 34 | * @access private 35 | * @var array 36 | * @static 37 | */ 38 | private static $_post; 39 | 40 | /** 41 | * A single entry point to the $_SERVER superglobal. 42 | * 43 | * @access private 44 | * @var array 45 | * @static 46 | */ 47 | private static $_server; 48 | 49 | /** 50 | * Return a breakdown of the URL into their sections. 51 | * 52 | * Note: This function accepts a URL as a parameter but is only made 53 | * available for unit testing. You shouldn't use this param in your project. 54 | * 55 | * @access public 56 | * @param string $url Pass in a URL to use that instead of the request URL. 57 | * @return array 58 | * @static 59 | */ 60 | public static function setUrl($url = null) { 61 | // Set the URL 62 | self::$_url = $url ?: $_SERVER['REQUEST_URI']; 63 | 64 | // We want to remove the path root from the front request URL, stripping 65 | // .. out all of the information this class does not care about. 66 | if (strpos(self::$_url, Config::get('path', 'root')) === 0) { 67 | self::$_url = '/' . substr(self::$_url, strlen(Config::get('path', 'root'))); 68 | } 69 | } 70 | 71 | /** 72 | * Break a URL down into its relevant parts. 73 | * 74 | * This class will break it down into controller and index, and then all of 75 | * the GET parameters. If we are using a custom route then there will be no 76 | * controller/action in the URL. 77 | * 78 | * @access public 79 | * @param string $url The URL to parse. 80 | * @param boolean $setControllerAction Whether we need a controller/action from the URL. 81 | * @static 82 | */ 83 | public static function setUrlFragments($url = null, $setControllerAction = false) { 84 | // Breakdown the request the user made into manageable fragments 85 | $urlFragments = array_filter(explode('/', $url ?: self::$_url)); 86 | 87 | // Chunk them into variable => value 88 | $url = array(); 89 | $urlFragments = array_chunk($urlFragments, 2); 90 | 91 | // If this is a basic route (not custom) then grab the controller/action 92 | if ($setControllerAction) { 93 | $url = array( 94 | 'controller' => isset($urlFragments[0][0]) ? ucfirst($urlFragments[0][0]) : 'Index', 95 | 'action' => isset($urlFragments[0][1]) ? $urlFragments[0][1] : 'index' 96 | ); 97 | 98 | // And remove the first chunk so it is not set in the GET array 99 | unset($urlFragments[0]); 100 | } 101 | 102 | // The URL is now comprised of only GET variables, so set them 103 | // If a variable has no specified value then it is set to boolean true 104 | foreach ($urlFragments as $urlFragment) { 105 | $url[$urlFragment[0]] = isset($urlFragment[1]) 106 | ? $urlFragment[1] 107 | : true; 108 | } 109 | 110 | // Merge the route GETs with the explicitly stated GETs (?var=val) 111 | // We give priority to GET variables set via /var/value 112 | $_GET = array_merge($_GET, $url); 113 | 114 | // We want all requests to the GET and POST to be through this class to 115 | // .. provide a common interface, so we unset the globals. 116 | self::$_get = $_GET; 117 | self::$_post = $_POST; 118 | self::$_server = $_SERVER; 119 | unset($_GET, $_POST, $_SERVER); 120 | } 121 | 122 | /** 123 | * Return the URL that the user has visited. 124 | * 125 | * @access public 126 | * @param string $replaceSlashes What to replace '/' with. 127 | * @return string 128 | * @static 129 | */ 130 | public static function getUrl($replaceSlashes = null) { 131 | // Do we need to replace forward slashes with something? 132 | if ($replaceSlashes && self::$_url) { 133 | return str_replace('/', $replaceSlashes, self::$_url); 134 | } 135 | 136 | // Is the URL empty? 137 | else if (empty(self::$_url)) { 138 | return 'index'; 139 | } 140 | 141 | return self::$_url; 142 | } 143 | 144 | /** 145 | * Get a single GET variable. 146 | * 147 | * @access public 148 | * @param string $variable The variable we wish to return. 149 | * @param mixed $default If the variable is not found, this is returned. 150 | * @return mixed 151 | * @static 152 | */ 153 | public static function get($variable, $default = null) { 154 | return isset(self::$_get[$variable]) 155 | ? self::$_get[$variable] 156 | : $default; 157 | } 158 | 159 | /** 160 | * Get a single POST variable. 161 | * 162 | * @access public 163 | * @param string $variable The variable we wish to return. 164 | * @param mixed $default If the variable is not found, this is returned. 165 | * @return mixed 166 | * @static 167 | */ 168 | public static function post($variable, $default = null) { 169 | return isset(self::$_post[$variable]) 170 | ? self::$_post[$variable] 171 | : $default; 172 | } 173 | 174 | /** 175 | * Get a single SERVER variable. 176 | * 177 | * @access public 178 | * @param string $variable The variable we wish to return. 179 | * @param mixed $default If the variable is not found, this is returned. 180 | * @return mixed 181 | * @static 182 | */ 183 | public static function server($variable, $default = null) { 184 | return isset(self::$_server[$variable]) 185 | ? self::$_server[$variable] 186 | : $default; 187 | } 188 | 189 | /** 190 | * Check whether the users request was a standard request, or via Ajax. 191 | * 192 | * @access public 193 | * @return boolean 194 | * @static 195 | */ 196 | public static function isAjax() { 197 | return isset(self::$_server['HTTP_X_REQUESTED_WITH']) 198 | && strtolower(self::$_server['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'; 199 | } 200 | } -------------------------------------------------------------------------------- /Library/Core/Route.php: -------------------------------------------------------------------------------- 1 | foo/:bar 13 | * 14 | * Will turn the request URL of: 15 | * 16 | * foo/hello/my/variables/go/here/foobar 17 | * 18 | * Into the following GET variables (minus controller/action indexes): 19 | * 20 | * 21 | * array( 22 | * 'bar' => 'hello', 23 | * 'my' => 'variables', 24 | * 'go' => 'here', 25 | * 'foobar' => true 26 | * ) 27 | * 28 | * 29 | * @copyright Copyright (c) 2012-2013 Christopher Hill 30 | * @license http://www.opensource.org/licenses/mit-license.php The MIT License 31 | * @author Christopher Hill 32 | * @package MVC 33 | */ 34 | class Route 35 | { 36 | /** 37 | * The unique name of this route. 38 | * 39 | * @access public 40 | * @var string 41 | */ 42 | private $name; 43 | 44 | /** 45 | * The path that we want to match. 46 | * 47 | * @access public 48 | * @var string 49 | */ 50 | public $route; 51 | 52 | /** 53 | * The regex tests for each variable in the URL. 54 | * 55 | * Note: If no regex test is added for a variable then we use [\w\-]+ 56 | * 57 | * @access public 58 | * @var array 59 | */ 60 | public $paramFormats = array(); 61 | 62 | /** 63 | * To which controller/action we will dispatch this request. 64 | * 65 | * 66 | * array( 67 | * 'controller' => 'Foo', 68 | * 'action' => 'bar' 69 | * ) 70 | * 71 | * 72 | * @access public 73 | * @var array 74 | */ 75 | public $endpoint = array(); 76 | 77 | /** 78 | * Create the route. 79 | * 80 | * @access public 81 | * @param string $name The unique name of the route. 82 | */ 83 | public function __construct($name) { 84 | $this->name = $name; 85 | } 86 | 87 | /** 88 | * Set the path for this route. 89 | * 90 | * Paths will we be worked out relative to your path root (as defined in your 91 | * projects config.ini). They can contain any combination of strings or 92 | * variables. A variable is declared by starting with a colon (:) and then a 93 | * series of a-z characters. The following are all examples of valid paths. 94 | * 95 | *
      96 | *
    • foo
    • 97 | *
    • foo/bar/:acme
    • 98 | *
    • :foo/:bar/:acme
    • 99 | *
    • :foo/bar/:acme
    • 100 | *
    101 | * 102 | * To set the regex for these variables, use the setParamFormats() method. 103 | * 104 | * @access public 105 | * @param string $route The path for this route. 106 | * @return Core\Route The Route, for chainability. 107 | */ 108 | public function setRoute($route) { 109 | $this->route = $route; 110 | return $this; 111 | } 112 | 113 | /** 114 | * Set the regex patterns for each variable in the route. 115 | * 116 | * If no regex pattern is passed in for a variable then we use [\w\-]+ 117 | * 118 | * All of your patterns will automatically start with ^, end with $, and will 119 | * include the pattern modifiers i and u. So, if you were to pass in the 120 | * regex pattern on \d+ then it would be evaluated as /^\d+$/iu 121 | * 122 | * If your path is
    foo/:bar/:acme
    then your $formats array could 123 | * potentially look like: 124 | * 125 | * 126 | * array( 127 | * 'bar' => '\d+', 128 | * 'acme' => '(foo|bar)' 129 | * ) 130 | * 131 | * 132 | * @access public 133 | * @param array $formats The regex patterns. 134 | * @return Core\Route The Route, for chainability. 135 | */ 136 | public function setFormat($formats) { 137 | $this->paramFormats = $formats; 138 | return $this; 139 | } 140 | 141 | /** 142 | * The endpoint for this route. 143 | * 144 | * Can receive a controller and action. If no controller name is passed in 145 | * then the default 'index' controller is used. If no action name is passed 146 | * in then the default 'index' action is used. 147 | * 148 | * 149 | * array( 150 | * 'controller' => 'Foo', 151 | * 'action' => 'bar' 152 | * ) 153 | * 154 | * 155 | * @access public 156 | * @param array $endpoint Where we will dispatch this request. 157 | * @return Core\Route The Route, for chainability. 158 | */ 159 | public function setEndpoint($endpoint) { 160 | // Controller and action has been set? 161 | if (! isset($endpoint['controller'])) { $endpoint['controller'] = 'Index'; } 162 | if (! isset($endpoint['action'])) { $endpoint['action'] = 'index'; } 163 | 164 | $this->endpoint = $endpoint; 165 | return $this; 166 | } 167 | } -------------------------------------------------------------------------------- /Library/Core/Router.php: -------------------------------------------------------------------------------- 1 | 17 | *
  • i: PCRE_CASELESS: matching both uppercase and lowercase.
  • 18 | *
  • u: PCRE_UTF8: Make strings UTF-8.
  • 19 | * 20 | * 21 | * An example of how to use this class in the index.php is as follows: 22 | * 23 | * 24 | * addRoute('Foo') 32 | * ->setRoute('foo/:bar/:acme') 33 | * ->setFormat(array( 34 | * 'bar' => '\d+', 35 | * 'acme' => '[a-z0-9]+') 36 | * ) 37 | * ->setEndpoint(array( 38 | * 'controller' => 'Foo', 39 | * 'action' => 'bar') 40 | * ); 41 | * 42 | * // Start the application 43 | * new Core\Front('MyProject', $router); 44 | * 45 | * 46 | * Note: If no regex formats are supplied then we use the default of [\w\-]+ (any 47 | * alpha numeric character (a-z, 0-9, underscores) and dashes) 48 | * for the variable (:var) matching. 49 | * 50 | * Reverse routing 51 | * --------------- 52 | * URL's will often change. Defining them in a single place (the router) will 53 | * save you having to rewrite them in your View Helpers/Partials. It is also 54 | * safer because URL encoding will be taken care for you. 55 | * 56 | * @copyright Copyright (c) 2012-2013 Christopher Hill 57 | * @license http://www.opensource.org/licenses/mit-license.php The MIT License 58 | * @author Christopher Hill 59 | * @package MVC 60 | * 61 | * @see /Library/MyProject/View/Helper/Route.php 62 | */ 63 | class Router 64 | { 65 | /** 66 | * A collection of Route's that have been declared. 67 | * 68 | * @access private 69 | * @var array 70 | * @static 71 | */ 72 | private static $_routes = array(); 73 | 74 | /** 75 | * The portion of the request URL that the route has matched. 76 | * 77 | * @access private 78 | * @var string 79 | */ 80 | private $_routePath; 81 | 82 | /** 83 | * Add a new route to the router. 84 | * 85 | * @access public 86 | * @param string $routeName The name of the route. 87 | * @return Core\Route The new Route, for chainability. 88 | * @throws \InvalidArgumentException If the route name has already been declared. 89 | */ 90 | public function addRoute($routeName) { 91 | // We cannot allow duplicate route names for reversing reasons 92 | if (isset(self::$_routes[$routeName])) { 93 | throw new \InvalidArgumentException("The route {$routeName} has already been declared."); 94 | } 95 | 96 | self::$_routes[$routeName] = new Route($routeName); 97 | return self::$_routes[$routeName]; 98 | } 99 | 100 | /** 101 | * Start the routing procedure and find a valid route, if any. 102 | * 103 | * @access public 104 | */ 105 | public function route() { 106 | // Start the profiler 107 | Profiler::register('Core', 'Router'); 108 | 109 | // First, let's look at the URL the user supplied 110 | $requestUrl = array_values(array_filter(explode('/', Request::getUrl()))); 111 | $requestRoute = null; 112 | 113 | // Loop over each route and test to see if they are valid 114 | foreach (self::$_routes as $route) { 115 | if ($this->routeTest($requestUrl, $route)) { 116 | $requestRoute = $route; 117 | break; 118 | } 119 | } 120 | 121 | // We have completed the route matching 122 | // Finish the setup of the request object 123 | Profiler::register('Core', 'Request'); 124 | if ($requestRoute) { 125 | $_GET['controller'] = $route->endpoint['controller']; 126 | $_GET['action'] = $route->endpoint['action']; 127 | Request::setUrlFragments(str_replace($this->_routePath, '', Request::getUrl())); 128 | } else { 129 | Request::setUrlFragments(Request::getUrl(), true); 130 | } 131 | Profiler::deregister('Core', 'Request'); 132 | 133 | // Inform the event listener a request has been initialised 134 | Event::trigger( 135 | 'initRequest', 136 | array( 137 | 'controller' => Request::get('controller'), 138 | 'action' => Request::get('action') 139 | ) 140 | ); 141 | 142 | // And stop the profiler 143 | Profiler::deregister('Core', 'Router'); 144 | Profiler::deregister('Core', 'Front'); 145 | 146 | // And dispatch 147 | Dispatcher::loadController( 148 | Request::get('controller'), 149 | Request::get('action') 150 | ); 151 | } 152 | 153 | /** 154 | * Test to see if this route is valid against the URL. 155 | * 156 | * @access private 157 | * @param array $requestUrl The URL to test the route against. 158 | * @param Core\Route $route A Route declared by the application. 159 | * @return boolean 160 | */ 161 | private function routeTest($requestUrl, $route) { 162 | // Break apart the route URL 163 | $routeUrl = array_filter(explode('/', $route->route)); 164 | $routePath = ''; 165 | 166 | // Loop over each part of the route 167 | foreach ($routeUrl as $routeFragmentId => $routeFragment) { 168 | // Does this fragment actually exist in the request? 169 | if (! isset($requestUrl[$routeFragmentId])) { 170 | return false; 171 | } 172 | 173 | // Request has this fragment 174 | // If it is a variable, does the format match? 175 | else if (strpos($routeFragment, ':') === 0) { 176 | // Get the name of this fragment 177 | $routeFragmentName = substr($routeFragment, 1); 178 | 179 | // Get the format regex test 180 | $regexTest = isset($route->paramFormats[$routeFragmentName]) 181 | ? $route->paramFormats[$routeFragmentName] 182 | : '[\w\-]+'; 183 | 184 | // And test 185 | if (! preg_match("/^{$regexTest}$/iu", $requestUrl[$routeFragmentId])) { 186 | return false; 187 | } 188 | 189 | // Add this route declared variable to the GET request 190 | $_GET[$routeFragmentName] = $requestUrl[$routeFragmentId]; 191 | } 192 | 193 | // This is not a regex test, so just check the strings are the same 194 | else if ($routeFragment != $requestUrl[$routeFragmentId]) { 195 | return false; 196 | } 197 | 198 | // Build up the path 199 | $routePath .= '/' . $requestUrl[$routeFragmentId]; 200 | } 201 | 202 | // This route has passed all of the fragment tests 203 | $this->_routePath = $routePath; 204 | return true; 205 | } 206 | 207 | /** 208 | * Reverse the router. 209 | * 210 | * Make a URL out of a route name and parameters, rather than parsing one. 211 | * Note that this function does not care about URL paths! 212 | * 213 | * @access public 214 | * @param string $routeName The name of the route we wish to generate a URL for. 215 | * @param array $params The parameters that the route requires. 216 | * @return string 217 | * @throws \Exception If the route does not exist. 218 | * @static 219 | */ 220 | public static function reverse($routeName, $params = array()) { 221 | // Does the route actually exist? 222 | if (! isset(self::$_routes[$routeName])) { 223 | throw new Exception('The route ' . $routeName . ' does not exist.'); 224 | } 225 | 226 | // Create a container for the URL 227 | $url = self::$_routes[$routeName]->route; 228 | 229 | // And replace the variables in the 230 | foreach ($params as $variable => $value) { 231 | $url = str_replace(":{$variable}", urlencode($value), $url); 232 | } 233 | 234 | return Config::get('path', 'root') . $url; 235 | } 236 | } -------------------------------------------------------------------------------- /Library/Core/Store.php: -------------------------------------------------------------------------------- 1 | 10 | * $store = new Core\Store($StorageInterface); 11 | * $storeItem = $store->get('foo'); 12 | * 13 | * if ($storeItem) { 14 | * echo $storeItem; 15 | * } 16 | * 17 | * 18 | * @copyright Copyright (c) 2012-2013 Christopher Hill 19 | * @license http://www.opensource.org/licenses/mit-license.php The MIT License 20 | * @author Christopher Hill 21 | * @package MVC 22 | */ 23 | class Store implements Store\StorageInterface 24 | { 25 | /** 26 | * How we interact with our store items. 27 | * 28 | * @access private 29 | * @var StorageInterface 30 | */ 31 | private $_storageInterface; 32 | 33 | /** 34 | * Setup the store by stating which StorageInterface we wish to use. 35 | * 36 | * @access public 37 | * @param StorageInterface $storageInterface Which interface to interact with store items. 38 | */ 39 | public function __construct($storageInterface) { 40 | $this->_storageInterface = $storageInterface; 41 | } 42 | 43 | /** 44 | * Check whether the variable exists in the store. 45 | * 46 | * @access public 47 | * @param string $variable The name of the variable to check existence of. 48 | * @return boolean If the variable exists or not. 49 | */ 50 | public function has($variable) { 51 | return $this->_storageInterface->has($variable); 52 | } 53 | 54 | /** 55 | * Store a variable for use. 56 | * 57 | * @access public 58 | * @param string $variable The name of the variable to store. 59 | * @param mixed $value The data we wish to store. 60 | * @param boolean $overwrite Whether we are allowed to overwrite the variable. 61 | * @return boolean If we managed to store the variable. 62 | */ 63 | public function put($variable, $value, $overwrite = false) { 64 | return $this->_storageInterface->put($variable, $value); 65 | } 66 | 67 | /** 68 | * Return the variable's value from the store. 69 | * 70 | * @access public 71 | * @param string $variable The name of the variable in the store. 72 | * @return mixed 73 | */ 74 | public function get($variable) { 75 | return $this->_storageInterface->get($variable); 76 | } 77 | 78 | /** 79 | * Remove the variable in the store. 80 | * 81 | * @access public 82 | * @param string $variable The name of the variable to remove. 83 | * @return boolean If the variable was removed successfully. 84 | */ 85 | public function remove($variable) { 86 | return $this->_storageInterface->remove($variable); 87 | } 88 | } -------------------------------------------------------------------------------- /Library/Core/Store/Apc.php: -------------------------------------------------------------------------------- 1 | 10 | * @package MVC 11 | */ 12 | class Apc implements StorageInterface 13 | { 14 | /** 15 | * Check whether the variable exists in the store. 16 | * 17 | * @access public 18 | * @param string $variable The name of the variable to check existence of. 19 | * @return boolean If the variable exists or not. 20 | */ 21 | public function has($variable) { 22 | return apc_exists($variable); 23 | } 24 | 25 | /** 26 | * Store a variable for use. 27 | * 28 | * @access public 29 | * @param string $variable The name of the variable to store. 30 | * @param mixed $value The data we wish to store. 31 | * @param boolean $overwrite Whether we are allowed to overwrite the variable. 32 | * @return boolean If we managed to store the variable. 33 | * @throws Exception If the variable already exists when we try not to overwrite it. 34 | */ 35 | public function put($variable, $value, $overwrite = false) { 36 | // If it exists, and we do not want to overwrite, then throw exception 37 | if ($this->has($variable) && ! $overwrite) { 38 | throw new \Exception("{$variable} already exists in the store."); 39 | } 40 | 41 | // use apc_store() instead of apc_add() as add does not overwrite data 42 | apc_store($variable, $value); 43 | return $this->has($variable); 44 | } 45 | 46 | /** 47 | * Return the variable's value from the store. 48 | * 49 | * @access public 50 | * @param string $variable The name of the variable in the store. 51 | * @return mixed 52 | * @throws Exception If the variable does not exist. 53 | */ 54 | public function get($variable) { 55 | if (! $this->has($variable)) { 56 | throw new \Exception("{$variable} does not exist in the store."); 57 | } 58 | 59 | return apc_fetch($variable); 60 | } 61 | 62 | /** 63 | * Remove the variable in the store. 64 | * 65 | * @access public 66 | * @param string $variable The name of the variable to remove. 67 | * @return boolean If the variable was removed successfully. 68 | * @throws Exception If the variable does not exist. 69 | */ 70 | public function remove($variable) { 71 | if (! $this->has($variable)) { 72 | throw new \Exception("{$variable} does not exist in the store."); 73 | } 74 | 75 | return ! apc_delete($variable); 76 | } 77 | } -------------------------------------------------------------------------------- /Library/Core/Store/Cookie.php: -------------------------------------------------------------------------------- 1 | 10 | * @package MVC 11 | */ 12 | class Cookie implements StorageInterface 13 | { 14 | /** 15 | * Check whether the variable exists in the store. 16 | * 17 | * @access public 18 | * @param string $variable The name of the variable to check existence of. 19 | * @return boolean If the variable exists or not. 20 | */ 21 | public function has($variable) { 22 | return isset($_COOKIE[$variable]); 23 | } 24 | 25 | /** 26 | * Store a variable for use. 27 | * 28 | * @access public 29 | * @param string $variable The name of the variable to store. 30 | * @param mixed $value The data we wish to store. 31 | * @param int $expires How many seconds the cookie should be kept. 32 | * @param boolean $overwrite Whether we are allowed to overwrite the variable. 33 | * @return boolean If we managed to store the variable. 34 | * @throws Exception If the variable already exists when we try not to overwrite it. 35 | */ 36 | public function put($variable, $value, $expires = 1314000, $overwrite = false) { 37 | // If it exists, and we do not want to overwrite, then throw exception 38 | if ($this->has($variable) && ! $overwrite) { 39 | throw new \Exception("{$variable} already exists in the store."); 40 | } 41 | 42 | setcookie($variable, $value, $expires, '/', '.'); 43 | return $this->has($variable); 44 | } 45 | 46 | /** 47 | * Return the variable's value from the store. 48 | * 49 | * @access public 50 | * @param string $variable The name of the variable in the store. 51 | * @return mixed 52 | * @throws Exception If the variable does not exist. 53 | */ 54 | public function get($variable) { 55 | if (! $this->has($variable)) { 56 | throw new \Exception("{$variable} does not exist in the store."); 57 | } 58 | 59 | return $_COOKIE[$variable]; 60 | } 61 | 62 | /** 63 | * Remove the variable in the store. 64 | * 65 | * @access public 66 | * @param string $variable The name of the variable to remove. 67 | * @throws Exception If the variable does not exist. 68 | */ 69 | public function remove($variable) { 70 | if (! $this->has($variable)) { 71 | throw new \Exception("{$variable} does not exist in the store."); 72 | } 73 | 74 | // Remove the cookie by setting its expires in the past 75 | setcookie($variable, '', (time() - 3600)); 76 | } 77 | } -------------------------------------------------------------------------------- /Library/Core/Store/File.php: -------------------------------------------------------------------------------- 1 | 13 | * @package MVC 14 | */ 15 | class File implements StorageInterface 16 | { 17 | /** 18 | * Check whether the variable exists in the store. 19 | * 20 | * @access public 21 | * @param string $variable The name of the variable to check existence of. 22 | * @return boolean If the variable exists or not. 23 | */ 24 | public function has($variable) { 25 | $filePath = Config::get('path', 'base') . Config::get('path', 'cache') . $variable; 26 | 27 | if ( 28 | Request::server('REQUEST_METHOD') == 'POST' || // Unique content possible 29 | ! Config::get('cache', 'enable') || // Caching disabled 30 | ! file_exists($filePath) // Cache entry does not exist 31 | ) { 32 | return false; 33 | } 34 | 35 | // Check the time the item was created to see if it is stale 36 | return (Request::server('REQUEST_TIME') - filemtime($filePath)) <= 37 | Config::get('cache', 'life'); 38 | } 39 | 40 | /** 41 | * Store a variable for use. 42 | * 43 | * @access public 44 | * @param string $variable The name of the variable to store. 45 | * @param mixed $value The data we wish to store. 46 | * @param boolean $overwrite Whether we are allowed to overwrite the variable. 47 | * @return boolean If we managed to store the variable. 48 | * @throws Exception If the variable already exists when we try not to overwrite it. 49 | */ 50 | public function put($variable, $value, $overwrite = false) { 51 | // If it exists, and we do not want to overwrite, then throw exception 52 | if ($this->has($variable) && ! $overwrite) { 53 | throw new \Exception("{$variable} already exists in the store."); 54 | } 55 | 56 | file_put_contents( 57 | Config::get('path', 'base') . Config::get('path', 'cache') . $variable, 58 | $value 59 | ); 60 | } 61 | 62 | /** 63 | * Return the variable's value from the store. 64 | * 65 | * @access public 66 | * @param string $variable The name of the variable in the store. 67 | * @return bool 68 | * @throws Exception If the variable does not exist. 69 | */ 70 | public function get($variable) { 71 | if (! $this->has($variable)) { 72 | throw new \Exception("{$variable} does not exist in the store."); 73 | } 74 | 75 | return file_get_contents( 76 | Config::get('path', 'base') 77 | . Config::get('path', 'cache') 78 | . $variable 79 | ); 80 | } 81 | 82 | /** 83 | * Remove the variable in the store. 84 | * 85 | * @access public 86 | * @param string $variable The name of the variable to remove. 87 | * @return boolean If the variable was removed successfully. 88 | * @throws Exception If the variable does not exist. 89 | */ 90 | public function remove($variable) { 91 | if (! $this->has($variable)) { 92 | throw new \Exception("{$variable} does not exist in the store."); 93 | } 94 | 95 | return unlink(Config::get('path', 'base') . Config::get('path', 'cache') . $variable); 96 | } 97 | } -------------------------------------------------------------------------------- /Library/Core/Store/Memcache.php: -------------------------------------------------------------------------------- 1 | 10 | * @package MVC 11 | */ 12 | class Memcache implements StorageInterface 13 | { 14 | /** 15 | * The reference to the Memcache server. 16 | * 17 | * @access private 18 | * @var \Memcache or \Memcached 19 | */ 20 | private $_memcache; 21 | 22 | /** 23 | * Setup Memcache for storing data. 24 | * 25 | * @access public 26 | * @param string $server Whether we are using Memcache or Memcached. 27 | * @param string $host The location of the Memcache server. 28 | * @param string $port The port the Memcache server lives on. 29 | * @throws \Exception If passed an incorrect server. 30 | */ 31 | public function setup($server, $host, $port) { 32 | // Sanity check: Make sure we have received a valid 33 | switch ($server) { 34 | case 'Memcache' : $this->$_memcache = new \Memcache(); break; 35 | case 'Memcached' : $this->$_memcache = new \Memcached(); break; 36 | default : throw new \Exception("Unknown server {$server}."); 37 | } 38 | 39 | // Memcache instance created, add the server 40 | $this->$_memcache->addServer($host, $port); 41 | } 42 | 43 | /** 44 | * Check whether the variable exists in the store. 45 | * 46 | * @access public 47 | * @param string $variable The name of the variable to check existence of. 48 | * @return boolean If the variable exists or not. 49 | */ 50 | public function has($variable) { 51 | return (bool)$this->$_memcache->get($variable); 52 | } 53 | 54 | /** 55 | * Store a variable for use. 56 | * 57 | * @access public 58 | * @param string $variable The name of the variable to store. 59 | * @param mixed $value The data we wish to store. 60 | * @param boolean $overwrite Whether we are allowed to overwrite the variable. 61 | * @return boolean If we managed to store the variable. 62 | * @throws Exception If the variable already exists when we try not to overwrite it. 63 | */ 64 | public function put($variable, $value, $overwrite = false) { 65 | // If it exists, and we do not want to overwrite, then throw exception 66 | if ($this->has($variable) && ! $overwrite) { 67 | throw new \Exception("{$variable} already exists in the store."); 68 | } 69 | 70 | return $this->$_memcache->set($variable, $value); 71 | } 72 | 73 | /** 74 | * Return the variable's value from the store. 75 | * 76 | * @access public 77 | * @param string $variable The name of the variable in the store. 78 | * @return mixed 79 | * @throws Exception If the variable does not exist. 80 | */ 81 | public function get($variable) { 82 | if (! $this->has($variable)) { 83 | throw new \Exception("{$variable} does not exist in the store."); 84 | } 85 | 86 | return (bool)$this->$_memcache->get($variable); 87 | } 88 | 89 | /** 90 | * Remove the variable in the store. 91 | * 92 | * @access public 93 | * @param string $variable The name of the variable to remove. 94 | * @return boolean If the variable was removed successfully. 95 | * @throws Exception If the variable does not exist. 96 | */ 97 | public function remove($variable) { 98 | if (! $this->has($variable)) { 99 | throw new \Exception("{$variable} does not exist in the store."); 100 | } 101 | 102 | return (bool)$this->$_memcache->delete($variable); 103 | } 104 | } -------------------------------------------------------------------------------- /Library/Core/Store/Request.php: -------------------------------------------------------------------------------- 1 | 10 | * @package MVC 11 | */ 12 | class Request implements StorageInterface 13 | { 14 | /** 15 | * A store for all the variables set. 16 | * 17 | * @access public 18 | * @var array 19 | */ 20 | public $store; 21 | 22 | /** 23 | * Check whether the variable exists in the store. 24 | * 25 | * @access public 26 | * @param string $variable The name of the variable to check existence of. 27 | * @return boolean If the variable exists or not. 28 | */ 29 | public function has($variable) { 30 | return isset($this->$store[$variable]); 31 | } 32 | 33 | /** 34 | * Store a variable for use. 35 | * 36 | * @access public 37 | * @param string $variable The name of the variable to store. 38 | * @param mixed $value The data we wish to store. 39 | * @param boolean $overwrite Whether we are allowed to overwrite the variable. 40 | * @return boolean If we managed to store the variable. 41 | * @throws Exception If the variable already exists when we try not to overwrite it. 42 | */ 43 | public function put($variable, $value, $overwrite = false) { 44 | // If it exists, and we do not want to overwrite, then throw exception 45 | if ($this->has($variable) && ! $overwrite) { 46 | throw new \Exception("{$variable} already exists in the store."); 47 | } 48 | 49 | $this->$store[$variable] = $value; 50 | return $this->has($variable); 51 | } 52 | 53 | /** 54 | * Return the variable's value from the store. 55 | * 56 | * @access public 57 | * @param string $variable The name of the variable in the store. 58 | * @return mixed 59 | * @throws Exception If the variable does not exist. 60 | */ 61 | public function get($variable) { 62 | if (! $this->has($variable)) { 63 | throw new \Exception("{$variable} does not exist in the store."); 64 | } 65 | 66 | return $this->$store[$variable]; 67 | } 68 | 69 | /** 70 | * Remove the variable in the store. 71 | * 72 | * @access public 73 | * @param string $variable The name of the variable to remove. 74 | * @return boolean If the variable was removed successfully. 75 | * @throws Exception If the variable does not exist. 76 | */ 77 | public function remove($variable) { 78 | if (! $this->has($variable)) { 79 | throw new \Exception("{$variable} does not exist in the store."); 80 | } 81 | 82 | // Unset the variable 83 | unset($this->$store[$variable]); 84 | 85 | // Was it removed 86 | return ! $this->has($variable); 87 | } 88 | } -------------------------------------------------------------------------------- /Library/Core/Store/Session.php: -------------------------------------------------------------------------------- 1 | 10 | * @package MVC 11 | */ 12 | class Session implements StorageInterface 13 | { 14 | /** 15 | * Check whether the variable exists in the store. 16 | * 17 | * @access public 18 | * @param string $variable The name of the variable to check existence of. 19 | * @return boolean If the variable exists or not. 20 | */ 21 | public function has($variable) { 22 | return isset($_SESSION[$variable]); 23 | } 24 | 25 | /** 26 | * Store a variable for use. 27 | * 28 | * @access public 29 | * @param string $variable The name of the variable to store. 30 | * @param mixed $value The data we wish to store. 31 | * @param boolean $overwrite Whether we are allowed to overwrite the variable. 32 | * @return boolean If we managed to store the variable. 33 | * @throws Exception If the variable already exists when we try not to overwrite it. 34 | */ 35 | public function put($variable, $value, $overwrite = false) { 36 | // If it exists, and we do not want to overwrite, then throw exception 37 | if ($this->has($variable) && ! $overwrite) { 38 | throw new \Exception($variable . ' already exists in the store.'); 39 | } 40 | 41 | $_SESSION[$variable] = serialize($value); 42 | return $this->has($variable); 43 | } 44 | 45 | /** 46 | * Return the variable's value from the store. 47 | * 48 | * @access public 49 | * @param string $variable The name of the variable in the store. 50 | * @return mixed 51 | * @throws Exception If the variable does not exist. 52 | */ 53 | public function get($variable) { 54 | if (! $this->has($variable)) { 55 | throw new \Exception("{$variable} does not exist in the store."); 56 | } 57 | 58 | return unserialize($_SESSION[$variable]); 59 | } 60 | 61 | /** 62 | * Remove the variable in the store. 63 | * 64 | * @access public 65 | * @param string $variable The name of the variable to remove. 66 | * @return boolean If the variable was removed successfully. 67 | * @throws Exception If the variable does not exist. 68 | */ 69 | public function remove($variable) { 70 | if (! $this->has($variable)) { 71 | throw new \Exception("{$variable} does not exist in the store."); 72 | } 73 | 74 | // Unset the variable 75 | unset($_SESSION[$variable]); 76 | 77 | // Was it removed 78 | return ! $this->has($variable); 79 | } 80 | } -------------------------------------------------------------------------------- /Library/Core/Store/StorageInterface.php: -------------------------------------------------------------------------------- 1 | 10 | * @package MVC 11 | */ 12 | interface StorageInterface 13 | { 14 | /** 15 | * Check whether the variable exists in the store. 16 | * 17 | * @access public 18 | * @param string $variable The name of the variable to check existence of. 19 | * @return boolean If the variable exists or not. 20 | */ 21 | public function has($variable); 22 | 23 | /** 24 | * Store a variable for use. 25 | * 26 | * @access public 27 | * @param string $variable The name of the variable to store. 28 | * @param mixed $value The data we wish to store. 29 | * @param boolean $overwrite Whether we are allowed to overwrite the variable. 30 | * @return boolean If we managed to store the variable. 31 | */ 32 | public function put($variable, $value, $overwrite = false); 33 | 34 | /** 35 | * Return the variable's value from the store. 36 | * 37 | * @access public 38 | * @param string $variable The name of the variable in the store. 39 | * @return mixed 40 | */ 41 | public function get($variable); 42 | 43 | /** 44 | * Remove the variable in the store. 45 | * 46 | * @access public 47 | * @param string $variable The name of the variable to remove. 48 | * @return boolean If the variable was removed successfully. 49 | */ 50 | public function remove($variable); 51 | } -------------------------------------------------------------------------------- /Library/Core/Validate.php: -------------------------------------------------------------------------------- 1 | 14 | * if (Core\Validate::is('cjhill@gmail.com', 'email')) { 15 | * // Houston, we have a valid email address 16 | * } 17 | * 18 | * 19 | * Mass mode example 20 | * ----------------- 21 | * 22 | * 23 | * $form = new Validate(array( 24 | * 'Email address' => array( 25 | * 'value' => 'cjhill@gmail.com', 26 | * 'tests' => array( 27 | * 'required' => true, 28 | * 'is' => 'email' 29 | * ) 30 | * ), 31 | * 'Age' => array( 32 | * 'value' => 20, 33 | * 'tests' => array( 34 | * 'between' => array('min' => 13, 'max' => 99) 35 | * ) 36 | * ), 37 | * 'Password' => array( 38 | * 'value' => 's3cr37', 39 | * 'tests' => array( 40 | * 'required' => true, 41 | * 'required_with' => array('Email address') 42 | * 'length' => array('min' => 8) 43 | * ) 44 | * ) 45 | * )); 46 | * 47 | * if (! $form->isValid()) { 48 | * // Errors 49 | * var_dump($validate->getErrors()); 50 | * } 51 | * 52 | * 53 | * @copyright Copyright (c) 2012-2013 Christopher Hill 54 | * @license http://www.opensource.org/licenses/mit-license.php The MIT License 55 | * @author Christopher Hill 56 | * @package MVC 57 | * 58 | * @todo Integrate with a Core\Notice class. 59 | */ 60 | class Validate 61 | { 62 | /** 63 | * The inputs that need to be validated. 64 | * 65 | * List of possible tests to perform: 66 | * 67 | *
      68 | *
    • 'required' => true
    • 69 | *
    • 'required_with' => array('foo', 'bar')
    • 70 | *
    • 71 | * You can explicitly set both min and max, or just one: 72 | *
        73 | *
      • 'length' => array('min' => 5, 'max' => 10)
      • 74 | *
      • 'length' => array('min' => 5)
      • 75 | *
      • 'length' => array('max' => 10)
      • 76 | *
      77 | *
    • 78 | *
    • 'is' => 'boolean|email|float|int|ip|url'
    • 79 | *
    • 'exactly' => array('foobar', 'acme')/li> 80 | *
    • 'between' => array('min' => 10, 'max' => 100)
    • 81 | *
    • 'regex' => '/[a-z0-9]/gi' 82 | *
    83 | * 84 | * @access private 85 | * @var array 86 | */ 87 | private $_input = array(); 88 | 89 | /** 90 | * Whether we have run the validation tests on the inputs. 91 | * 92 | * @access private 93 | * @var boolean 94 | */ 95 | private $_validated = false; 96 | 97 | /** 98 | * A collection of errors that have occurred with the inputs and their tests. 99 | * 100 | * @access private 101 | * @var array 102 | */ 103 | private $_error = array(); 104 | 105 | /** 106 | * Set the inputs that need to be validated. 107 | * 108 | * @access public 109 | * @param array $input The inputs that need to be validated. 110 | * @param boolean $autorun Whether we should run the validation automatically. 111 | */ 112 | public function __construct($input, $autorun = true) { 113 | // Set the inputs 114 | $this->_input = $input; 115 | 116 | // Run the tests automatically without further instruction 117 | if ($autorun) { 118 | $this->validate(); 119 | } 120 | } 121 | 122 | /** 123 | * Run the validation on each input that has been passed in. 124 | * 125 | * @access public 126 | * @return boolean Whether the validation was successful or not. 127 | */ 128 | public function validate() { 129 | // We only ever need to run once 130 | if ($this->_validated) { 131 | return $this->getIsValid(); 132 | } 133 | 134 | // Loop through each input 135 | foreach ($this->_input as $inputName => $inputInfo) { 136 | // Sanity check that we have tests to perform 137 | if (! isset($inputInfo['tests'])) { 138 | continue; 139 | } 140 | 141 | // Loop over each test that we need to run 142 | foreach ($inputInfo['tests'] as $testName => $testParams) { 143 | // Run the test 144 | // Note: The function to test might not exist, so we can catch with __call. 145 | if (! self::$testName($inputInfo['value'], $testParams)) { 146 | // The test was unsuccessful, report error 147 | $this->addError($inputName, $testName); 148 | } 149 | } 150 | } 151 | 152 | // We have run the validation tests, so do not run again 153 | $this->_validated = true; 154 | 155 | // Pass back whether it was successful or not 156 | return $this->isValid(); 157 | } 158 | 159 | /** 160 | * Test to see if the input actually has a value. 161 | * 162 | * @access private 163 | * @param string $inputValue The value of the input. 164 | * @param array $testParams The parameters for this test. 165 | * @return boolean 166 | */ 167 | private function required($inputValue, $testParams = array()) { 168 | return ! empty($inputValue); 169 | } 170 | 171 | /** 172 | * Test to see if the input's other required fields are valid and exist. 173 | * 174 | * We only test to see if the required inputs have been defined. Any further 175 | * validating should be done on the required input. 176 | * 177 | * @access private 178 | * @param string $inputValue The value of the input. 179 | * @param array $testParams The parameters for this test. 180 | * @return boolean 181 | */ 182 | private function requiredWith($inputValue, $testParams) { 183 | // Loop over each of the required other fields 184 | foreach ($testParams as $inputRequired) { 185 | // Do we even have a reference to this input? 186 | if (! isset($this->_input[$inputRequired])) { 187 | return false; 188 | } 189 | 190 | // Does the parameter actually pass the required test? 191 | else if (! $this->required($this->_input[$inputRequired]['value'])) { 192 | return false; 193 | } 194 | } 195 | 196 | return true; 197 | } 198 | 199 | /** 200 | * Test to see if the input's length is within bounds. 201 | * 202 | * Note: This function can deal with a min, a max, or a min and a max boundary. 203 | * 204 | * @access public 205 | * @param string|array $inputValue The value of the input. 206 | * @param array $testParams The parameters for this test. 207 | * @return boolean 208 | * @throws \Exception If neither a min or max boundary is specified. 209 | * @static 210 | */ 211 | public static function length($inputValue, $testParams) { 212 | // We need to have at least one boundary 213 | if (! isset($testParams['min']) && ! isset($testParams['max'])) { 214 | throw new \Exception('Length test requires at least a min or max boundary.'); 215 | } 216 | 217 | // Get the string length of the inputs value 218 | // Note: We could have been given a string or array 219 | $length = is_array($inputValue) 220 | ? count($inputValue) 221 | : strlen($inputValue); 222 | 223 | // Length is less than the minimum 224 | if (isset($testParams['min']) && $length < $testParams['min']) { 225 | return false; 226 | } 227 | 228 | // Length is more than the maximum 229 | else if (isset($testParams['max']) && $length > $testParams['max']) { 230 | return false; 231 | } 232 | 233 | return true; 234 | } 235 | 236 | /** 237 | * Test to see if the input is a specific type. 238 | * 239 | * @access public 240 | * @param string $inputValue The value of the input. 241 | * @param array $testParams The parameters for this test. 242 | * @return boolean 243 | * @static 244 | */ 245 | public static function is($inputValue, $testParams) { 246 | // Due to a bug in filter_var we handle booleans differently 247 | // @see https://bugs.php.net/bug.php?id=49510 248 | if ($testParams == 'boolean') { 249 | return is_bool($inputValue); 250 | } 251 | 252 | // Work out which filter we should use 253 | switch ($testParams) { 254 | case 'email' : $filter = \FILTER_VALIDATE_EMAIL; break; 255 | case 'float' : $filter = \FILTER_VALIDATE_FLOAT; break; 256 | case 'int' : $filter = \FILTER_VALIDATE_INT; break; 257 | case 'ip' : $filter = \FILTER_VALIDATE_IP; break; 258 | case 'url' : $filter = \FILTER_VALIDATE_URL; break; 259 | default : return false; 260 | } 261 | 262 | // And try to filer the input with this type 263 | return filter_var($inputValue, $filter); 264 | } 265 | 266 | /** 267 | * Test to see if the input matches exactly another value. 268 | * 269 | * Note: We also check for same data types, so "1" and 1 will fail. 270 | * 271 | * @access public 272 | * @param string $inputValue The value of the input. 273 | * @param array $testParams The parameters for this test. 274 | * @return boolean 275 | * @static 276 | */ 277 | public static function exactly($inputValue, $testParams) { 278 | return in_array($inputValue, $testParams, true); 279 | } 280 | 281 | /** 282 | * Test to see if the input is between two numbers. 283 | * 284 | * Note: We check the datatype of the input first to make sure it is an int. 285 | * 286 | * @access public 287 | * @param string $inputValue The value of the input. 288 | * @param array $testParams The parameters for this test. 289 | * @return boolean 290 | * @throws \Exception If the user has supplied no min and max boundary. 291 | * @static 292 | */ 293 | public static function between($inputValue, $testParams) { 294 | // User has supplied both boundaries? 295 | if (! isset($testParams['min']) || ! isset($testParams['max'])) { 296 | throw new \Exception('Between expects a min and max boundary.'); 297 | } 298 | 299 | // Make sure the user has given us an int 300 | else if (! self::is($inputValue, 'int')) { 301 | return false; 302 | } 303 | 304 | // Check to see if the input value is between the two boundaries 305 | else if ($inputValue < $testParams['min'] || $inputValue > $testParams['max']) { 306 | return false; 307 | } 308 | 309 | return true; 310 | } 311 | 312 | /** 313 | * Whether the input matches a regular expression. 314 | * 315 | * @access public 316 | * @param string $inputValue The value of the input. 317 | * @param array $testParams The parameters for this test. 318 | * @return boolean 319 | * @static 320 | */ 321 | public static function regex($inputValue, $testParams) { 322 | return preg_match($inputValue, $testParams); 323 | } 324 | 325 | /** 326 | * Add a human readable error message. 327 | * 328 | * @access private 329 | * @param string $inputName The value of the input. 330 | * @param array $testName The parameters for this test. 331 | * @return boolean 332 | */ 333 | private function addError($inputName, $testName) { 334 | // Work out the best response for the error message 335 | switch ($testName) { 336 | case 'required' : $error = ' is required.'; break; 337 | case 'requiredWith' : $error = ' has not been completed.'; break; 338 | case 'length' : $error = ' length is incorrect.'; break; 339 | case 'is' : $error = ' type is incorrect.'; break; 340 | case 'exactly' : $error = ' is invalid.'; break; 341 | case 'between' : $error = ' is invalid.'; break; 342 | default : $error = ' is invalid.'; break; 343 | } 344 | 345 | // Add to the error array 346 | $this->_error[$inputName][] = $inputName . $error; 347 | } 348 | 349 | /** 350 | * Return the outcome of the validation. 351 | * 352 | * @access public 353 | * @return boolean 354 | */ 355 | public function isValid() { 356 | return count($this->_error) <= 0; 357 | } 358 | 359 | /** 360 | * Return the errors that we found with the inputs. 361 | * 362 | * @access public 363 | * @return array 364 | */ 365 | public function getErrors() { 366 | return $this->_error; 367 | } 368 | 369 | /** 370 | * Return if a single input had an error. 371 | * 372 | * @access public 373 | * @param string $inputName The name of the input. 374 | * @return boolean 375 | */ 376 | public function hadError($inputName) { 377 | return isset($this->_error[$inputName]); 378 | } 379 | 380 | /** 381 | * The user has passed in a validating test that we do not know of. 382 | * 383 | * @access public 384 | * @param string $testName The name of the method the user called. 385 | * @param array $params The parameters that the user passed in. 386 | * @throws \Exception If called then the user has entered an invalid test name. 387 | * @magic 388 | */ 389 | public function __call($testName, $params) { 390 | throw new \Exception($testName . ' is not a valid test.'); 391 | } 392 | } -------------------------------------------------------------------------------- /Library/Core/View.php: -------------------------------------------------------------------------------- 1 | 11 | * @package MVC 12 | */ 13 | class View 14 | { 15 | /** 16 | * The controller that we need to render. 17 | * 18 | * @access public 19 | * @var string 20 | */ 21 | public $controller = 'index'; 22 | 23 | /** 24 | * The action that we need to render. 25 | * 26 | * @access public 27 | * @var string 28 | */ 29 | public $action = 'index'; 30 | 31 | /** 32 | * Which layout we are going to use for this view. 33 | * 34 | * @access public 35 | * @var string 36 | */ 37 | public $layout = 'default'; 38 | 39 | /** 40 | * The variables that we want to pass to the view. 41 | * 42 | * @access public 43 | * @var array 44 | */ 45 | public $_variables = array(); 46 | 47 | /** 48 | * The interface for caching. 49 | * 50 | * @access private 51 | * @var Cache 52 | */ 53 | private $_cache; 54 | 55 | /** 56 | * The View has been created. 57 | * 58 | * We need to give a reference to ourself to the View Helper. 59 | * 60 | * @var CacheInterface $cacheInterface The Interface to use for caching. 61 | * @access public 62 | */ 63 | public function __construct($cacheInterface) { 64 | ViewHelper::$_view = $this; 65 | $this->_cache = new Store($cacheInterface); 66 | } 67 | 68 | /** 69 | * Add a variable to the view. 70 | * 71 | * These variables will be made available to the view. Any variable that has 72 | * already been defined will be overwritten. 73 | * 74 | * @access public 75 | * @param string $variable The variable we wish to add to the view. 76 | * @param string $value The value of the variable. 77 | */ 78 | public function addVariable($variable, $value) { 79 | $this->_variables[$variable] = $value; 80 | } 81 | 82 | /** 83 | * Returns a set variable if it exists. 84 | * 85 | * @access public 86 | * @param string $variable The variable that we wish to retrieve from the view. 87 | * @return mixed 88 | */ 89 | public function getVariable($variable) { 90 | return isset($this->_variables[$variable]) 91 | ? $this->_variables[$variable] 92 | : false; 93 | } 94 | 95 | /** 96 | * Render the page. 97 | * 98 | * @access public 99 | * @throws Exception If the view does not exist. 100 | */ 101 | public function render() { 102 | // Can we use a cache to speed things up? 103 | $cacheItem = $this->_cache->has(Request::getUrl('_')) 104 | ? $this->_cache->get(Request::getUrl('_')) 105 | : false; 106 | // If the cache object exists then it means the controller wants to use caching 107 | // However, the action might have disabled it 108 | if ($cacheItem) { 109 | // The cache is enabled and there is an instance of the file in cache 110 | $this->_variables['viewContent'] = $cacheItem; 111 | } 112 | 113 | // Nope, there is no cache 114 | else { 115 | // Set the action location we need to run 116 | $templateUrlAction = Config::get('path', 'base') . Config::get('path', 'project') 117 | . 'View/Script/' . $this->controller . '/' . $this->action . '.phtml'; 118 | 119 | // Does the view file exist? 120 | if (! file_exists($templateUrlAction)) { 121 | throw new \Exception('The view ' . $this->action . ' does not exist in ' . $this->controller); 122 | } 123 | 124 | // And parse the action's script 125 | $this->_variables['viewContent'] = $this->parse( 126 | $templateUrlAction, 127 | $this->_variables, 128 | Request::getUrl('_') 129 | ); 130 | } 131 | 132 | // This is the end of the controller's work 133 | Profiler::deregister('action', $this->action); 134 | Profiler::deregister('controller', $this->controller); 135 | 136 | // Now start to wrap the view content in the layout 137 | if (! $this->layout) { 138 | // No template, thanks 139 | $template = $this->_variables['viewContent']; 140 | } else { 141 | // Set the action location we need to run 142 | $templateUrlLayout = Config::get('path', 'base') . Config::get('path', 'project') 143 | . 'Layout/' . $this->layout . '.phtml'; 144 | 145 | // And parse the action's script 146 | $template = $this->parse( 147 | $templateUrlLayout, 148 | $this->_variables 149 | ); 150 | } 151 | 152 | // Inform the event listener that we are about to shutdown 153 | Event::trigger( 154 | 'initShutdown', 155 | array( 156 | 'controller' => $this->controller, 157 | 'action' => $this->action 158 | ) 159 | ); 160 | 161 | // Stop the profiling, we're done 162 | Profiler::stop(); 163 | 164 | // Add the profile to the page output? 165 | if (Config::get('profiler', 'enable', true)) { 166 | $template .= $this->profiler(Profiler::getProfilerData()); 167 | } 168 | 169 | // And now, the journey ends 170 | echo $template; 171 | } 172 | 173 | /** 174 | * Parse a template, also caching if desired. 175 | * 176 | * @param string $template The full path of the template file. 177 | * @param array $variables The variables we wish to replace. 178 | * @param string $cacheName What to call the cached file. 179 | * @return string 180 | */ 181 | public function parse($template, $variables, $cacheName = null) { 182 | // Start profiling 183 | $templateName = str_replace( 184 | Config::get('path', 'base') . Config::get('path', 'project'), 185 | '', 186 | $template 187 | ); 188 | Profiler::register('Parse', $templateName); 189 | 190 | // The view exists 191 | // Extract the variables that have been set 192 | if ($variables) { 193 | extract($variables); 194 | } 195 | 196 | // Enable object buffering 197 | ob_start(); 198 | 199 | // And include the file for parsing 200 | include $template; 201 | 202 | // Get the content of the view after parsing, and dispose of the buffer 203 | $content = ob_get_contents(); 204 | ob_end_clean(); 205 | 206 | // If we are using the cache then save it 207 | if ($cacheName && Config::get('cache', 'enable')) { 208 | $this->_cache->put( 209 | $cacheName, 210 | $content . '' 211 | ); 212 | } 213 | 214 | // And return the result of this parse 215 | Profiler::deregister('Parse', $templateName); 216 | return $content; 217 | } 218 | 219 | /** 220 | * Provides a nice interface to call view helpers. 221 | * 222 | * This is a magic function, so any calls to the view/view helper which do not 223 | * exist will end up here. We only pass through the first parameter to make for 224 | * a nicer implementation in each view helper. This is why it needs to be an array. 225 | * 226 | * @access public 227 | * @param string $helperName The View Helper that we wish to use. 228 | * @param array $param The parameters that need to be passed to the View Helper. 229 | * @return string 230 | * @magic 231 | */ 232 | public function __call($helperName, $param) { 233 | // Try and instantiate the helper 234 | // Note: Calling section_Author will translate to Section\Author 235 | $viewHelperClassName = Config::get('settings', 'project') 236 | . '\View\Helper\\' 237 | . str_replace('_', '\\', $helperName); 238 | $viewHelper = new $viewHelperClassName(); 239 | 240 | // Render and return 241 | Profiler::register('Helper', $helperName); 242 | $content = $viewHelper->render(isset($param[0]) ? $param[0] : array()); 243 | Profiler::deregister('Helper', $helperName); 244 | return $content; 245 | } 246 | } -------------------------------------------------------------------------------- /Library/Core/ViewHelper.php: -------------------------------------------------------------------------------- 1 | 10 | * @package MVC 11 | */ 12 | class ViewHelper 13 | { 14 | /** 15 | * The View, so all View Helpers can interact with it. 16 | * 17 | * @access public 18 | * @var View 19 | */ 20 | public static $_view; 21 | 22 | /** 23 | * Parses a template file and returns the converted HTML. 24 | * 25 | * @access protected 26 | * @param string $template The name of the partial to render. 27 | * @param array $variables An array of variables to replace. 28 | * @param mixed $cacheName null to not cache, string otherwise. 29 | * @return string Converted template file into HTML. 30 | */ 31 | protected function renderPartial($template, $variables, $cacheName = null) { 32 | return self::$_view->parse( 33 | Config::get('path', 'base') . Config::get('path', 'project') 34 | . 'View/Partial/' . $template . '.phtml', 35 | $variables, 36 | $cacheName 37 | ); 38 | } 39 | 40 | /** 41 | * Returns the view. 42 | * 43 | * This function exists to provide a common interface for accessing the View. 44 | * 45 | * @access public 46 | * @param string $variableName The name of the variable to return. 47 | * @return View 48 | * @magic 49 | */ 50 | public function __get($variableName) { 51 | return self::$_view; 52 | } 53 | 54 | /** 55 | * If the View Helper is echo'd then we need to render it. 56 | * 57 | * @access public 58 | * @return string 59 | * @magic 60 | */ 61 | public function __toString() { 62 | return $this->render(); 63 | } 64 | } -------------------------------------------------------------------------------- /Library/MyProject/Cache/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisjhill/MVC/fa863edfa034fcdadea243f2d14419c88d54e90e/Library/MyProject/Cache/.gitignore -------------------------------------------------------------------------------- /Library/MyProject/Controller/Error.php: -------------------------------------------------------------------------------- 1 | view->addVariable( 17 | 'foo', // Name of the variable 18 | $this->request->get('foo') // Variable passed in via the URL 19 | ); 20 | } 21 | 22 | /** 23 | * The error action. 24 | * 25 | * This action is run if you try and call an action that does not exist. 26 | * 27 | * @access public 28 | */ 29 | public function errorAction() { 30 | // Do nothing 31 | } 32 | } -------------------------------------------------------------------------------- /Library/MyProject/EventListener.php: -------------------------------------------------------------------------------- 1 | 13 | *
  • controller - The name of the controller being loaded.
  • 14 | *
  • action - The name of the action being loaded.
  • 15 | * 16 | * 17 | * @access public 18 | * @param array $params Parameters passed into this state update. 19 | * @static 20 | */ 21 | public static function initRequest($params) { 22 | /* Do nothing */ 23 | } 24 | 25 | /** 26 | * A controller has been loaded. 27 | * 28 | * This event is triggered just after a controller is initiated, and just 29 | * before the action is loaded. 30 | * 31 | *
      32 | *
    • controller - The Core\Controller object.
    • 33 | *
    34 | * 35 | * @access public 36 | * @param array $params Parameters passed into this state update. 37 | * @static 38 | */ 39 | public static function initController($params) { 40 | // Add variables to the view 41 | $params['controller']->view->addVariable('urlRoot', Core\Config::get('path', 'root')); 42 | } 43 | 44 | /** 45 | * An action is just about to be loaded. 46 | * 47 | * This event is triggered just before an action is loaded. 48 | * 49 | *
      50 | *
    • controller - The Core\Controller object.
    • 51 | *
    • action - The name of the action which is about to be loaded.
    • 52 | *
    53 | * 54 | * @access public 55 | * @param array $params Parameters passed into this state update. 56 | * @static 57 | */ 58 | public static function initAction($params) { 59 | /* Do nothing */ 60 | } 61 | 62 | /** 63 | * A request has completed, and ready to be shutdown. 64 | * 65 | *
      66 | *
    • controller - The name of the controller that was rendered.
    • 67 | *
    • action - The name of the action that was rendered.
    • 68 | *
    69 | * 70 | * @access public 71 | * @param array $params Parameters passed into this state update. 72 | * @static 73 | */ 74 | public static function initShutdown($params) { 75 | /* Do nothing */ 76 | } 77 | } -------------------------------------------------------------------------------- /Library/MyProject/Layout/default.phtml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | PHP MVC Framework 8 | 9 | 10 | 11 | 12 |
    13 |
    14 |

    Hello World from the default layout!

    15 | 16 |
    17 | 18 | 19 |
    20 |
    21 | 22 | -------------------------------------------------------------------------------- /Library/MyProject/Model/User.php: -------------------------------------------------------------------------------- 1 | 11 | * @package MVC 12 | */ 13 | class User extends Core\Model 14 | { 15 | /** 16 | * The name of the table where your users live. 17 | * 18 | * @access protected 19 | * @var string 20 | */ 21 | protected $_table = 'user'; 22 | 23 | /** 24 | * The primary key for the the user table. 25 | * 26 | * @access protected 27 | * @var string 28 | */ 29 | protected $_primaryKey = 'user_id'; 30 | } -------------------------------------------------------------------------------- /Library/MyProject/View/Helper/Profiler.php: -------------------------------------------------------------------------------- 1 | 9 | * @author Christopher Hill 10 | * @since 25/04/2013 11 | */ 12 | class Profiler extends Core\ViewHelper 13 | { 14 | /** 15 | * Render the waterfall profiler. 16 | * 17 | * 18 | * array( 19 | * 'requestStart' => 123, 20 | * 'requestEnd' => 456, 21 | * 'stack' => array() 22 | * ) 23 | * 24 | * 25 | * @access public 26 | * @param array $params The data that we received from the Core\Profiler. 27 | * @return string 28 | */ 29 | public function render($params) { 30 | // Work out the total runtime and set the trace item HTML 31 | $profilerRuntime = $params['requestEnd'] - $params['requestStart']; 32 | $profilerTraceHtml = ''; 33 | 34 | // Loop over each trace 35 | foreach ($params['stack'] as $traceId => $trace) { 36 | // Work out where to position this trace in the waterfall 37 | $traceStart = (($trace['start'] - $params['requestStart']) / $profilerRuntime) * 100; 38 | $traceEnd = (($trace['end'] - $params['requestStart']) / $profilerRuntime) * 100; 39 | 40 | // Get the trace HTML 41 | $profilerTraceHtml .= $this->renderPartial('ProfilerItem', array( 42 | 'traceType' => strtolower($trace['type']), 43 | 'traceTitle' => $trace['name'], 44 | 'traceStart' => $traceStart, 45 | 'traceEnd' => $traceEnd - $traceStart, 46 | 'traceTime' => number_format($trace['end'] - $trace['start'], 3), 47 | 'traceMem' => number_format($trace['mem'] / 1024 / 1024, 3) 48 | )); 49 | } 50 | 51 | // And place the trace items into the profiler container 52 | return $this->renderPartial('Profiler', array( 53 | 'profilerRuntime' => number_format($profilerRuntime, 3), 54 | 'profilerTraceHtml' => $profilerTraceHtml 55 | )); 56 | } 57 | } -------------------------------------------------------------------------------- /Library/MyProject/View/Helper/Route.php: -------------------------------------------------------------------------------- 1 | 9 | * @author Christopher Hill 10 | * @since 09/04/2013 11 | */ 12 | class Route 13 | { 14 | /** 15 | * Create a link from a pre-defined route. 16 | * 17 | * 18 | * array( 19 | * 'route' => 'Foo', 20 | * 'params' => array( 21 | * 'bar' => 'Hello', 22 | * 'amce' => 'World' 23 | * ) 24 | * ) 25 | * 26 | * 27 | * @access public 28 | * @param array $param Parameters used to build the URL. 29 | * @return string 30 | */ 31 | public function render($param = array()) { 32 | return Core\Config::get('path', 'root') . Core\Router::reverse( 33 | $param['route'], 34 | $param['params'] 35 | ); 36 | } 37 | } -------------------------------------------------------------------------------- /Library/MyProject/View/Helper/Safe.php: -------------------------------------------------------------------------------- 1 | 10 | * @author Christopher Hill 11 | * @since 18/01/2012 12 | */ 13 | class Safe 14 | { 15 | /** 16 | * Ensure that a string is safe to be outputted to the browser. 17 | * 18 | * 19 | * array( 20 | * 'string' => 'Evil string' 21 | * ) 22 | * 23 | * 24 | * @access public 25 | * @param array $params The string that we wish to make safe to output. 26 | * @return string 27 | */ 28 | public function render($params) { 29 | return (new Format())->safeHtml($params['string']); 30 | } 31 | } -------------------------------------------------------------------------------- /Library/MyProject/View/Helper/Test.php: -------------------------------------------------------------------------------- 1 | 13 | * @author Christopher Hill 14 | * @since 18/01/2012 15 | */ 16 | class Test extends Core\ViewHelper 17 | { 18 | /** 19 | * Render a View Helper. 20 | * 21 | * @access public 22 | * @param array $params A collection of variables that has been passed to us. 23 | * @return string A rendered View Helper Partial template file. 24 | */ 25 | public function render($params = array()) { 26 | return $this->renderPartial('test', array( 27 | 'testVar' => $params['testVar'] 28 | )); 29 | } 30 | } -------------------------------------------------------------------------------- /Library/MyProject/View/Helper/Url.php: -------------------------------------------------------------------------------- 1 | 9 | * @author Christopher Hill 10 | * @since 18/01/2012 11 | */ 12 | class Url 13 | { 14 | /** 15 | * Make a URL that links to a controller/action/variables. 16 | * 17 | * By default we do not use the URL variables, but you can chose to do so. 18 | * 19 | * 20 | * array( 21 | * 'controller' => 'Index', 22 | * 'action' => 'Index', 23 | * 'variables' => array( 24 | * 'foo' => 'bar', 25 | * 'bar' => 'foobar' 26 | * ) 27 | * 'variable_retain' => false 28 | * ) 29 | * 30 | * 31 | * @access public 32 | * @param array $param Parameters used to build the URL. 33 | * @return string 34 | */ 35 | public function render($param = array()) { 36 | // Set some defaults 37 | $defaults = array( 38 | 'controller' => 'index', 39 | 'action' => 'index', 40 | 'variables' => isset($param['variable_retain']) && $param['variable_retain'] 41 | ? $_GET 42 | : array(), 43 | 'variable_retain' => false 44 | ); 45 | 46 | // However, we do not want the controller/action in the variable list 47 | unset($defaults['variables']['controller'], $defaults['variables']['action']); 48 | 49 | // Merge these in with the parameters 50 | // Parameters will take precedence 51 | $param = array_merge($defaults, $param); 52 | 53 | // Start to build URL 54 | // The controller 55 | $url = Core\Config::get('path', 'root') . $param['controller'] . '/' . $param['action']; 56 | 57 | // Any variables 58 | if ($param['variables']) { 59 | // Yes, there are variables to append, loop over them 60 | foreach ($param['variables'] as $variable => $value) { 61 | // If there is an odd amount of variables in the URL string 62 | // .. then we just set the last variable to true. This needs 63 | // .. to be the same in this case also. 64 | $url .= '/' . urlencode($variable) . '/' . ($value === true ? '' : $value); 65 | } 66 | } 67 | 68 | // URL has finished constructing, pass back 69 | return $url; 70 | } 71 | } -------------------------------------------------------------------------------- /Library/MyProject/View/Partial/Profiler.phtml: -------------------------------------------------------------------------------- 1 |
    2 |

    You request lasted seconds

    3 | 4 |
    5 |

    6 |   7 | Core 8 | 9 |    10 | Controller 11 | 12 |   13 | Action 14 | 15 |   16 | View Helper 17 | 18 |   19 | Template parsing 20 |

    21 |
    22 | 23 |
    24 | 25 |
    26 |
    -------------------------------------------------------------------------------- /Library/MyProject/View/Partial/ProfilerItem.phtml: -------------------------------------------------------------------------------- 1 |
    2 | (ms @ kb) 3 |
    -------------------------------------------------------------------------------- /Library/MyProject/View/Partial/test.phtml: -------------------------------------------------------------------------------- 1 | Test var: -------------------------------------------------------------------------------- /Library/MyProject/View/Script/Error/notFound.phtml: -------------------------------------------------------------------------------- 1 |

    404

    2 | 3 |

    Sorry, the page you requested could not be found.

    -------------------------------------------------------------------------------- /Library/MyProject/View/Script/Index/error.phtml: -------------------------------------------------------------------------------- 1 |

    Hello from the Index/Error View!

    2 | 3 |

    Well, something went wrong with the routing.

    -------------------------------------------------------------------------------- /Library/MyProject/View/Script/Index/index.phtml: -------------------------------------------------------------------------------- 1 |

    Hello from the Index/Index View Script!

    2 | 3 |

    Since you are seeing this your MVC is setup correctly - congratulations!

    4 | 5 |

    Just a recap to remind you that you can change this Layout in the /Library/MyProject/Layout/default.phtml file, edit Controllers in the /Library/MyProject/Controller/ directory, and View Scripts in the /Library/MyProject/View/Script/ directory.

    6 | 7 |
    8 | 9 |

    Testing View Helpers:

    10 | 11 |

    12 | You can call View Helpers in this context: 13 | test(['testVar' => 'Hello from the Test View Helper!'])?> 14 |

    15 | 16 |

    Safely output user supplied data:

    17 | 18 |

    19 | You can also securely output potentially dangerous output: 20 | safe(['string' => ''])?> 21 |

    22 | 23 |

    Passing variables to the View from the Controller:

    24 | 25 | 26 |

    27 | The $foo variable was set in the Controller, and its value is: 28 | safe(['string' => $foo])?> 29 |

    30 | 31 |

    32 | Oh dear, $foo isn't set. 33 | Click here to pass it in via the URL. 34 |

    35 | 36 | 37 |
    38 | 39 |

    Happy coding!

    -------------------------------------------------------------------------------- /Library/MyProject/config.ini: -------------------------------------------------------------------------------- 1 | [settings] 2 | project = "MyProject" ; Name your project directory 3 | 4 | [path] 5 | base = "/usr/local/www/Library" ; Path to the Library 6 | root = "/" ; Web path, e.g., example.org/your/root 7 | project = "/MyProject/" ; Relative to your base 8 | cache = "/MyProject/Cache/" ; Relative to your base 9 | 10 | [db] 11 | host = localhost 12 | database = my_project 13 | username = root 14 | password = P455w0rd 15 | 16 | [cache] 17 | interface = "File" ; Either "Apc", "File", or "Memcache" 18 | enable = false 19 | life = 60 ; In seconds 20 | 21 | [profiler] 22 | enable = false -------------------------------------------------------------------------------- /Library/autoloader.php: -------------------------------------------------------------------------------- 1 | register(); 13 | * 14 | * @author Jonathan H. Wage 15 | * @author Roman S. Borschel 16 | * @author Matthew Weier O'Phinney 17 | * @author Kris Wallsmith 18 | * @author Fabien Potencier 19 | */ 20 | class SplClassLoader 21 | { 22 | private $_fileExtension = '.php'; 23 | private $_namespace; 24 | private $_includePath; 25 | private $_namespaceSeparator = '\\'; 26 | 27 | /** 28 | * Creates a new SplClassLoader that loads classes of the 29 | * specified namespace. 30 | * 31 | * @param string $ns The namespace to use. 32 | */ 33 | public function __construct($ns = null, $includePath = null) 34 | { 35 | $this->_namespace = $ns; 36 | $this->_includePath = $includePath; 37 | } 38 | 39 | /** 40 | * Sets the namespace separator used by classes in the namespace of this class loader. 41 | * 42 | * @param string $sep The separator to use. 43 | */ 44 | public function setNamespaceSeparator($sep) 45 | { 46 | $this->_namespaceSeparator = $sep; 47 | } 48 | 49 | /** 50 | * Gets the namespace seperator used by classes in the namespace of this class loader. 51 | * 52 | * @return void 53 | */ 54 | public function getNamespaceSeparator() 55 | { 56 | return $this->_namespaceSeparator; 57 | } 58 | 59 | /** 60 | * Sets the base include path for all class files in the namespace of this class loader. 61 | * 62 | * @param string $includePath 63 | */ 64 | public function setIncludePath($includePath) 65 | { 66 | $this->_includePath = $includePath; 67 | } 68 | 69 | /** 70 | * Gets the base include path for all class files in the namespace of this class loader. 71 | * 72 | * @return string $includePath 73 | */ 74 | public function getIncludePath() 75 | { 76 | return $this->_includePath; 77 | } 78 | 79 | /** 80 | * Sets the file extension of class files in the namespace of this class loader. 81 | * 82 | * @param string $fileExtension 83 | */ 84 | public function setFileExtension($fileExtension) 85 | { 86 | $this->_fileExtension = $fileExtension; 87 | } 88 | 89 | /** 90 | * Gets the file extension of class files in the namespace of this class loader. 91 | * 92 | * @return string $fileExtension 93 | */ 94 | public function getFileExtension() 95 | { 96 | return $this->_fileExtension; 97 | } 98 | 99 | /** 100 | * Installs this class loader on the SPL autoload stack. 101 | */ 102 | public function register() 103 | { 104 | spl_autoload_register(array($this, 'loadClass')); 105 | } 106 | 107 | /** 108 | * Uninstalls this class loader from the SPL autoloader stack. 109 | */ 110 | public function unregister() 111 | { 112 | spl_autoload_unregister(array($this, 'loadClass')); 113 | } 114 | 115 | /** 116 | * Loads the given class or interface. 117 | * 118 | * @param string $className The name of the class to load. 119 | * @return void 120 | */ 121 | public function loadClass($className) 122 | { 123 | if (null === $this->_namespace || $this->_namespace.$this->_namespaceSeparator === substr($className, 0, strlen($this->_namespace.$this->_namespaceSeparator))) { 124 | $fileName = ''; 125 | $namespace = ''; 126 | if (false !== ($lastNsPos = strripos($className, $this->_namespaceSeparator))) { 127 | $namespace = substr($className, 0, $lastNsPos); 128 | $className = substr($className, $lastNsPos + 1); 129 | $fileName = str_replace($this->_namespaceSeparator, DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; 130 | } 131 | $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . $this->_fileExtension; 132 | 133 | require ($this->_includePath !== null ? $this->_includePath . DIRECTORY_SEPARATOR : '') . $fileName; 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /Library/global.php: -------------------------------------------------------------------------------- 1 | register(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to MVC 2 | 3 | ## What is this project? 4 | There are plenty of excellent MVC's out there. So why the need for yet another? Simply because they are big and complex. Why use a sledgehammer to crack a nut? 5 | 6 | ## The projects aims 7 | To provide a compact codebase providing the basics for any application. It's designed to be fast. *Really fast.* With the built in profiler you can easily see how fast each fragment of your site is, how much memory it consumes, and how each fragment was initiated. 8 | 9 | ## What does this project have to offer? 10 | We provide the basics of the MVC, everything from the router, dispatcher, controllers, views, and view helpers. We even have a few components you can make use of, including: 11 | 12 | * Model ORM 13 | * Cache 14 | * Event listeners 15 | * Formatting of data 16 | * Notice messages 17 | * Data validation 18 | * Store (APC, Cookie, Memcache(d), Request, and Session) 19 | 20 | ## Testing 21 | This project contains a PHPUnit test suite with 64 tests and 194 assertions (all passing). 22 | 23 | --- 24 | 25 | ## Installation 26 | Checkout a copy of the source files and head over to your app's config in `/Library/MyProject/config.ini` and update the `path`'s to your specific directory structure. You can rename the `MyProject` in the `Library` directory, just make sure you also update the namespace definition in each of your `.php` files. 27 | 28 | --- 29 | 30 | ## Features and Documentation 31 | 32 | ### Routing (Basic) 33 | We parse URI's such as `/index/hello/my/variables/go/here/foobar` and place it into the GET array. Dumping the will give you: 34 | 35 | array( 36 | 'controller' => 'index', 37 | 'action' => 'hello', 38 | 'my' => 'variables', 39 | 'go' => 'here', 40 | 'foobar' => true 41 | ) 42 | 43 | This will forward the request onto the `Index` controller and into the `hello` action. 44 | 45 | ### Routing (Advanced) 46 | 47 | If you want to customise your URL's as such that the basic `/controller/action/my/variables/go/here` will not suffice, then you can use the built in `Router`. 48 | 49 | #### Example 50 | 51 | // Global configurations 52 | include dirname(__FILE__) . '/../Library/global.php'; 53 | 54 | // Create new Router instance 55 | $router = new Core\Router(); 56 | $router 57 | ->addRoute('Foo') 58 | ->setRoute('foo/:bar/:acme') 59 | ->setFormat(array( 60 | 'bar' => '\d+', 61 | 'acme' => '[a-z0-9]+') 62 | ) 63 | ->setEndpoint(array( 64 | 'controller' => 'Foo', 65 | 'action' => 'bar') 66 | ); 67 | 68 | // Start the application, passing in the Router 69 | new Core\Front('MyProject', $router); 70 | 71 | You can add as many routes as you like. Any variables that you do not specify in the `setFormat` method (which is optional) will use the regex of `[\w\-]+`. The `setEndpoint` method does not require an `action` parameter, but if none is declared then it will use the default `index` action of that controller. 72 | 73 | This advanced routing system will use the first declared route that it finds matching the request URL. 74 | 75 | ### Reverse routing 76 | 77 | URL's will often change. Defining them in a single place (the router) will save you having to rewrite them in your View Helpers/Partials. It is also safer because URL encoding will be taken care for you. You can call the `Route` View Helper via: 78 | 79 | $this->view->route(array( 80 | 'route' => 'Foo', 81 | 'params' => array( 82 | 'bar' => 1234, 83 | 'acme' => 'foobar' 84 | ) 85 | ); 86 | 87 | --- 88 | 89 | ### Controllers 90 | 91 | Controllers are created in `/Library/MyProject/Controller`, the file name begins with an uppercase letter and ends in a `.php` extension, so `index` would be called `Index.php`. 92 | 93 | You can pass variables from the `Controller` to the `View` via: 94 | 95 | public function indexAction() { 96 | $this->view->addVariable($name = 'foo', $value = 'bar'); 97 | } 98 | 99 | Which you can then access in the Layout and View Script by simple using the name of the variable you added to the view, such as in the above example: `$foo` 100 | 101 | You can interact directly with the `Request` object (data passed by the URL and forms, server variables, and the URL object): 102 | 103 | public function indexAction() { 104 | if ($getVariable = $this->request->get('foo')) { 105 | echo "Foo is: {$getVariable}"; 106 | } 107 | 108 | if ($postVariable = $this->request->post('bar')) { 109 | echo "Bar is: {$postVariable}"; 110 | } 111 | } 112 | 113 | ### Actions 114 | 115 | Actions are named the same as specified in the URI, are lowercase, and end in `Action`. So the `index` action will be named `indexAction()`. 116 | 117 | ### Caching 118 | 119 | Caching can be turned on or off from your projects configuration file (`/Library/MyProject/config.ini`), and you can set how long you want before the cache is invalidated. You can also select how you would like your cache to be stored: "File", "Apc", or "Memcache". 120 | 121 | [cache] 122 | interface = "File" 123 | enable = true 124 | life = 60 125 | 126 | ### Forwarding 127 | 128 | You can forward to another action (or controller) via the `return $this->forward('action', 'controller')` command in a controller. 129 | 130 | ### Layouts 131 | 132 | You can change the layout (layouts are stored in `/Library/MyProject/Layout`, are lowercase, and end with a `.phtml` extension) that will wrap the View by calling the `$this->setLayout('layout')` method in a controllers action. 133 | 134 | #### Example 135 | 136 | class Index extends Core\Controller 137 | { 138 | public function indexAction() { 139 | $this->setLayout('new-layout'); 140 | } 141 | } 142 | 143 | * * * 144 | 145 | ### View Scripts 146 | 147 | Views are stored in the `/Library/MyProject/View/Script` directory, and each controller has their own directory. So the `Index` controller's views will be stored in `/Library/MyProject/View/Script/Index`. Each of the controllers actions have a separate view, so the `Index` controller's `hello` action will be stored in `/Library/MyProject/View/Script/Index/hello.phtml`. 148 | 149 | #### URL generation 150 | 151 | The view has a built in method to generate URL's. You can specify the controller, action and any variables. You can also state whether you want to retain the current pages URL variables (disabled by default). This is called via: 152 | 153 | echo $this->url( 154 | array( 155 | 'controller' => 'index', 156 | 'action' => 'hello', 157 | 'variables' => array('foo' => 'bar'), 158 | 'variable_retain' => true 159 | ) 160 | ); 161 | 162 | #### Safe HTML 163 | 164 | You can output HTML to the browser safely by using the `$this->safe(array('string' => 'Evil string'))` method. 165 | 166 | ### View Helpers and View Partials 167 | 168 | Your View Scripts can easily direct logic away from themselves into View Helpers. View helpers can have their own template files, called Partials. For example, the `Test` View Helper: 169 | 170 | return $this->renderPartial('test', array( 171 | 'testVar' => $params['testVar'] 172 | )); 173 | 174 | --- 175 | 176 | ## Storage 177 | 178 | Every storage method has exactly the same interface; `has`, `put`, `get`, and `remove`. You pass a storage method into the `Store` class and you can then interact with it. 179 | 180 | $store = new Store(new Store\File()); 181 | $store->put('foo', 'bar'); 182 | $store->get('foo'); // Echo's "bar" 183 | 184 | Some storage methods might require setting up (such as `Memcache`), you can easily do this before passing it into the `Store`. 185 | 186 | $memcache = new Store\Memcache(); 187 | $memcache->setup($server, $host, $port); 188 | 189 | $store = new Store($memcache); 190 | $store->put('foo', 'bar'); 191 | $store->get('foo'); // Echo's "bar" 192 | 193 | The advantages of a single interface is you can very easily switch your storage mechanism without having to change any of the logic. 194 | 195 | --- 196 | 197 | ## Profiling 198 | A powerful in-built profiler will let you know exactly where your application is expending time and additional memory. Its waterfall display allows you to see which functions have been called by whom. 199 | 200 | ![](https://raw.github.com/chrisjhill/MVC/master/Web/assets/img/profiler.png) -------------------------------------------------------------------------------- /Tests/ConfigTest.php: -------------------------------------------------------------------------------- 1 | assertNotNull(Core\Config::get('settings', 'project')); 16 | 17 | // Paths 18 | $this->assertNotNull(Core\Config::get('path', 'base')); 19 | $this->assertNotNull(Core\Config::get('path', 'root')); 20 | $this->assertNotNull(Core\Config::get('path', 'project')); 21 | $this->assertNotNull(Core\Config::get('path', 'cache')); 22 | 23 | // Cache 24 | $this->assertNotNull(Core\Config::get('cache', 'enable')); 25 | $this->assertNotNull(Core\Config::get('cache', 'life')); 26 | } 27 | 28 | /** 29 | * Are we able to set new config variables? 30 | * 31 | * @access public 32 | */ 33 | public function testConfigCanSetNewVariables() { 34 | // An existing parent index 35 | Core\Config::set('settings', 'foo', 'bar'); 36 | $this->assertEquals(Core\Config::get('settings', 'foo'), 'bar'); 37 | 38 | // A new parent index 39 | Core\Config::set('foo', 'bar', 'foobar'); 40 | $this->assertEquals(Core\Config::get('foo', 'bar'), 'foobar'); 41 | } 42 | 43 | /** 44 | * By default you cannot overwrite config variables. You need to pass the 45 | * forth variable 'true' to make sure you are not doing something bad. 46 | * 47 | * @access public 48 | * @expectedException Exception 49 | */ 50 | public function testConfigOverwritingVariablesWithNoOverwritePassed() { 51 | // This should fail 52 | Core\Config::set('settings', 'foo', 'foobar'); 53 | $this->assertNotEquals(Core\Config::get('settings', 'foo'), 'foobar'); 54 | } 55 | 56 | /** 57 | * By default you cannot overwrite config variables. You need to pass the 58 | * forth variable 'true' to make sure you are not doing something bad. 59 | * 60 | * @access public 61 | */ 62 | public function testConfigOverwritingVariableWithOverwritePassed() { 63 | // This should overwrite 64 | Core\Config::set('settings', 'foo', 'foobar', true); 65 | $this->assertEquals(Core\Config::get('settings', 'foo'), 'foobar'); 66 | } 67 | 68 | /** 69 | * Removing a config variable. 70 | * 71 | * @access public 72 | */ 73 | public function testConfigRemovingVariable() { 74 | // Removing a config variable that does not exist... 75 | $this->assertFalse(Core\Config::remove('foo', 'doesNotExist')); 76 | 77 | // .. and one that does exist 78 | $this->assertTrue(Core\Config::remove('foo', 'bar')); 79 | } 80 | } -------------------------------------------------------------------------------- /Tests/FormatTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(Core\Format::safeHtml('>foo'), '>foo'); 12 | } 13 | 14 | /** 15 | * Are we encoding correctly? 16 | * 17 | * @access public 18 | */ 19 | public function testLessThanEncoded() { 20 | $this->assertEquals(Core\Format::safeHtml('assertEquals(Core\Format::safeHtml('"foo'), '"foo'); 30 | } 31 | 32 | /** 33 | * Are we encoding correctly? 34 | * 35 | * @access public 36 | */ 37 | public function testSingleQuoteEncoded() { 38 | $this->assertEquals(Core\Format::safeHtml('\'foo'), ''foo'); 39 | } 40 | 41 | /** 42 | * Are we encoding correctly? 43 | * 44 | * @access public 45 | */ 46 | public function testAmpersandEncoded() { 47 | $this->assertEquals(Core\Format::safeHtml('&foo'), '&foo'); 48 | } 49 | 50 | /** 51 | * Are we encoding correctly? 52 | * 53 | * @access public 54 | */ 55 | public function testForNonDoubleEncoded() { 56 | $this->assertEquals(Core\Format::safeHtml('&foo'), '&foo'); 57 | } 58 | } -------------------------------------------------------------------------------- /Tests/ModelTest.php: -------------------------------------------------------------------------------- 1 | assertFalse($user->name); 14 | $user->name = 'Chris'; 15 | $this->assertEquals($user->name, 'Chris'); 16 | } 17 | 18 | /** 19 | * Testing SELECTing all fields. 20 | * 21 | * @access public 22 | */ 23 | public function testSelectAll() { 24 | // Create our test model object 25 | $user = new MyProject\Model\User(); 26 | $this->assertEquals($this->format($user->build('select')), "SELECT * FROM user"); 27 | } 28 | 29 | /** 30 | * Testing SELECTing certain fields. 31 | * 32 | * @access public 33 | */ 34 | public function testSelectFields() { 35 | // Create our test model object 36 | $user = new MyProject\Model\User(); 37 | $user->select('user_id'); 38 | $this->assertEquals($this->format($user->build('select')), "SELECT user_id FROM user"); 39 | $user->select('name'); 40 | $this->assertEquals($this->format($user->build('select')), "SELECT user_id, name FROM user"); 41 | } 42 | 43 | /** 44 | * Testing SELECT AS. 45 | * 46 | * @access public 47 | */ 48 | public function testSelectAs() { 49 | // Create our test model object 50 | $user = new MyProject\Model\User(); 51 | $user->select('user_id', 'user'); 52 | $this->assertEquals($this->format($user->build('select')), "SELECT user_id AS 'user' FROM user"); 53 | } 54 | 55 | /** 56 | * Testing SELECTing fields using functions. 57 | * 58 | * @access public 59 | */ 60 | public function testSelectFieldsFunction() { 61 | // Create our test model object 62 | $user = new MyProject\Model\User(); 63 | $user->select('DISTINCT(email)'); 64 | $this->assertEquals($this->format($user->build('select')), "SELECT DISTINCT(email) FROM user"); 65 | } 66 | 67 | /** 68 | * Testing SELECTing fields using functions and setting them to AS. 69 | * 70 | * @access public 71 | */ 72 | public function testSelectFieldsFunctionAs() { 73 | // Create our test model object 74 | $user = new MyProject\Model\User(); 75 | $user->select('DISTINCT(email)', 'email'); 76 | $this->assertEquals($this->format($user->build('select')), "SELECT DISTINCT(email) AS 'email' FROM user"); 77 | } 78 | 79 | /** 80 | * Testing FROM. 81 | * 82 | * @access public 83 | */ 84 | public function testFrom() { 85 | // Create our test model object 86 | $user = new MyProject\Model\User(); 87 | $user->from('user'); 88 | $this->assertEquals($this->format($user->build('select')), "SELECT * FROM user"); 89 | $user->from('foo'); 90 | $this->assertEquals($this->format($user->build('select')), "SELECT * FROM user, foo"); 91 | } 92 | 93 | /** 94 | * Testing FROM joins. 95 | * 96 | * @access public 97 | */ 98 | public function testFromJoin() { 99 | // Create our test model object 100 | $user = new MyProject\Model\User(); 101 | $user->from('user'); 102 | $user->from('foo', 'LEFT', 'email', 'email'); 103 | $this->assertEquals($this->format($user->build('select')), "SELECT * FROM user, LEFT JOIN foo ON email = email"); 104 | 105 | // Create our test model object 106 | $user = new MyProject\Model\User(); 107 | $user->from('user'); 108 | $user->from('foo', 'RIGHT', 'email', 'email'); 109 | $this->assertEquals($this->format($user->build('select')), "SELECT * FROM user, RIGHT JOIN foo ON email = email"); 110 | 111 | // Create our test model object 112 | $user = new MyProject\Model\User(); 113 | $user->from('user'); 114 | $user->from('foo', 'INNER', 'email', 'email'); 115 | $this->assertEquals($this->format($user->build('select')), "SELECT * FROM user, INNER JOIN foo ON email = email"); 116 | } 117 | 118 | /** 119 | * Testing WHERE. 120 | * 121 | * @access public 122 | */ 123 | public function testWhere() { 124 | // Create our test model object 125 | $user = new MyProject\Model\User(); 126 | $user->where('user_id', '=', 1); 127 | $this->assertEquals($this->format($user->build('select')), "SELECT * FROM user WHERE user_id = :__where_0"); 128 | 129 | // Create our test model object 130 | $user = new MyProject\Model\User(); 131 | $user->where('user_id', '=', 1, 'AND'); 132 | $user->where('name', '=', 'Chris'); 133 | $this->assertEquals($this->format($user->build('select')), "SELECT * FROM user WHERE user_id = :__where_0 AND name = :__where_1"); 134 | } 135 | 136 | /** 137 | * Testing WHERE. 138 | * 139 | * @access public 140 | */ 141 | public function testWhereBraces() { 142 | // Create our test model object 143 | $user = new MyProject\Model\User(); 144 | $user->brace('open'); 145 | $user->where('user_id', '=', 1); 146 | $user->brace('close'); 147 | $this->assertEquals($this->format($user->build('select')), "SELECT * FROM user WHERE (user_id = :__where_1)"); 148 | 149 | // Create our test model object 150 | $user = new MyProject\Model\User(); 151 | $user->brace('open'); 152 | $user->where('user_id', '=', 1, 'AND'); 153 | $user->where('name', '=', 'Chris'); 154 | $user->brace('close'); 155 | $this->assertEquals($this->format($user->build('select')), "SELECT * FROM user WHERE (user_id = :__where_1 AND name = :__where_2)"); 156 | 157 | // Create our test model object 158 | $user = new MyProject\Model\User(); 159 | $user->brace('open'); 160 | $user->where('user_id', '=', 1); 161 | $user->brace('close', 'AND'); 162 | $user->brace('open'); 163 | $user->where('name', '=', 'Chris'); 164 | $user->brace('close'); 165 | $this->assertEquals($this->format($user->build('select')), "SELECT * FROM user WHERE (user_id = :__where_1) AND (name = :__where_4)"); 166 | 167 | // Create our test model object 168 | $user = new MyProject\Model\User(); 169 | $user->brace('open'); 170 | $user->brace('open'); 171 | $user->where('user_id', '=', 1); 172 | $user->brace('close'); 173 | $user->brace('close'); 174 | $this->assertEquals($this->format($user->build('select')), "SELECT * FROM user WHERE ((user_id = :__where_2))"); 175 | 176 | // Create our test model object 177 | $user = new MyProject\Model\User(); 178 | $user->where('user_id', '=', 1, 'AND'); 179 | $user->brace('open'); 180 | $user->where('name', '=', 'Chris', 'OR'); 181 | $user->where('name', '=', 'Christopher'); 182 | $user->brace('close'); 183 | $this->assertEquals($this->format($user->build('select')), "SELECT * FROM user WHERE user_id = :__where_0 AND (name = :__where_2 OR name = :__where_3)"); 184 | } 185 | 186 | /** 187 | * Testing GROUP BY. 188 | * 189 | * @access public 190 | */ 191 | public function testGroupBy() { 192 | // Create our test model object 193 | $user = new MyProject\Model\User(); 194 | $user->group('email'); 195 | $this->assertEquals($this->format($user->build('select')), "SELECT * FROM user GROUP BY email"); 196 | $user->group('name'); 197 | $this->assertEquals($this->format($user->build('select')), "SELECT * FROM user GROUP BY email, name"); 198 | } 199 | 200 | /** 201 | * Testing ORDER BY. 202 | * 203 | * @access public 204 | */ 205 | public function testOrderBy() { 206 | // Create our test model object 207 | $user = new MyProject\Model\User(); 208 | $user->order('user_id'); 209 | $this->assertEquals($this->format($user->build('select')), "SELECT * FROM user ORDER BY user_id ASC"); 210 | 211 | // Create our test model object 212 | $user = new MyProject\Model\User(); 213 | $user->order('user_id', 'DESC'); 214 | $this->assertEquals($this->format($user->build('select')), "SELECT * FROM user ORDER BY user_id DESC"); 215 | 216 | // Create our test model object 217 | $user = new MyProject\Model\User(); 218 | $user->order('user_id'); 219 | $user->order('name'); 220 | $this->assertEquals($this->format($user->build('select')), "SELECT * FROM user ORDER BY user_id ASC, name ASC"); 221 | } 222 | 223 | /** 224 | * Testing LIMIT. 225 | * 226 | * @access public 227 | */ 228 | public function testLimit() { 229 | // Create our test model object 230 | $user = new MyProject\Model\User(); 231 | $user->limit(10); 232 | $this->assertEquals($this->format($user->build('select')), "SELECT * FROM user LIMIT 10"); 233 | 234 | // Create our test model object 235 | $user = new MyProject\Model\User(); 236 | $user->limit(10, 25); 237 | $this->assertEquals($this->format($user->build('select')), "SELECT * FROM user LIMIT 25, 10"); 238 | } 239 | 240 | /** 241 | * Testing INSERT statements. 242 | * 243 | * @access public 244 | */ 245 | public function testInsert() { 246 | // Create our test model object 247 | $user = new MyProject\Model\User(); 248 | $user->name = 'Chris'; 249 | $this->assertEquals($this->format($user->build('insert')), "INSERT INTO user (name) VALUES (:name)"); 250 | $user->email = 'cjhill@gmail.com'; 251 | $this->assertEquals($this->format($user->build('insert')), "INSERT INTO user (name, email) VALUES (:name, :email)"); 252 | } 253 | 254 | /** 255 | * Testing UPDATE statements. 256 | * 257 | * @access public 258 | */ 259 | public function testUpdate() { 260 | // Create our test model object 261 | $user = new MyProject\Model\User(); 262 | $user->name = 'Chris'; 263 | $this->assertEquals($this->format($user->build('update')), "UPDATE user SET name = :name"); 264 | $user->where('user_id', '=', 1); 265 | $this->assertEquals($this->format($user->build('update')), "UPDATE user SET name = :name WHERE user_id = :__where_0"); 266 | $user->limit(1); 267 | $this->assertEquals($this->format($user->build('update')), "UPDATE user SET name = :name WHERE user_id = :__where_0 LIMIT 1"); 268 | } 269 | 270 | /** 271 | * Testing DELETE statements. 272 | * 273 | * @access public 274 | */ 275 | public function testDelete() { 276 | // Create our test model object 277 | $user = new MyProject\Model\User(); 278 | $this->assertEquals($this->format($user->build('delete')), "DELETE FROM user"); 279 | $user->where('user_id', '=', 1); 280 | $this->assertEquals($this->format($user->build('delete')), "DELETE FROM user WHERE user_id = :__where_0"); 281 | $user->limit(1); 282 | $this->assertEquals($this->format($user->build('delete')), "DELETE FROM user WHERE user_id = :__where_0 LIMIT 1"); 283 | } 284 | 285 | /** 286 | * Testing the return values for when a query has not yet been run. 287 | * 288 | * @access public 289 | */ 290 | public function testNoQueryRun() { 291 | // Create our test model object 292 | $user = new MyProject\Model\User(); 293 | $this->assertFalse($user->rowCount()); 294 | $this->assertFalse($user->fetch()); 295 | } 296 | 297 | /** 298 | * Testing the function that resets all of the query information. 299 | * 300 | * @access public 301 | */ 302 | public function testReset() { 303 | // Create our test model object 304 | $user = new MyProject\Model\User(); 305 | $user 306 | ->select('name') 307 | ->from('user_fake') 308 | ->where('name', '=', 'Chris') 309 | ->group('name') 310 | ->order('name') 311 | ->limit(1) 312 | ->name = 'Chris'; 313 | 314 | // Make sure everything has been set 315 | $this->assertEquals($user->name, 'Chris'); 316 | $this->assertEquals($this->format($user->build('select')), 'SELECT name FROM user_fake WHERE name = :__where_0 GROUP BY name ORDER BY name ASC LIMIT 1'); 317 | 318 | // Now reset and make sure we have a fresh start 319 | $user->reset(); 320 | $this->assertFalse($user->name); 321 | $this->assertEquals($this->format($user->build('select')), 'SELECT * FROM user'); 322 | } 323 | 324 | /** 325 | * Strip all of the excess whitespace from the query 326 | * @param [type] $sql [description] 327 | * @return [type] [description] 328 | */ 329 | private function format($sql) { 330 | return str_replace(' , ', ', ', preg_replace('/\s+/', ' ', trim($sql))); 331 | } 332 | } -------------------------------------------------------------------------------- /Tests/ProfilerTest.php: -------------------------------------------------------------------------------- 1 | assertFalse(Core\Profiler::register('Foo', 'bar')); 16 | } 17 | 18 | /** 19 | * Start the request. 20 | * 21 | * @access public 22 | */ 23 | public function testRequestStarting() { 24 | // Test start time not set 25 | $stack = Core\Profiler::getProfilerData(); 26 | $this->assertNull($stack['requestStart']); 27 | 28 | // Start the request 29 | Core\Profiler::start(); 30 | 31 | // And test that it has been set 32 | $stack = Core\Profiler::getProfilerData(); 33 | $this->assertNotNull($stack['requestStart']); 34 | } 35 | 36 | /** 37 | * Stop the request. 38 | * 39 | * @access public 40 | */ 41 | public function testRequestStopping() { 42 | // Test start time not set 43 | $stack = Core\Profiler::getProfilerData(); 44 | $this->assertNull($stack['requestEnd']); 45 | 46 | // Start the request 47 | Core\Profiler::stop(); 48 | 49 | // And test that it has been set 50 | $stack = Core\Profiler::getProfilerData(); 51 | $this->assertNotNull($stack['requestEnd']); 52 | } 53 | 54 | /** 55 | * Can we add stacks to the profiler, and they arrange themselves correctly? 56 | * 57 | * @access public 58 | */ 59 | public function testAddingStackToProfile() { 60 | // Enable the profiler 61 | Core\Config::set('profiler', 'enable', true, true); 62 | 63 | // Add a stack 64 | Core\Profiler::register('Foo', 'bar'); 65 | $stack = Core\Profiler::getProfilerData(); 66 | $this->assertTrue(is_array($stack['stack'])); 67 | $this->assertEquals(1, count($stack['stack'])); 68 | } 69 | 70 | /** 71 | * Are multiple stacks correctly ordered? 72 | * 73 | * @access public 74 | */ 75 | public function testAddingMultipleStacksToProfile() { 76 | // Add another stack 77 | Core\Profiler::register('Bar', 'foo'); 78 | $stack = Core\Profiler::getProfilerData(); 79 | $this->assertTrue(is_array($stack['stack'])); 80 | $this->assertEquals(2, count($stack['stack'])); 81 | 82 | // First should be Bar, then Foo 83 | $this->assertEquals('Foo', $stack['stack'][0]['type']); 84 | $this->assertEquals('Bar', $stack['stack'][1]['type']); 85 | } 86 | } -------------------------------------------------------------------------------- /Tests/RequestTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(Core\Request::get('controller'), 'Index'); 20 | 21 | // Action okay? 22 | $this->assertEquals(Core\Request::get('action'), 'foo'); 23 | } 24 | 25 | /** 26 | * Testing that getting the URL works as expected. 27 | * 28 | * @access public 29 | */ 30 | public function testGetUrl() { 31 | // Get the standard URL with no replacements 32 | Core\Request::setUrl('/index/foo/bar/foobar'); 33 | $this->assertEquals(Core\Request::getUrl(), '/index/foo/bar/foobar'); 34 | 35 | // And try replacing slashes with underscores 36 | $this->assertEquals(Core\Request::getUrl('_'), '_index_foo_bar_foobar'); 37 | } 38 | 39 | /** 40 | * Testing GET variables exist. 41 | * 42 | * @access public 43 | */ 44 | public function testGetVariables() { 45 | // A variable that exists 46 | $this->assertEquals(Core\Request::get('bar'), 'foobar'); 47 | 48 | // A variable that does not exist 49 | $this->assertNull(Core\Request::get('doesNotExist')); 50 | 51 | // A variable that does not exist with a default return value 52 | $this->assertEquals(Core\Request::get('doesNotExist', 'foo'), 'foo'); 53 | } 54 | 55 | /** 56 | * Testing POST variables exist. 57 | * 58 | * @access public 59 | */ 60 | public function testPostVariables() { 61 | // A variable that exists 62 | $this->assertEquals(Core\Request::post('bar'), 'foo'); 63 | 64 | // A variable that does not exist 65 | $this->assertNull(Core\Request::post('doesNotExist')); 66 | 67 | // A variable that does not exist with a default return value 68 | $this->assertEquals(Core\Request::post('doesNotExist', 'foo'), 'foo'); 69 | } 70 | 71 | /** 72 | * Testing SERVER variables exist. 73 | * 74 | * @access public 75 | */ 76 | public function testServerVariables() { 77 | // A variable that exists 78 | $this->assertEquals(Core\Request::server('foobar'), 'barfoo'); 79 | 80 | // A variable that does not exist 81 | $this->assertNull(Core\Request::server('doesNotExist')); 82 | 83 | // A variable that does not exist with a default return value 84 | $this->assertEquals(Core\Request::server('doesNotExist', 'foo'), 'foo'); 85 | } 86 | } -------------------------------------------------------------------------------- /Tests/RouterTest.php: -------------------------------------------------------------------------------- 1 | setAccessible(true); 14 | 15 | // Simple routes 16 | // One directory 17 | $route = new Core\Route('Foo'); 18 | $route->setRoute('foo')->setEndpoint(array('controller' => 'Foo')); 19 | $this->assertTrue( $reflection->invoke(new Core\Router(), array('foo'), $route)); 20 | $this->assertTrue( $reflection->invoke(new Core\Router(), array('foo', 'bar'), $route)); 21 | $this->assertFalse($reflection->invoke(new Core\Router(), array('bar'), $route)); 22 | 23 | // Two directories 24 | $route = new Core\Route('Foo'); 25 | $route->setRoute('foo/bar')->setEndpoint(array('controller' => 'Foo')); 26 | // Passes 27 | $this->assertTrue( $reflection->invoke(new Core\Router(), array('foo', 'bar'), $route)); 28 | $this->assertTrue( $reflection->invoke(new Core\Router(), array('foo', 'bar', 'acme'), $route)); 29 | // Fails 30 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo'), $route)); 31 | $this->assertFalse($reflection->invoke(new Core\Router(), array('bar'), $route)); 32 | 33 | // Five directories 34 | $route = new Core\Route('Foo'); 35 | $route->setRoute('foo/bar/acme/boop/beep')->setEndpoint(array('controller' => 'Foo')); 36 | // Passes 37 | $this->assertTrue( $reflection->invoke(new Core\Router(), array('foo', 'bar', 'acme', 'boop', 'beep'), $route)); 38 | $this->assertTrue( $reflection->invoke(new Core\Router(), array('foo', 'bar', 'acme', 'boop', 'beep', 'hello'), $route)); 39 | // Fails 40 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo'), $route)); 41 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo', 'bar'), $route)); 42 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo', 'bar'), $route)); 43 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo', 'bar', 'acme'), $route)); 44 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo', 'bar', 'acme', 'boop'), $route)); 45 | $this->assertFalse($reflection->invoke(new Core\Router(), array('hello'), $route)); 46 | $this->assertFalse($reflection->invoke(new Core\Router(), array('hello', 'world'), $route)); 47 | } 48 | 49 | /** 50 | * Match a plain URL. 51 | * 52 | * @access public 53 | */ 54 | public function testComplexRoutes() { 55 | // Set up the reflection class 56 | $reflection = new ReflectionMethod('Core\Router', 'routeTest'); 57 | $reflection->setAccessible(true); 58 | 59 | // Here be variable routes 60 | // One directory, one variable 61 | $route = new Core\Route('Foo'); 62 | $route->setRoute('foo/:bar')->setFormat(array('bar' => '\d+'))->setEndpoint(array('controller' => 'Foo')); 63 | // Passes 64 | $this->assertTrue( $reflection->invoke(new Core\Router(), array('foo', '1'), $route)); 65 | $this->assertTrue( $reflection->invoke(new Core\Router(), array('foo', '12'), $route)); 66 | $this->assertTrue( $reflection->invoke(new Core\Router(), array('foo', '123'), $route)); 67 | $this->assertTrue( $reflection->invoke(new Core\Router(), array('foo', '123', 'foo'), $route)); 68 | // Fails 69 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo'), $route)); 70 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo/:bar'), $route)); 71 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo/bar'), $route)); 72 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo/a123'), $route)); 73 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo/1a23'), $route)); 74 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo/12a3'), $route)); 75 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo/a123a'), $route)); 76 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo/12-3'), $route)); 77 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo/12_3'), $route)); 78 | 79 | // Two directories, two variables 80 | $route = new Core\Route('Foo'); 81 | $route->setRoute('foo/:bar/hello/:world')->setFormat(array('bar' => '\d+'))->setEndpoint(array('controller' => 'Foo')); 82 | // Passes 83 | $this->assertTrue( $reflection->invoke(new Core\Router(), array('foo', '1', 'hello', 'world'), $route)); 84 | $this->assertTrue( $reflection->invoke(new Core\Router(), array('foo', '123', 'hello', 'abc123'), $route)); 85 | $this->assertTrue( $reflection->invoke(new Core\Router(), array('foo', '123', 'hello', 'ab-c12-3'), $route)); 86 | $this->assertTrue( $reflection->invoke(new Core\Router(), array('foo', '123', 'hello', 'ab_c12_3'), $route)); 87 | $this->assertTrue( $reflection->invoke(new Core\Router(), array('foo', '1', 'hello', 'world', 'foo'), $route)); 88 | // Fails 89 | // Missing parameters 90 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo'), $route)); 91 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo', '1'), $route)); 92 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo', '1', 'hello'), $route)); 93 | // Wrong basic parameters 94 | $this->assertFalse($reflection->invoke(new Core\Router(), array('bar', '1', 'hello', 'world'), $route)); 95 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo', '1', 'bar', 'world'), $route)); 96 | // Wrong number type 97 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo', 'bar', 'hello', 'world'), $route)); 98 | // Incorrect default data types, not matching [\w\-]+ 99 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo', '1', 'hello', 'wo"rld'), $route)); 100 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo', '1', 'hello', 'wo\'rld'), $route)); 101 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo', '1', 'hello', 'woassertFalse($reflection->invoke(new Core\Router(), array('foo', '1', 'hello', 'wo>rld'), $route)); 103 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo', '1', 'hello', 'wo@rld'), $route)); 104 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo', '1', 'hello', 'wo!rld'), $route)); 105 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo', '1', 'hello', 'wo£rld'), $route)); 106 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo', '1', 'hello', 'wo$rld'), $route)); 107 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo', '1', 'hello', 'wo%rld'), $route)); 108 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo', '1', 'hello', 'wo&rld'), $route)); 109 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo', '1', 'hello', 'wo+rld'), $route)); 110 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo', '1', 'hello', 'wo.rld'), $route)); 111 | $this->assertFalse($reflection->invoke(new Core\Router(), array('foo', '1', 'hello', 'wo rld'), $route)); 112 | } 113 | 114 | /** 115 | * Add a new route. 116 | * 117 | * @access public 118 | */ 119 | public function testAddingNewRoutes() { 120 | // Create router 121 | $router = new Core\Router(); 122 | 123 | // Add a couple of uniquely titled routes 124 | $this->assertTrue(get_class($router->addRoute('Foo')) == 'Core\Route'); 125 | $this->assertTrue(get_class($router->addRoute('Bar')) == 'Core\Route'); 126 | $this->assertTrue(get_class($router->addRoute('Acme')) == 'Core\Route'); 127 | } 128 | 129 | /** 130 | * Adding a route that has already been defined. 131 | * 132 | * @access public 133 | * @expectedException InvalidArgumentException 134 | */ 135 | public function testAddingExistingRoute() { 136 | // Create router 137 | $router = new Core\Router(); 138 | 139 | // Create two routes called the same 140 | $router->addRoute('Foo'); 141 | $router->addRoute('Foo'); 142 | } 143 | } -------------------------------------------------------------------------------- /Tests/StoreTest.php: -------------------------------------------------------------------------------- 1 | register(); 6 | 7 | // Start tests 8 | class StoreTest extends PHPUnit_Framework_TestCase 9 | { 10 | /** 11 | * Can we create a store object? 12 | * 13 | * @access public 14 | * @expectedException Exception 15 | */ 16 | public function testCreateCachedObject() { 17 | // Load the config file 18 | Core\Config::load('MyProject'); 19 | Core\Config::set('cache', 'enable', true, true); 20 | 21 | // Put the cache 22 | $store = new Core\Store(new Core\Store\File()); 23 | $store->put('foo', 'bar'); 24 | 25 | $this->assertTrue($store->has('foo')); 26 | $this->assertEquals($store->get('foo'), 'bar'); 27 | $this->assertTrue($store->remove('foo')); 28 | $this->assertFalse($store->has('foo')); 29 | $this->assertFalse($store->remove('foobar')); 30 | } 31 | } -------------------------------------------------------------------------------- /Tests/ValidateTest.php: -------------------------------------------------------------------------------- 1 | array('value' => 'bar', 'tests' => array('required' => true)) 14 | )); 15 | $this->assertTrue($form->isValid()); 16 | 17 | // Input exists, but with invalid values 18 | $form = new Core\Validate(array( 19 | 'foo' => array('value' => '', 'tests' => array('required' => true)) 20 | )); 21 | $this->assertFalse($form->isValid()); 22 | 23 | $form = new Core\Validate(array( 24 | 'foo' => array('value' => null, 'tests' => array('required' => true)) 25 | )); 26 | $this->assertFalse($form->isValid()); 27 | 28 | $form = new Core\Validate(array( 29 | 'foo' => array('value' => false, 'tests' => array('required' => true)) 30 | )); 31 | $this->assertFalse($form->isValid()); 32 | } 33 | 34 | /** 35 | * Testing the required with test. 36 | * 37 | * @access public 38 | */ 39 | public function testRequiredWithInput() { 40 | // Passing tests 41 | // Required with single 42 | $form = new Core\Validate(array( 43 | 'foo' => array('value' => 'bar', 'tests' => array('requiredWith' => array('bar'))), 44 | 'bar' => array('value' => 'bar') 45 | )); 46 | $this->assertTrue($form->isValid()); 47 | 48 | // Required with double 49 | $form = new Core\Validate(array( 50 | 'foo' => array('value' => 'bar', 'tests' => array('requiredWith' => array('bar', 'car'))), 51 | 'bar' => array('value' => 'bar'), 52 | 'car' => array('value' => 'bar') 53 | )); 54 | $this->assertTrue($form->isValid()); 55 | 56 | // Failing tests 57 | // Required with single 58 | $form = new Core\Validate(array( 59 | 'foo' => array('value' => 'bar', 'tests' => array('requiredWith' => array('bar'))) 60 | )); 61 | $this->assertFalse($form->isValid()); 62 | 63 | // Required with double 64 | $form = new Core\Validate(array( 65 | 'foo' => array('value' => 'bar', 'tests' => array('requiredWith' => array('bar', 'car'))) 66 | )); 67 | $this->assertFalse($form->isValid()); 68 | 69 | // Required with double, one valid 70 | $form = new Core\Validate(array( 71 | 'foo' => array('value' => 'bar', 'tests' => array('requiredWith' => array('bar', 'car'))), 72 | 'bar' => array('value' => 'bar') 73 | )); 74 | $this->assertFalse($form->isValid()); 75 | } 76 | 77 | /** 78 | * Testing the inputs character length, but passing no min or max boundary 79 | * 80 | * @access public 81 | * @expectedException Exception 82 | */ 83 | public function testLengthNoMinOrMaxBoundary() { 84 | // Required with double, one valid 85 | $form = new Core\Validate(array( 86 | 'foo' => array('value' => 'bar', 'tests' => array('length' => true)) 87 | )); 88 | } 89 | 90 | /** 91 | * Testing the inputs character length. 92 | * 93 | * @access public 94 | */ 95 | public function testLength() { 96 | // Passing tests 97 | // Greater than min 98 | $form = new Core\Validate(array( 99 | 'foo' => array('value' => 'bar', 'tests' => array('length' => array('min' => 1))) 100 | )); 101 | $this->assertTrue($form->isValid()); 102 | 103 | // Less than max 104 | $form = new Core\Validate(array( 105 | 'foo' => array('value' => 'bar', 'tests' => array('length' => array('max' => 10))) 106 | )); 107 | $this->assertTrue($form->isValid()); 108 | 109 | // Between min and max 110 | $form = new Core\Validate(array( 111 | 'foo' => array('value' => 'bar', 'tests' => array('length' => array('min' => 2, 'max' => 4))) 112 | )); 113 | $this->assertTrue($form->isValid()); 114 | 115 | // Failing tests 116 | // Less than min 117 | $form = new Core\Validate(array( 118 | 'foo' => array('value' => 'a', 'tests' => array('length' => array('min' => 2))) 119 | )); 120 | $this->assertFalse($form->isValid()); 121 | 122 | // More than max 123 | $form = new Core\Validate(array( 124 | 'foo' => array('value' => 'abdefgh', 'tests' => array('length' => array('max' => 4))) 125 | )); 126 | $this->assertFalse($form->isValid()); 127 | 128 | // Not between min and max 129 | $form = new Core\Validate(array( 130 | 'foo' => array('value' => 'abdefgh', 'tests' => array('length' => array('min' => 2, 'max' => 4))) 131 | )); 132 | $this->assertFalse($form->isValid()); 133 | } 134 | 135 | /** 136 | * Testing the is type test. 137 | * 138 | * @access public 139 | */ 140 | public function testIsType() { 141 | // Passing tests 142 | // Boolean: true 143 | $form = new Core\Validate(array( 144 | 'foo' => array('value' => true, 'tests' => array('is' => 'boolean')) 145 | )); 146 | $this->assertTrue($form->isValid()); 147 | // Boolean: false 148 | $form = new Core\Validate(array( 149 | 'foo' => array('value' => false, 'tests' => array('is' => 'boolean')) 150 | )); 151 | $this->assertTrue($form->isValid()); 152 | 153 | // Email 154 | $form = new Core\Validate(array( 155 | 'foo' => array('value' => 'cjhill@gmail.com', 'tests' => array('is' => 'email')) 156 | )); 157 | $this->assertTrue($form->isValid()); 158 | 159 | // Float 160 | $form = new Core\Validate(array( 161 | 'foo' => array('value' => 1.5, 'tests' => array('is' => 'float')) 162 | )); 163 | $this->assertTrue($form->isValid()); 164 | 165 | // IPV4 166 | $form = new Core\Validate(array( 167 | 'foo' => array('value' => '127.0.0.1', 'tests' => array('is' => 'ip')) 168 | )); 169 | $this->assertTrue($form->isValid()); 170 | 171 | // IPV6 172 | $form = new Core\Validate(array( 173 | 'foo' => array('value' => '2001:0db8:85a3:0042:1000:8a2e:0370:7334', 'tests' => array('is' => 'ip')) 174 | )); 175 | $this->assertTrue($form->isValid()); 176 | 177 | // URL 178 | $form = new Core\Validate(array( 179 | 'foo' => array('value' => 'http://www.google.com', 'tests' => array('is' => 'url')) 180 | )); 181 | $this->assertTrue($form->isValid()); 182 | 183 | // Failing tests 184 | // Boolean 185 | $form = new Core\Validate(array( 186 | 'foo' => array('value' => 'bar', 'tests' => array('is' => 'boolean')) 187 | )); 188 | $this->assertFalse($form->isValid()); 189 | 190 | // Email 191 | $form = new Core\Validate(array( 192 | 'foo' => array('value' => 'bar', 'tests' => array('is' => 'email')) 193 | )); 194 | $this->assertFalse($form->isValid()); 195 | 196 | // Float 197 | $form = new Core\Validate(array( 198 | 'foo' => array('value' => 'bar', 'tests' => array('is' => 'float')) 199 | )); 200 | $this->assertFalse($form->isValid()); 201 | 202 | // IPV4 203 | $form = new Core\Validate(array( 204 | 'foo' => array('value' => '127.0.0', 'tests' => array('is' => 'ip')) 205 | )); 206 | $this->assertFalse($form->isValid()); 207 | 208 | // URL 209 | $form = new Core\Validate(array( 210 | 'foo' => array('value' => 'google.com', 'tests' => array('is' => 'url')) 211 | )); 212 | $this->assertFalse($form->isValid()); 213 | } 214 | 215 | /** 216 | * Testing the exact test. 217 | * 218 | * @access public 219 | */ 220 | public function testExactly() { 221 | // Pass with a single option 222 | $form = new Core\Validate(array( 223 | 'foo' => array('value' => 'bar', 'tests' => array('exactly' => array('bar'))) 224 | )); 225 | $this->assertTrue($form->isValid()); 226 | 227 | // Pass with two options 228 | $form = new Core\Validate(array( 229 | 'foo' => array('value' => 'bar', 'tests' => array('exactly' => array('bar', 'car'))) 230 | )); 231 | $this->assertTrue($form->isValid()); 232 | 233 | // Fail with a single option 234 | $form = new Core\Validate(array( 235 | 'foo' => array('value' => 'bar', 'tests' => array('exactly' => array('foo'))) 236 | )); 237 | $this->assertFalse($form->isValid()); 238 | 239 | // Pass with two options 240 | $form = new Core\Validate(array( 241 | 'foo' => array('value' => 'bar', 'tests' => array('exactly' => array('foo', 'car'))) 242 | )); 243 | $this->assertFalse($form->isValid()); 244 | } 245 | 246 | /** 247 | * Testing the between test, but with no max boundary. 248 | * 249 | * @access public 250 | * @expectedException Exception 251 | */ 252 | public function testBetweenNoMaxBoundary() { 253 | $form = new Core\Validate(array( 254 | 'foo' => array('value' => 5, 'tests' => array('between' => array('min' => 1))) 255 | )); 256 | } 257 | 258 | /** 259 | * Testing the between test, but with no mmin boundary. 260 | * 261 | * @access public 262 | * @expectedException Exception 263 | */ 264 | public function testBetweenNoMinBoundary() { 265 | $form = new Core\Validate(array( 266 | 'foo' => array('value' => 5, 'tests' => array('between' => array('max' => 10))) 267 | )); 268 | } 269 | 270 | /** 271 | * Testing the between test. 272 | * 273 | * @access public 274 | */ 275 | public function testBetween() { 276 | // Pass 277 | $form = new Core\Validate(array( 278 | 'foo' => array('value' => 5, 'tests' => array('between' => array('min' => 1, 'max' => 10))) 279 | )); 280 | $this->assertTrue($form->isValid()); 281 | 282 | // Fail, not an int 283 | $form = new Core\Validate(array( 284 | 'foo' => array('value' => 'bar', 'tests' => array('between' => array('min' => 1, 'max' => 10))) 285 | )); 286 | $this->assertFalse($form->isValid()); 287 | 288 | // Fail, too low 289 | $form = new Core\Validate(array( 290 | 'foo' => array('value' => 2, 'tests' => array('between' => array('min' => 5, 'max' => 10))) 291 | )); 292 | $this->assertFalse($form->isValid()); 293 | 294 | // Fail, too high 295 | $form = new Core\Validate(array( 296 | 'foo' => array('value' => 10, 'tests' => array('between' => array('min' => 1, 'max' => 5))) 297 | )); 298 | $this->assertFalse($form->isValid()); 299 | } 300 | } -------------------------------------------------------------------------------- /Tests/ViewHelperTest.php: -------------------------------------------------------------------------------- 1 | test(array('testVar' => 'foo')); 19 | 20 | // And test 21 | $this->assertEquals($viewHelperContent, 'Test var: foo'); 22 | } 23 | } -------------------------------------------------------------------------------- /Tests/ViewTest.php: -------------------------------------------------------------------------------- 1 | addVariable('foo', 'bar'); 13 | $this->assertEquals('bar', $view->getVariable('foo')); 14 | } 15 | 16 | /** 17 | * Change to a layout that does not exist. 18 | * 19 | * @access public 20 | * @expectedException Exception 21 | */ 22 | public function testChangingLayoutToNonExistentLayout() { 23 | $controller = new MyProject\Controller\Index(); 24 | $controller->setLayout('foo'); 25 | } 26 | 27 | /** 28 | * Change the layout. 29 | * 30 | * @access public 31 | */ 32 | public function testChangingLayout() { 33 | $controller = new MyProject\Controller\Index(); 34 | $controller->setLayout('default'); 35 | $this->assertEquals('default', $controller->view->layout); 36 | } 37 | } -------------------------------------------------------------------------------- /Web/.htaccess: -------------------------------------------------------------------------------- 1 | # Either "Dev" or "Live" 2 | SetEnv APP_ENVIRONMENT "Dev" 3 | 4 | # Redirect all requests to our index.php file 5 | RewriteEngine On 6 | RewriteCond %{REQUEST_FILENAME} -s [OR] 7 | RewriteCond %{REQUEST_FILENAME} -l [OR] 8 | RewriteCond %{REQUEST_FILENAME} -d 9 | RewriteRule ^.*$ - [NC,L] 10 | RewriteRule ^.*$ index.php [NC,L] -------------------------------------------------------------------------------- /Web/assets/css/styles.css: -------------------------------------------------------------------------------- 1 | body{margin:0;padding:0;background:#EEE;color:#555;font-family:Arial;font-size:14px} 2 | 3 | /* Contains the content to 960px wide */ 4 | #container{position:relative;width:960px;margin:20px auto} 5 | .section{position:relative;background:#FFF;margin-bottom:30px;padding:40px 80px;border-bottom:2px solid #CCC;-webkit-border-radius:8px;-moz-border-radius:8px;border-radius:8px} 6 | 7 | /* Typography */ 8 | h1{font-size:48px;margin:0;padding-bottom:0;letter-spacing:-2px;font-weight:normal} 9 | h2{font-family:Arial;font-size:28px;margin-bottom:0;padding-bottom:0;letter-spacing:-1px;font-weight:normal} 10 | h3{} 11 | p,ol{margin-bottom:25px} 12 | p,li{line-height:30px} 13 | a{color:#4fbae9;text-decoration:underline} 14 | a:hover{text-decoration:none} 15 | a img{border:none} 16 | abbr{border-bottom:1px dotted #BBB;cursor:help} 17 | hr{margin:30px 0;padding:0;background:#FFF;color:#FFF;border:0;border-bottom:1px dotted #BBB} 18 | code,pre{background-color:#f8f8f8;border:1px solid #ccc;font-size:13px;line-height:19px;overflow:auto;padding:4px 5px;border-radius:3px} 19 | 20 | /* Success message */ 21 | .success{background:#080;color:#FFF;padding:10px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px} 22 | 23 | /* Clear floated divs */ 24 | .clear{clear:both} 25 | 26 | /* Profiler */ 27 | #profiler{margin:20px auto;padding:20px 0;width:960px;background:#FFF;border-bottom:2px solid #CCC;-webkit-border-radius:8px;-moz-border-radius:8px;border-radius:8px} 28 | #profiler h2{padding-left:80px} 29 | 30 | #profiler-legend{margin:-38px 85px 0 0;font-size:11px;text-align:right} 31 | .legend-item{padding:0 4px} 32 | .legend-title{padding-right:10px} 33 | 34 | #profiler-items{margin:40px 80px 20px;padding-right:120px;background:url(../img/profiler-bg.png?v2) repeat 5px 0} 35 | #profiler .item{display:block;position:relative;margin-top:25px;min-width:2px;height:10px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px} 36 | .item-core{background:#BDC3C6} 37 | .item-controller{background:#006594} 38 | .item-action{background:#4AB2D6} 39 | .item-helper{background:#F60} 40 | .item-parse{background:#7BC342} 41 | #profiler .item > span{position:absolute;top:-18px;left:0;font-size:11px;white-space:nowrap} 42 | #profiler .item span span{visibility:hidden} 43 | #profiler .item:hover span span{visibility:visible} -------------------------------------------------------------------------------- /Web/assets/img/profiler-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisjhill/MVC/fa863edfa034fcdadea243f2d14419c88d54e90e/Web/assets/img/profiler-bg.png -------------------------------------------------------------------------------- /Web/assets/img/profiler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisjhill/MVC/fa863edfa034fcdadea243f2d14419c88d54e90e/Web/assets/img/profiler.png -------------------------------------------------------------------------------- /Web/assets/js/scripts.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisjhill/MVC/fa863edfa034fcdadea243f2d14419c88d54e90e/Web/assets/js/scripts.js -------------------------------------------------------------------------------- /Web/index.php: -------------------------------------------------------------------------------- 1 |