Controller -> Router, Display -> Response

Just gutting the brains of this thing.
This commit is contained in:
Josh Sherman 2014-09-25 20:55:11 -04:00
parent b67269a202
commit 94f46fc583
5 changed files with 246 additions and 458 deletions

View file

@ -1,206 +0,0 @@
<?php
/**
* Single Entry Controller
*
* PHP version 5
*
* Licensed under The MIT License
* Redistribution of these files must retain the above copyright notice.
*
* @author Josh Sherman <josh@gravityblvd.com>
* @copyright Copyright 2007-2014, Josh Sherman
* @license http://www.opensource.org/licenses/mit-license.html
* @package PICKLES
* @link https://github.com/joshtronic/pickles
*/
/**
* Controller Class
*
* The heavy lifter of PICKLES, makes the calls to get the session and
* configuration loaded. Loads modules, serves up user authentication when the
* module asks for it, and loads the viewer that the module requested. Default
* values are present to make things easier on the user.
*
* @usage <code>new Controller();</code>
*/
class Controller extends Object
{
/**
* Constructor
*
* To save a few keystrokes, the Controller is executed as part of the
* constructor instead of via a method. You either want the Controller or
* you don't.
*/
public function __construct()
{
parent::__construct();
try
{
// Catches requests that aren't lowercase
$lowercase_request = strtolower($_REQUEST['request']);
if ($_REQUEST['request'] != $lowercase_request)
{
// @todo Rework the Browser class to handle the 301 (perhaps redirect301()) to not break other code
header('Location: ' . substr_replace($_SERVER['REQUEST_URI'], $lowercase_request, 1, strlen($lowercase_request)), true, 301);
throw new Exception();
}
// Grabs the requested page
$request = $_REQUEST['request'];
// Loads the module's information
$module_class = strtr($request, '/', '_');
$module_filename = SITE_MODULE_PATH . $request . '.php';
$module_exists = file_exists($module_filename);
// Attempts to instantiate the requested module
if ($module_exists)
{
if (class_exists($module_class))
{
$module = new $module_class;
}
}
// No module instantiated, load up a generic Module
if (!isset($module))
{
$module = new Module();
}
// Determines if we need to serve over HTTP or HTTPS
if ($module->secure == false && isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'])
{
header('Location: http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], true, 301);
throw new Exception();
}
elseif ($module->secure == true && (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] == false))
{
header('Location: https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], true, 301);
throw new Exception();
}
// Gets the profiler status
$profiler = $this->config->pickles['profiler'];
$profiler = $profiler === true || stripos($profiler, 'timers') !== false;
$default_method = '__default';
$role_method = null;
// Attempts to execute the default method
// @todo Seems a bit redundant, refactor
if ($default_method == $role_method || method_exists($module, $default_method))
{
// Starts a timer before the module is executed
if ($profiler)
{
Profiler::timer('module ' . $default_method);
}
$valid_request = false;
$error_message = 'An unexpected error has occurred.';
// Determines if the request method is valid for this request
if ($module->method)
{
if (!is_array($module->method))
{
$module->method = [$module->method];
}
foreach ($module->method as $method)
{
if ($_SERVER['REQUEST_METHOD'] == $method)
{
$valid_request = true;
break;
}
}
if (!$valid_request)
{
// @todo Should probably utilize that AJAX flag to determine the type of return
$error_message = 'There was a problem with your request method.';
}
}
else
{
$valid_request = true;
}
$valid_form_input = true;
if ($valid_request && $module->validate)
{
$validation_errors = $module->__validate();
if ($validation_errors)
{
$error_message = implode(' ', $validation_errors);
$valid_form_input = false;
}
}
/**
* Note to Self: When building in caching will need to let the
* module know to use the cache, either passing in a variable
* or setting it on the object
*/
if ($valid_request && $valid_form_input)
{
$module_return = $module->$default_method();
if (!is_array($module_return))
{
$module_return = $module->response;
}
else
{
$module_return = array_merge($module_return, $module->response);
}
}
// Stops the module timer
if ($profiler)
{
Profiler::timer('module ' . $default_method);
}
$display = new Display($module);
}
// Starts a timer for the display rendering
if ($profiler)
{
Profiler::timer('display render');
}
// Renders the content
$output = $display->render();
// Stops the display timer
if ($profiler)
{
Profiler::timer('display render');
}
}
catch (Exception $e)
{
$output = $e->getMessage();
}
echo $output;
// Display the Profiler's report if the stars are aligned
if ($this->config->pickles['profiler'])
{
Profiler::report();
}
}
}

View file

@ -1,96 +0,0 @@
<?php
/**
*Display Class File for PICKLES
*
* PHP version 5
*
* Licensed under The MIT License
* Redistribution of these files must retain the above copyright notice.
*
* @author Josh Sherman <josh@gravityblvd.com>
* @copyright Copyright 2007-2014, Josh Sherman
* @license http://www.opensource.org/licenses/mit-license.html
* @package PICKLES
* @link https://github.com/joshtronic/pickles
*/
/**
* Display Class
*
* If you can see it then it probably happened in here.
*/
class Display extends Object
{
/**
* Module
*
* This is the module we are attempting to display output for.
*/
public $module = null;
public function __construct($module = null)
{
if ($module && $module instanceof Module)
{
$this->module = $module;
}
}
public function render()
{
try
{
// Starts up the buffer so we can capture it
ob_start();
if (!is_array($this->module->response))
{
$this->module->response = [$this->module->response];
}
// Checks for the PHPSESSID in the query string
if (stripos($_SERVER['REQUEST_URI'], '?PHPSESSID=') === false)
{
// XHTML compliancy stuff
// @todo Wonder if this could be yanked now that we're in HTML5 land
ini_set('arg_separator.output', '&amp;');
ini_set('url_rewriter.tags', 'a=href,area=href,frame=src,input=src,fieldset=');
header('Content-type: text/html; charset=UTF-8');
}
else
{
// Redirect so Google knows to index the page without the session ID
list($request_uri, $phpsessid) = explode('?PHPSESSID=', $_SERVER['REQUEST_URI'], 2);
header('HTTP/1.1 301 Moved Permanently');
header('Location: ' . $request_uri);
throw new Exception('Requested URI contains PHPSESSID, redirecting.');
}
$response = [
'meta' => [
'status' => $this->module->status,
'message' => $this->module->message,
],
];
if ($this->module->response)
{
$response['response'] = $this->module->response;
}
header('Content-type: application/json');
$pretty = isset($_REQUEST['pretty']) ? JSON_PRETTY_PRINT : false;
echo json_encode($response, $pretty);
return ob_get_clean();
}
catch (Exception $e)
{
return $e->getMessage();
}
}
}

View file

@ -27,41 +27,6 @@
*/ */
abstract class Module extends Object abstract class Module extends Object
{ {
/**
* Page Title
*
* @var string, null by default
* @todo Abandon for $this->meta
*/
public $title = null;
/**
* Meta Description
*
* @var string, null by default
* @todo Abandon for $this->meta
*/
public $description = null;
/**
* Meta Keywords (comma separated)
*
* @var string, null by default
* @todo Abandon for $this->meta
*/
public $keywords = null;
/**
* Meta Data
*
* @var array
*/
public $meta = [
'title' => '',
'description' => '',
'keywords' => '',
];
/** /**
* Secure * Secure
* *
@ -72,20 +37,13 @@ abstract class Module extends Object
public $secure = false; public $secure = false;
/** /**
* Security Settings * Filter
* *
* @var boolean, null by default * Variables to filter.
*
* @var array
*/ */
public $security = null; public $filter = [];
/**
* Method
*
* Request methods that are allowed to access the module.
*
* @var string or array, null by default
*/
public $method = null;
/** /**
* Validate * Validate
@ -96,46 +54,13 @@ abstract class Module extends Object
*/ */
public $validate = []; public $validate = [];
/**
* Template
*
* This is the parent template that will be loaded if you are using the
* 'template' return type in the Display class. Parent templates are found
* in ./templates/__shared and use the phtml extension.
*
* @var string, 'index' by default
*/
public $template = 'index';
/**
* Response
*
* Array of data that will be rendered as part of the display. This is
* somewhat of a one way trip as you cannot get the variable unless you
* reference the response array explicitly, $this->response['variable']
*
* @var array
*/
public $response = [];
/**
* Output
*
* What should the class render as output? This can be a string or an array
* containing either 'json', 'rss', 'template' or 'xml'. Default is to use
* templates and if the template is not present, fall back to JSON.
*
* @var mixed string or array
*/
public $output = ['template', 'json'];
// @todo // @todo
public $status = 200; public $status = 200;
public $message = 'OK'; public $message = 'OK';
public $echo = false; public $echo = false;
public $limit = false; public $limit = false;
public $offset = false; public $offset = false;
public $errors = []; public $errors = [];
// @todo if $status != 200 && $message == 'OK' ... // @todo if $status != 200 && $message == 'OK' ...
@ -146,81 +71,10 @@ abstract class Module extends Object
* variable to tell it to automatically run the __default() method. This is * variable to tell it to automatically run the __default() method. This is
* typically used when a module is called outside of the scope of the * typically used when a module is called outside of the scope of the
* controller (the registration page calls the login page in this manner. * controller (the registration page calls the login page in this manner.
*
* @param boolean $autorun optional flag to autorun __default()
@ @param boolean $filter optional flag to disable autorun filtering
* @param boolean $valiate optional flag to disable autorun validation
*/ */
public function __construct($autorun = false, $filter = true, $validate = true) public function __construct()
{ {
parent::__construct(['cache', 'db']); parent::__construct(['cache', 'db']);
if ($autorun)
{
if ($filter)
{
// @todo
//$this->__filter();
}
if ($validate)
{
$errors = $this->__validate();
if (!$errors)
{
// @todo Fatal error perhaps?
exit('Errors encountered, this is a @todo for form validation when calling modules from inside of modules');
}
}
$this->__default();
}
}
/**
* Default "Magic" Method
*
* The __default() method is where you want to place any code that needs to
* be executed at runtime.
*
* @abstract
*/
abstract public function __default();
/**
* Magic Setter Method
*
* Places undefined properties into the response array as part of the
* module's payload.
*
* @param string $variable name of the variable to be set
* @param mixed $value value of the variable to be set
*/
public function __set($variable, $value)
{
$this->response[$variable] = $value;
}
/**
* Magic Getter Method
*
* Any variables not defined in this class are set in the response array
* and default to false if not defined there.
*
* @param string $name name of the variable requested
* @return mixed value of the variable or boolean false
*/
public function __get($name)
{
if (!isset($this->response[$name]))
{
return false;
}
else
{
return $this->response[$name];
}
} }
/** /**

46
src/classes/Response.php Normal file
View file

@ -0,0 +1,46 @@
<?php
class Response extends Object
{
public $status = 200;
public $message = 'OK';
public $echo = false;
public $limit = false;
public $offset = false;
public $errors = false;
public $response = false;
public $profiler = false;
public function respond()
{
header('Content-type: application/json');
$meta = [
'status' => $this->status,
'message' => $this->message,
];
foreach (['echo', 'limit', 'offset', 'errors'] as $variable)
{
if ($this->$variable)
{
$meta[$variable] = $this->$variable;
}
}
$response = ['meta' => $meta];
foreach (['response', 'profiler'] as $variable)
{
if ($this->$variable)
{
$response[$variable] = $this->$variable;
}
}
$pretty = isset($_REQUEST['pretty']) ? JSON_PRETTY_PRINT : false;
exit(json_encode($response, $pretty));
}
}

190
src/classes/Router.php Normal file
View file

@ -0,0 +1,190 @@
<?php
/**
* Single Entry Router
*
* PHP version 5
*
* Licensed under The MIT License
* Redistribution of these files must retain the above copyright notice.
*
* @author Josh Sherman <josh@gravityblvd.com>
* @copyright Copyright 2007-2014, Josh Sherman
* @license http://www.opensource.org/licenses/mit-license.html
* @package PICKLES
* @link https://github.com/joshtronic/pickles
*/
/**
* Router Class
*
* The heavy lifter of PICKLES, makes the calls to get the session and
* configuration loaded. Loads modules, serves up user authentication when the
* module asks for it, and loads the viewer that the module requested. Default
* values are present to make things easier on the user.
*
* @usage <code>new Router();</code>
*/
class Router extends Object
{
/**
* Constructor
*
* To save a few keystrokes, the Controller is executed as part of the
* constructor instead of via a method. You either want the Controller or
* you don't.
*/
public function __construct()
{
parent::__construct();
try
{
// Catches requests that aren't lowercase
$lowercase_request = strtolower($_REQUEST['request']);
if ($_REQUEST['request'] != $lowercase_request)
{
// @todo Rework the Browser class to handle the 301 (perhaps redirect301()) to not break other code
header('Location: ' . substr_replace($_SERVER['REQUEST_URI'], $lowercase_request, 1, strlen($lowercase_request)), true, 301);
throw new Exception();
}
// Grabs the requested page
$request = $_REQUEST['request'];
$components = explode('/', $request);
$version = array_shift($components);
$nouns = [];
$uids = [];
// Loops through the components to determine nouns and IDs
foreach ($components as $index => $component)
{
if ($index % 2)
{
$uids[end($nouns)] = $component;
}
else
{
$nouns[] = $component;
}
}
array_unshift($nouns, $version);
$class = implode('_', $nouns);
array_unshift($nouns, SITE_MODULE_PATH);
$filename = implode('/', $nouns) . '.php';
if (file_exists($filename))
{
if (class_exists($class))
{
$resource = new $class($uids);
// Determines if we need to serve over HTTP or HTTPS
if ($resource->secure == false && isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'])
{
header('Location: http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], true, 301);
throw new Exception();
}
elseif ($resource->secure == true && (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] == false))
{
header('Location: https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], true, 301);
throw new Exception();
}
// Checks for the PHPSESSID in the query string
if (stripos($_SERVER['REQUEST_URI'], '?PHPSESSID=') === false)
{
// XHTML compliancy stuff
// @todo Wonder if this could be yanked now that we're in HTML5 land
ini_set('arg_separator.output', '&amp;');
ini_set('url_rewriter.tags', 'a=href,area=href,frame=src,input=src,fieldset=');
// @todo Will want to generate the header based on if we're pushing documentation or API
header('Content-type: text/html; charset=UTF-8');
// header('Content-type: application/json');
//header('Content-type: application/json; charset=UTF-8');
}
else
{
// Redirect so Google knows to index the page without the session ID
list($request_uri, $phpsessid) = explode('?PHPSESSID=', $_SERVER['REQUEST_URI'], 2);
header('HTTP/1.1 301 Moved Permanently');
header('Location: ' . $request_uri);
throw new Exception('Requested URI contains PHPSESSID, redirecting.');
}
// Gets the profiler status
$profiler = $this->config->pickles['profiler'];
$profiler = $profiler === true || stripos($profiler, 'timers') !== false;
$method = strtolower($_SERVER['REQUEST_METHOD']);
if (method_exists($resource, $method))
{
// Starts a timer before the resource is executed
if ($profiler)
{
Profiler::timer('resource ' . $method);
}
$response = new Response();
if ($resource->validate)
{
$validation_errors = $resource->__validate();
if ($validation_errors)
{
$response->status = 400;
$response->message = implode(' ', $validation_errors);
}
}
if ($response->status == 200)
{
$resource_return = $resource->$method();
if ($resource_return)
{
$response->response = $resource_return;
}
}
// Stops the resource timer
if ($profiler)
{
Profiler::timer('resource ' . $method);
}
$response->respond();
}
else
{
throw new Exception('Missing method');
}
}
else
{
throw new Exception('Missing class');
}
}
else
{
throw new Exception('Missing file');
}
}
catch (Exception $e)
{
// @todo
exit('fuuuu');
$output = $e->getMessage();
}
}
}