From 78804a1dc21baf4763116e17c4440501fe79a6bc Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sun, 30 Mar 2014 20:55:15 -0400 Subject: [PATCH 01/16] Add RootController::StopWithRedirect() as a redirect shortcut. --- http/root_controller.php | 12 +++++++++++ testing/tests/http/root_controller_test.php | 23 ++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/http/root_controller.php b/http/root_controller.php index 97dd137..03e4747 100644 --- a/http/root_controller.php +++ b/http/root_controller.php @@ -145,6 +145,18 @@ class RootController $this->Stop(); } + /*! + Sets the response code to HTTP 302 FOUND and redirects the page to a new + location. + @param string The destination location of the redirect. + */ + public function StopWithRedirect($location) + { + $this->response->headers['Location'] = $location; + $this->StopWithCode(ResponseCode::FOUND); + } + + /*! Wrapper around PHP exit(). */ diff --git a/testing/tests/http/root_controller_test.php b/testing/tests/http/root_controller_test.php index 9a2d74c..bd10a93 100644 --- a/testing/tests/http/root_controller_test.php +++ b/testing/tests/http/root_controller_test.php @@ -129,7 +129,7 @@ class RootControllerTest extends \PHPUnit_Framework_TestCase $mock = $this->ConfigureMock(array('Stop'), $globals); $mock->request()->url = 'another/action'; - + $mock->expects($this->once()) ->method('Stop'); @@ -161,6 +161,27 @@ class RootControllerTest extends \PHPUnit_Framework_TestCase $mock->Stop(); } + public function testStopWithRedirect() + { + $globals = array(); + $mock = $this->ConfigureMock(array('_Exit'), $globals); + $mock->expects($this->once()) + ->method('_Exit'); + + $output_filter = $this->getMock('hoplite\http\OutputFilter', array(), array($mock)); + $output_filter->expects($this->once()) + ->method('FilterOutput') + ->with($this->isInstanceOf('hoplite\http\Request'), + $this->isInstanceOf('hoplite\http\Response')); + + $mock->set_output_filter($output_filter); + + $mock->StopWithRedirect('/foo/bar'); + + $this->assertEquals('/foo/bar', $mock->response()->headers['Location']); + $this->assertEquals(http\ResponseCode::FOUND, $mock->response()->response_code); + } + public function testAbsolutify() { $globals = array( -- 2.22.5 From dc08cff4b89d90466ebc2b3f2a8dc38b611558ab Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Mon, 31 Mar 2014 00:16:15 -0400 Subject: [PATCH 02/16] Do not call RootControllerDelegate through WeakInterface. This adds too much indirection in critical codepaths. --- http/root_controller.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/http/root_controller.php b/http/root_controller.php index 03e4747..f24b1f9 100644 --- a/http/root_controller.php +++ b/http/root_controller.php @@ -16,7 +16,6 @@ namespace hoplite\http; -require_once HOPLITE_ROOT . '/base/weak_interface.php'; require_once HOPLITE_ROOT . '/http/request.php'; require_once HOPLITE_ROOT . '/http/response.php'; require_once HOPLITE_ROOT . '/http/response_code.php'; @@ -39,7 +38,7 @@ class RootController /*! @var OutputFilter */ private $output_filter = NULL; - /*! @var WeakInterface */ + /*! @var RootControllerDelegate */ private $delegate = NULL; /*! @@ -59,7 +58,6 @@ class RootController '_COOKIE' => &$globals['_COOKIE'], '_SERVER' => &$globals['_SERVER'] ); - $this->delegate = new \hoplite\base\WeakInterface('hoplite\http\RootControllerDelegate'); } /*! Accessors */ @@ -78,7 +76,7 @@ class RootController /*! Sets the delegate. */ public function set_delegate($delegate) { - $this->delegate->Bind($delegate); + $this->delegate = $delegate; } public function delegate() { @@ -257,8 +255,7 @@ class RootController } /*! - Delegate for the root controller. The controller uses WeakInterface to call - these methods, so they're all optional. + Delegate for the root controller. All methods are optional. */ interface RootControllerDelegate { -- 2.22.5 From 9730b0f2489a9cba8950cdcb37ef65509f3ba1a0 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Mon, 31 Mar 2014 09:16:26 -0400 Subject: [PATCH 03/16] Check for the presence of a RootControllerDelegate before calling it. --- http/root_controller.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/http/root_controller.php b/http/root_controller.php index f24b1f9..75a25d7 100644 --- a/http/root_controller.php +++ b/http/root_controller.php @@ -80,7 +80,7 @@ class RootController } public function delegate() { - return $this->delegate->Get(); + return $this->delegate; } /*! @@ -113,7 +113,8 @@ class RootController // Register self as the active instance. $GLOBALS[__CLASS__] = $this; - $this->delegate->OnInitialRequest($this->request, $this->response); + if ($this->delegate) + $this->delegate->OnInitialRequest($this->request, $this->response); // Dispatch the request to an Action. $this->RouteRequest($this->request); @@ -129,7 +130,8 @@ class RootController */ public function Stop() { - $this->delegate->WillStop($this->request, $this->response); + if ($this->delegate) + $this->delegate->WillStop($this->request, $this->response); $this->output_filter->FilterOutput($this->request, $this->response); $this->_Exit(); } @@ -170,7 +172,8 @@ class RootController */ public function RouteRequest(Request $request) { - $this->delegate->WillRouteRequest($request, $this->response); + if ($this->delegate) + $this->delegate->WillRouteRequest($request, $this->response); $url_map_value = $this->url_map->Evaluate($request); @@ -193,13 +196,15 @@ class RootController */ public function InvokeAction(Action $action) { - $this->delegate->WillInvokeAction($action, $this->request, $this->response); + if ($this->delegate) + $this->delegate->WillInvokeAction($action, $this->request, $this->response); $action->FilterRequest($this->request, $this->response); $action->Invoke($this->request, $this->response); $action->FilterResponse($this->request, $this->response); - $this->delegate->DidInvokeAction($action, $this->request, $this->response); + if ($this->delegate) + $this->delegate->DidInvokeAction($action, $this->request, $this->response); } /*! -- 2.22.5 From 38d8c893ebfb727e6f70ee86a155431e8d9fd571 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Mon, 31 Mar 2014 09:18:30 -0400 Subject: [PATCH 04/16] In views\Template, do not use base/filter.php wrappers where it's not necessary. --- views/template.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/views/template.php b/views/template.php index c9ca39c..2aeb455 100644 --- a/views/template.php +++ b/views/template.php @@ -256,21 +256,20 @@ class Template if ($formatter_pos === FALSE) return 'echo hoplite\\base\\filter\\String(' . $macro . ')'; + $expression = trim(substr($macro, 0, $formatter_pos)); + // Otherwise, apply the right filter. $formatter = trim(substr($macro, $formatter_pos + 1)); $function = ''; switch (strtolower($formatter)) { - case 'int': $function = 'Int'; break; - case 'float': $function = 'Float'; break; - case 'str': $function = 'String'; break; - case 'raw': $function = 'RawString'; break; + case 'int': return "echo intval($expression)"; + case 'float': return "echo floatval($expression)"; + case 'str': return "echo hoplite\\base\\filter\\String($expression)"; + case 'raw': return "echo $expression"; + case 'json': return "echo json_encode($expression)"; default: throw new TemplateException('Invalid macro formatter "' . $formatter . '"'); } - - // Now get the expression and return a PHP statement. - $expression = trim(substr($macro, 0, $formatter_pos)); - return 'echo hoplite\\base\\filter\\' . $function . '(' . $expression . ')'; } protected function _ProcessBuiltin($macro) -- 2.22.5 From 87adeac143e78f72501a81353d91a01d18e157fa Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Mon, 25 May 2015 17:12:52 -0400 Subject: [PATCH 05/16] The default behavior for RestAction should be to error METHOD_NOT_ALLOWED. --- http/rest_action.php | 27 +++++++++++++++++++++------ testing/tests/http/fixtures.php | 4 ---- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/http/rest_action.php b/http/rest_action.php index 35c5191..b3d2c5d 100644 --- a/http/rest_action.php +++ b/http/rest_action.php @@ -1,11 +1,11 @@ controller()->StopWithCode(ResponseCode::METHOD_NOT_ALLOWED); + } + + public function DoPost(Request $request, Response $response) + { + $this->controller()->StopWithCode(ResponseCode::METHOD_NOT_ALLOWED); + } + + public function DoDelete(Request $request, Response $response) + { + $this->controller()->StopWithCode(ResponseCode::METHOD_NOT_ALLOWED); + } + + public function DoPut(Request $request, Response $response) + { + $this->controller()->StopWithCode(ResponseCode::METHOD_NOT_ALLOWED); + } } diff --git a/testing/tests/http/fixtures.php b/testing/tests/http/fixtures.php index fcd3082..20dff31 100644 --- a/testing/tests/http/fixtures.php +++ b/testing/tests/http/fixtures.php @@ -26,22 +26,18 @@ class TestRestAction extends \hoplite\http\RestAction public function DoGet(http\Request $request, http\Response $response) { - parent::DoGet($request, $response); $this->did_get = TRUE; } public function DoPost(http\Request $request, http\Response $response) { - parent::DoPost($request, $response); $this->did_post = TRUE; } public function DoDelete(http\Request $request, http\Response $response) { - parent::DoDelete($request, $response); $this->did_delete = TRUE; } public function DoPut(http\Request $request, http\Response $response) { - parent::DoPut($request, $response); $this->did_put = TRUE; } } -- 2.22.5 From 2dbb698f2da9339190cc1e3d2e8bfb1f803ebc84 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Mon, 25 May 2015 17:44:48 -0400 Subject: [PATCH 06/16] Cache templates with the full template path rather than just the name. This allows multiple TemplateLoaders to operate on different template_paths but with the same template_cache. --- testing/tests/views/template_loader_test.php | 12 ++++++++---- views/template_loader.php | 13 ++++++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/testing/tests/views/template_loader_test.php b/testing/tests/views/template_loader_test.php index 8dfc66d..d903f46 100644 --- a/testing/tests/views/template_loader_test.php +++ b/testing/tests/views/template_loader_test.php @@ -40,16 +40,18 @@ class TemplateLoaderTest extends \PHPUnit_Framework_TestCase public function testCacheMiss() { + $file_path = sprintf($this->fixture->template_path(), 'cache_test'); + $this->cache->expects($this->once()) ->method('GetTemplateDataForName') - ->with($this->equalTo('cache_test')) + ->with($this->equalTo($file_path)) ->will($this->returnValue(NULL)); - $expected = file_get_contents(sprintf($this->fixture->template_path(), 'cache_test')); + $expected = file_get_contents($file_path); $this->cache->expects($this->once()) ->method('StoreCompiledTemplate') - ->with($this->equalTo('cache_test'), + ->with($this->equalTo($file_path), $this->greaterThan(0), $this->equalTo($expected)); @@ -59,10 +61,12 @@ class TemplateLoaderTest extends \PHPUnit_Framework_TestCase public function testCacheHit() { + $file_path = sprintf($this->fixture->template_path(), 'cache_test'); + $expected = 'Cache hit!'; $this->cache->expects($this->once()) ->method('GetTemplateDataForName') - ->with($this->equalTo('cache_test')) + ->with($this->equalTo($file_path)) ->will($this->returnValue($expected)); // The cache backend is only consulted once. diff --git a/views/template_loader.php b/views/template_loader.php index 84d461e..4a08b4a 100644 --- a/views/template_loader.php +++ b/views/template_loader.php @@ -114,20 +114,23 @@ class TemplateLoader if (!$this->cache_backend) return; - $fetch_templates = array(); + $fetch_templates = []; + $paths_to_names = []; foreach ($templates AS $name) { // Do not re-cache templates that have already been cached. if (isset($this->cache[$name])) continue; $tpl_path = $this->_TemplatePath($name); - $fetch_templates[$name] = filemtime($tpl_path); + $fetch_templates[$tpl_path] = filemtime($tpl_path); + $paths_to_names[$tpl_path] = $name; } $profile = Profiling::IsProfilingEnabled(); $cached_templates = $this->cache_backend->GetMultipleTemplates($fetch_templates); - foreach ($cached_templates AS $name => $data) { + foreach ($cached_templates AS $tpl_path => $data) { + $name = $paths_to_names[$tpl_path]; $this->cache[$name] = Template::NewWithCompiledData($name, $data); if ($profile) $this->usage[$name] = 0; @@ -162,7 +165,7 @@ class TemplateLoader if (!$this->cache_backend) return NULL; - $data = $this->cache_backend->GetTemplateDataForName($name, filemtime($tpl_path)); + $data = $this->cache_backend->GetTemplateDataForName($tpl_path, filemtime($tpl_path)); if (!$data) return NULL; @@ -189,7 +192,7 @@ class TemplateLoader if ($this->cache_backend) { $this->cache_backend->StoreCompiledTemplate( - $name, filemtime($tpl_path), $template->template()); + $tpl_path, filemtime($tpl_path), $template->template()); } return $template; -- 2.22.5 From 31600eeb381d24a83267ad4d892df3692ed0faea Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Mon, 25 May 2015 22:24:31 -0400 Subject: [PATCH 07/16] Use filter_var() over base/filter.php, which is going away soon. --- data/profiling_pdo.php | 3 +-- views/template.php | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/data/profiling_pdo.php b/data/profiling_pdo.php index 4b1a9d2..cd055b8 100644 --- a/data/profiling_pdo.php +++ b/data/profiling_pdo.php @@ -18,7 +18,6 @@ namespace hoplite\data; use \hoplite\base\Profiling; -require_once HOPLITE_ROOT . '/base/filter.php'; require_once HOPLITE_ROOT . '/base/profiling.php'; /*! @@ -118,7 +117,7 @@ class ProfilingPDO extends \PDO $debug .= "\n\t\t\t$query[query]\n\n"; if (isset($query['params'])) { $debug .= "\t\t\t
    \n\t\t\t\t
  1. "; - $debug .= implode("
  2. \n\t\t\t\t
  3. ", \hoplite\base\filter\String($query['params'])); + $debug .= implode("
  4. \n\t\t\t\t
  5. ", filter_var_array($query['params'], FILTER_SANITIZE_SPECIAL_CHARS)); $debug .= "
  6. \n\t\t\t
\n"; } $debug .= "\n\t\t\t
(" . diff --git a/views/template.php b/views/template.php index 2aeb455..da6857d 100644 --- a/views/template.php +++ b/views/template.php @@ -18,7 +18,6 @@ namespace hoplite\views; use \hoplite\base\Profiling; -require_once HOPLITE_ROOT . '/base/filter.php'; require_once HOPLITE_ROOT . '/base/profiling.php'; /*! @@ -254,7 +253,7 @@ class Template // No specifier defaults to escaped string. if ($formatter_pos === FALSE) - return 'echo hoplite\\base\\filter\\String(' . $macro . ')'; + return 'echo filter_var(' . $macro . ', FILTER_SANITIZE_STRING)'; $expression = trim(substr($macro, 0, $formatter_pos)); @@ -264,7 +263,7 @@ class Template switch (strtolower($formatter)) { case 'int': return "echo intval($expression)"; case 'float': return "echo floatval($expression)"; - case 'str': return "echo hoplite\\base\\filter\\String($expression)"; + case 'str': return "echo filter_var($expression, FILTER_SANITIZE_STRING)"; case 'raw': return "echo $expression"; case 'json': return "echo json_encode($expression)"; default: -- 2.22.5 From ac42824b920b4073ff7c1823779f10a10832edc9 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Wed, 27 May 2015 00:22:54 -0400 Subject: [PATCH 08/16] Add Request::Filter and ::FilterArray. --- http/request.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/http/request.php b/http/request.php index ff48b00..031518b 100644 --- a/http/request.php +++ b/http/request.php @@ -40,4 +40,24 @@ class Request extends \hoplite\base\StrictObject { $this->url = $url; } + + /*! + Wrapper around filter_input() that stores the result in the ::$data field. + */ + public function Filter($type, $name, $filter=FILTER_SANITIZE_STRING, $options=NULL) + { + $rv = filter_input($type, $name, $filter, $options); + $this->data[$name] = $rv; + return $rv; + } + + /*! + Wrapper around filter_input() that merges the result in the ::$data field. + */ + public function FilterArray($type, $definition, $add_empty=TRUE) + { + $rv = filter_input_array($type, $definition, $add_empty); + $this->data = array_merge($this->data, $rv); + return $rv; + } } -- 2.22.5 From 09c49802e246f2fe7e326d2afe3dfb832f43738c Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sun, 31 May 2015 16:23:31 -0400 Subject: [PATCH 09/16] Introduce FrontController as the replacement for RootController. The FrontController has no delegate, but instead uses Interceptors to allow middleware to further process the request or to interrupt control flow. One integral change is that if no Action is found for the Request in the UrlMap, then no action will be taken by the FrontController. Instead, a NotFoundInterceptor should be used to send a HTTP 404 response in this event. --- data/controller.php | 4 +- http/action.php | 22 +-- http/action_controller.php | 3 +- ...ot_controller.php => front_controller.php} | 107 ++++-------- http/interceptor.php | 35 ++++ http/not_found_interceptor.php | 33 ++++ http/output_filter.php | 10 +- http/rest_adapter.php | 3 +- http/url_map.php | 12 +- testing/tests/http/action_controller_test.php | 6 +- ...ler_test.php => front_controller_test.php} | 157 +++++++++++------- testing/tests/http/output_filter_test.php | 4 +- testing/tests/http/rest_action_test.php | 10 +- testing/tests/http/rest_adapter_test.php | 28 ++-- testing/tests/http/url_map_test.php | 2 +- 15 files changed, 246 insertions(+), 190 deletions(-) rename http/{root_controller.php => front_controller.php} (69%) create mode 100644 http/interceptor.php create mode 100644 http/not_found_interceptor.php rename testing/tests/http/{root_controller_test.php => front_controller_test.php} (65%) diff --git a/data/controller.php b/data/controller.php index 79c338b..969c4cf 100644 --- a/data/controller.php +++ b/data/controller.php @@ -34,11 +34,13 @@ abstract class Controller extends http\RestAction protected $model = NULL; /*! Selects the Model object. */ - public function FilterRequest(http\Request $request, http\Response $response) + public function Invoke(http\Request $request, http\Response $response) { $this->model = $this->_SelectModel(); $this->model->SetFrom(array_merge( $request->data, $request->data['_POST'], $request->data['_GET'])); + + parent::Invoke($request, $response); } /*! Returns a new instance of the Model that this object will control. */ diff --git a/http/action.php b/http/action.php index 46f1859..0d8dd63 100644 --- a/http/action.php +++ b/http/action.php @@ -1,11 +1,11 @@ controller = $controller; } - /*! Accesses the RootController */ + /*! Accesses the FrontController */ public function controller() { return $this->controller; } - /*! - Called before the Action is Invoked(). - */ - public function FilterRequest(Request $request, Response $response) {} - /*! Performs the action and fills out the response's data model. */ public abstract function Invoke(Request $request, Response $response); - - /*! - Called after this has been Invoked(). - */ - public function FilterResponse(Request $request, Response $response) {} } diff --git a/http/action_controller.php b/http/action_controller.php index 3da7039..25a4b34 100644 --- a/http/action_controller.php +++ b/http/action_controller.php @@ -34,8 +34,7 @@ class ActionController extends Action { $method = $this->_GetActionMethod($request); if (!method_exists($this, $method)) { - $response->response_code = ResponseCode::NOT_FOUND; - $this->controller()->Stop(); + $this->controller()->SendResponseCode(ResponseCode::NOT_FOUND); return; } diff --git a/http/root_controller.php b/http/front_controller.php similarity index 69% rename from http/root_controller.php rename to http/front_controller.php index 75a25d7..b84f298 100644 --- a/http/root_controller.php +++ b/http/front_controller.php @@ -1,11 +1,11 @@ */ + private $interceptors = []; /*! Creates the controller with the request context information, typicallhy from the global scope ($GLOBALS), but can be injected for testing. - @param UrlMap The routing map - @param OutputFilter The object responsible for decorating output. - @param array& PHP globals array + @param array PHP globals array */ public function __construct(array $globals) { @@ -73,21 +72,18 @@ class RootController $this->output_filter = $output_filter; } - /*! Sets the delegate. */ - public function set_delegate($delegate) - { - $this->delegate = $delegate; - } - public function delegate() + /*! Registers an Interceptor that will be run before executing an Action. */ + public function AddInterceptor(Interceptor $interceptor) { - return $this->delegate; + $this->interceptors[] = $interceptor; } /*! - Createst the Request and Response that are used throughout the duration of - the execution. - */ - public function Run() + Called in index.php to process the current HTTP request. This initializes the + Request object from its data and then routes the request to generate the + response. + */ + public function ProcessRequest() { // The query rewriter module of the webserver rewrites a request from: // http://example.com/webapp/user/view/42 @@ -113,25 +109,20 @@ class RootController // Register self as the active instance. $GLOBALS[__CLASS__] = $this; - if ($this->delegate) - $this->delegate->OnInitialRequest($this->request, $this->response); - // Dispatch the request to an Action. $this->RouteRequest($this->request); // When control returns here, all actions have been invoked and it's time // to start the output filter and exit. - $this->Stop(); + $this->SendResponse(); } /*! Prevents any other Actions from executing. This starts the OutputFilter and then exits. */ - public function Stop() + public function SendResponse() { - if ($this->delegate) - $this->delegate->WillStop($this->request, $this->response); $this->output_filter->FilterOutput($this->request, $this->response); $this->_Exit(); } @@ -139,10 +130,10 @@ class RootController /*! Sets the response code and stops the controller. Returns void. */ - public function StopWithCode($code) + public function SendResponseCode($code) { $this->response->response_code = $code; - $this->Stop(); + $this->SendResponse(); } /*! @@ -150,13 +141,12 @@ class RootController location. @param string The destination location of the redirect. */ - public function StopWithRedirect($location) + public function SendResponseRedirect($location) { $this->response->headers['Location'] = $location; - $this->StopWithCode(ResponseCode::FOUND); + $this->SendResponseCode(ResponseCode::FOUND); } - /*! Wrapper around PHP exit(). */ @@ -166,45 +156,24 @@ class RootController } /*! - Invoked by Run() and can be invoked by others to evaluate and perform the - lookup in the UrlMap. This then calls InvokeAction(). + Given an Request object, this executes the action for the corresponding URL. + The action is located by performing a lookup in the UrlMap. Interceptors + are run before invoking the action. @param Request The Request whose URL will be routed */ public function RouteRequest(Request $request) { - if ($this->delegate) - $this->delegate->WillRouteRequest($request, $this->response); - $url_map_value = $this->url_map->Evaluate($request); $action = NULL; if ($url_map_value) $action = $this->url_map->LookupAction($url_map_value); - if (!$action) { - $this->response->response_code = ResponseCode::NOT_FOUND; - $this->Stop(); - return; - } + foreach ($this->interceptors as $interceptor) + $interceptor->DoIntercept($this, $action, $request, $this->response); - $this->InvokeAction($action); - } - - /*! - Used to run an Action and drive it through its states. - @param Action - */ - public function InvokeAction(Action $action) - { - if ($this->delegate) - $this->delegate->WillInvokeAction($action, $this->request, $this->response); - - $action->FilterRequest($this->request, $this->response); - $action->Invoke($this->request, $this->response); - $action->FilterResponse($this->request, $this->response); - - if ($this->delegate) - $this->delegate->DidInvokeAction($action, $this->request, $this->response); + if ($action) + $action->Invoke($request, $this->response); } /*! @@ -258,19 +227,3 @@ class RootController return $url . $new_path; } } - -/*! - Delegate for the root controller. All methods are optional. -*/ -interface RootControllerDelegate -{ - public function OnInitialRequest(Request $request, Response $response); - - public function WillRouteRequest(Request $request, Response $response); - - public function WillInvokeAction(Action $action, Request $request, Response $response); - - public function DidInvokeAction(Action $action, Request $request, Response $response); - - public function WillStop(Request $request, Response $response); -} diff --git a/http/interceptor.php b/http/interceptor.php new file mode 100644 index 0000000..15bf0ce --- /dev/null +++ b/http/interceptor.php @@ -0,0 +1,35 @@ +. + +namespace hoplite\http; + +/*! + An Interceptor runs as part of FrontController::RouteRequest() to provide + extra processing before the Action is Invoke()ed. The Action is provided to + allow the Interceptor to change the control flow based on characteristics + of the Action processor. An Interceptor may interrupt the control flow by + calling SendResponse() on the controller. +*/ +interface Interceptor +{ + /*! + Performs the action and fills out the response's data model. + */ + public function DoIntercept(FrontController $controller, + Action $action = NULL, + Request $request, + Response $response); +} diff --git a/http/not_found_interceptor.php b/http/not_found_interceptor.php new file mode 100644 index 0000000..a49fd41 --- /dev/null +++ b/http/not_found_interceptor.php @@ -0,0 +1,33 @@ +. + +namespace hoplite\http; + +require_once HOPLITE_ROOT . '/http/interceptor.php'; +require_once HOPLITE_ROOT . '/http/response_code.php'; + +class NotFoundInterceptor implements Interceptor +{ + public function DoIntercept(FrontController $controller, + Action $action, + Request $request, + Response $response) + { + if ($action === NULL) { + $controller->SendResponseCode(ResponseCode::NOT_FOUND); + } + } +} diff --git a/http/output_filter.php b/http/output_filter.php index 1e1925a..1ae998f 100644 --- a/http/output_filter.php +++ b/http/output_filter.php @@ -29,7 +29,7 @@ require_once HOPLITE_ROOT . '/views/template_loader.php'; */ class OutputFilter { - /*! @var RootController */ + /*! @var FrontController */ private $controller; /*! @var WeakInterface */ @@ -52,15 +52,15 @@ class OutputFilter const RENDER_TEMPLATE = 'template'; /*! - Constructor that takes a reference to the RootController. + Constructor that takes a reference to the FrontController. */ - public function __construct(RootController $controller) + public function __construct(FrontController $controller) { $this->controller = $controller; $this->delegate = new \hoplite\base\WeakInterface('hoplite\http\OutputFilterDelegate'); } - /*! Accessor for the RootController. */ + /*! Accessor for the FrontController. */ public function controller() { return $this->controller; } /*! Accessors for the delegate. */ @@ -71,7 +71,7 @@ class OutputFilter public function delegate() { return $this->delegate->Get(); } /*! @brief Main entry point for output filtering - This is called from the RootController to begin processing the output and + This is called from the FrontController to begin processing the output and generating the response. */ public function FilterOutput(Request $request, Response $response) diff --git a/http/rest_adapter.php b/http/rest_adapter.php index 0329177..aeec994 100644 --- a/http/rest_adapter.php +++ b/http/rest_adapter.php @@ -29,9 +29,10 @@ abstract class RestAdapter extends ActionController /*! @var RestAction The RESTful interface which will be adapted. */ protected $action = NULL; - public function FilterRequest(Request $request, Response $response) + public function Invoke(Request $request, Response $response) { $this->action = $this->_GetRestAction(); + parent::Invoke($request, $response); } /*! Gets the RestAction that will be adapted. */ diff --git a/http/url_map.php b/http/url_map.php index bc40a67..8bf450a 100644 --- a/http/url_map.php +++ b/http/url_map.php @@ -25,7 +25,7 @@ require_once HOPLITE_ROOT . '/base/functions.php'; */ class UrlMap { - /*! @var RootController */ + /*! @var FrontController */ private $controller; /*! @var array The map of URLs to actions. */ @@ -35,15 +35,15 @@ class UrlMap private $file_loader = NULL; /*! - Constructs the object with a reference to the RootController. - @param RootController + Constructs the object with a reference to the FrontController. + @param FrontController */ - public function __construct(RootController $controller) + public function __construct(FrontController $controller) { $this->controller = $controller; } - /*! Accessor for the RootController */ + /*! Accessor for the FrontController */ public function controller() { return $this->controller; } /*! Gets the URL map */ @@ -55,7 +55,7 @@ class UrlMap The keys can be either a URL prefix or a regular expression. For URL prefixes, the pattern is matched relative to the root of the entry- - point as specified by the RootController. Patterns should not begin with a + point as specified by the FrontController. Patterns should not begin with a slash. Path fragment parameter extraction can be performed as well. For example, the pattern 'user/view/{id}' will match a URL like http://example.com/webapp/user/view/42 diff --git a/testing/tests/http/action_controller_test.php b/testing/tests/http/action_controller_test.php index 5781b97..6e74c12 100644 --- a/testing/tests/http/action_controller_test.php +++ b/testing/tests/http/action_controller_test.php @@ -34,7 +34,7 @@ class ActionControllerTest extends \PHPUnit_Framework_TestCase public function setUp() { $globals = array(); - $this->fixture = new TestActionController(new http\RootController($globals)); + $this->fixture = new TestActionController(new http\FrontController($globals)); $this->request = new http\Request(); $this->response = new http\Response(); } @@ -49,11 +49,11 @@ class ActionControllerTest extends \PHPUnit_Framework_TestCase public function testFailedDispatch() { $globals = array(); - $mock = $this->getMock('hoplite\http\RootController', array(), array($globals)); + $mock = $this->getMock('hoplite\http\FrontController', array('SendResponse'), array($globals)); $this->fixture = new TestActionController($mock); $mock->expects($this->once()) - ->method('Stop'); + ->method('SendResponse'); $this->request->data['action'] = 'nothing'; $this->fixture->Invoke($this->request, $this->response); diff --git a/testing/tests/http/root_controller_test.php b/testing/tests/http/front_controller_test.php similarity index 65% rename from testing/tests/http/root_controller_test.php rename to testing/tests/http/front_controller_test.php index bd10a93..e3a86bd 100644 --- a/testing/tests/http/root_controller_test.php +++ b/testing/tests/http/front_controller_test.php @@ -1,11 +1,11 @@ did_filter_request = TRUE; + $this->did_invoke = TRUE; } +} - public function Invoke(http\Request $q, http\Response $s) +class ClosureInterceptor implements http\Interceptor +{ + private $interceptor; + public $did_intercept = FALSE; + + public function __construct($interceptor) { - $this->did_invoke = TRUE; + $this->interceptor = $interceptor; } - public function FilterResponse(http\Request $q, http\Response $s) + public function DoIntercept(http\FrontController $controller, + http\Action $action = NULL, + http\Request $request, + http\Response $response) { - $this->did_filter_response = TRUE; + $this->did_intercept = TRUE; + if ($this->interceptor) { + $interceptor = $this->interceptor; + $interceptor($controller, $action, $request, $response); + } } } -class RootControllerTest extends \PHPUnit_Framework_TestCase +class FrontControllerTest extends \PHPUnit_Framework_TestCase { /*! - Configures a mock RootControler. + Configures a mock FrontControler. @param array|NULL Array of methods to mock @param varargs Constructor parameters. - @return Mock RootControler + @return Mock FrontControler */ public function ConfigureMock() { $args = func_get_args(); - return $this->getMock('hoplite\http\RootController', $args[0], array_slice($args, 1)); + return $this->getMock('hoplite\http\FrontController', $args[0], array_slice($args, 1)); } - public function testRun() + public function testProcessRequest() { $globals = array('_SERVER' => array( 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/some/action/42' )); - $mock = $this->ConfigureMock(array('RouteRequest', 'Stop'), $globals); + $mock = $this->ConfigureMock(array('RouteRequest', 'SendResponse'), $globals); $mock->request()->url = 'some/action/42'; @@ -73,42 +85,20 @@ class RootControllerTest extends \PHPUnit_Framework_TestCase ->with($this->equalTo($mock->request())); $mock->expects($this->once()) - ->method('Stop'); - - $mock->Run(); - } - - public function testInvokeAction() - { - $globals = array(); - $fixture = new http\RootController($globals); - - $action = new ActionReporter($fixture); + ->method('SendResponse'); - $this->assertFalse($action->did_filter_request); - $this->assertFalse($action->did_invoke); - $this->assertFalse($action->did_filter_response); - - $fixture->InvokeAction($action); - - $this->assertTrue($action->did_filter_request); - $this->assertTrue($action->did_invoke); - $this->assertTrue($action->did_filter_response); + $mock->ProcessRequest(); } public function testRouteRequest() { $globals = array(); - $mock = $this->ConfigureMock(array('Stop', 'InvokeAction'), $globals); + $mock = $this->ConfigureMock(array('SendResponse', 'InvokeAction'), $globals); $mock->request()->url = 'some/action/42'; $map_value = 'ActionReporter'; $action = new ActionReporter($mock); - $mock->expects($this->once()) - ->method('InvokeAction') - ->with($this->isInstanceOf('hoplite\test\ActionReporter')); - $url_map = $this->getMock('hoplite\http\UrlMap', array(), array($mock)); $url_map->expects($this->once()) ->method('Evaluate') @@ -121,17 +111,19 @@ class RootControllerTest extends \PHPUnit_Framework_TestCase $mock->set_url_map($url_map); $mock->RouteRequest($mock->request()); + + $this->assertTrue($action->did_invoke); } public function testRouteRequestInvalid() { $globals = array(); - $mock = $this->ConfigureMock(array('Stop'), $globals); + $mock = $this->ConfigureMock(array('SendResponse'), $globals); $mock->request()->url = 'another/action'; - $mock->expects($this->once()) - ->method('Stop'); + $mock->expects($this->never()) + ->method('SendResponse'); $url_map = $this->getMock('hoplite\http\UrlMap', array(), array($mock)); $url_map->expects($this->once()) @@ -140,10 +132,10 @@ class RootControllerTest extends \PHPUnit_Framework_TestCase $mock->set_url_map($url_map); $mock->RouteRequest($mock->request()); - $this->assertEquals(http\ResponseCode::NOT_FOUND, $mock->response()->response_code); + // Nothing should happen for a non-routed request. } - public function testStop() + public function testSendResponse() { $globals = array(); $mock = $this->ConfigureMock(array('_Exit'), $globals); @@ -158,7 +150,7 @@ class RootControllerTest extends \PHPUnit_Framework_TestCase $this->isInstanceOf('hoplite\http\Response')); $mock->set_output_filter($output_filter); - $mock->Stop(); + $mock->SendResponse(); } public function testStopWithRedirect() @@ -176,7 +168,7 @@ class RootControllerTest extends \PHPUnit_Framework_TestCase $mock->set_output_filter($output_filter); - $mock->StopWithRedirect('/foo/bar'); + $mock->SendResponseRedirect('/foo/bar'); $this->assertEquals('/foo/bar', $mock->response()->headers['Location']); $this->assertEquals(http\ResponseCode::FOUND, $mock->response()->response_code); @@ -192,7 +184,7 @@ class RootControllerTest extends \PHPUnit_Framework_TestCase 'SERVER_PORT' => 80, ), ); - $mock = new \hoplite\http\RootController($globals); + $mock = new \hoplite\http\FrontController($globals); $this->assertEquals($mock->MakeURL('/'), '/hoplite/webapp/'); $this->assertEquals($mock->MakeURL('/', TRUE), 'http://www.bluestatic.org/hoplite/webapp/'); @@ -202,7 +194,7 @@ class RootControllerTest extends \PHPUnit_Framework_TestCase $globals['_SERVER']['HTTPS'] = 'on'; $globals['_SERVER']['SERVER_PORT'] = 443; - $mock = new \hoplite\http\RootController($globals); + $mock = new \hoplite\http\FrontController($globals); $this->assertEquals($mock->MakeURL('/'), '/hoplite/webapp/'); $this->assertEquals($mock->MakeURL('/', TRUE), 'https://www.bluestatic.org/hoplite/webapp/'); @@ -210,13 +202,13 @@ class RootControllerTest extends \PHPUnit_Framework_TestCase $this->assertEquals($mock->MakeURL('/path/3', TRUE), 'https://www.bluestatic.org/hoplite/webapp/path/3'); $globals['_SERVER']['SERVER_PORT'] = 8080; - $mock = new \hoplite\http\RootController($globals); + $mock = new \hoplite\http\FrontController($globals); $this->assertEquals($mock->MakeURL('/path/2'), '/hoplite/webapp/path/2'); $this->assertEquals($mock->MakeURL('/', TRUE), 'https://www.bluestatic.org:8080/hoplite/webapp/'); $this->assertEquals($mock->MakeURL('/path/3', TRUE), 'https://www.bluestatic.org:8080/hoplite/webapp/path/3'); } - public function testAbsolutifyRoot() + public function testAbsolutifyFront() { $globals = array( '_SERVER' => array( @@ -226,14 +218,14 @@ class RootControllerTest extends \PHPUnit_Framework_TestCase 'SERVER_PORT' => 80, ), ); - $mock = new \hoplite\http\RootController($globals); + $mock = new \hoplite\http\FrontController($globals); $this->assertEquals($mock->MakeURL('/'), '/hoplite/webapp/'); $this->assertEquals($mock->MakeURL('/', TRUE), 'http://www.bluestatic.org/hoplite/webapp/'); $globals['_SERVER']['HTTPS'] = 'on'; $globals['_SERVER']['SERVER_PORT'] = 443; - $mock = new \hoplite\http\RootController($globals); + $mock = new \hoplite\http\FrontController($globals); $this->assertEquals($mock->MakeURL('/'), '/hoplite/webapp/'); $this->assertEquals($mock->MakeURL('/', TRUE), 'https://www.bluestatic.org/hoplite/webapp/'); @@ -241,9 +233,60 @@ class RootControllerTest extends \PHPUnit_Framework_TestCase $this->assertEquals($mock->MakeURL('/path/3', TRUE), 'https://www.bluestatic.org/hoplite/webapp/path/3'); $globals['_SERVER']['SERVER_PORT'] = 8080; - $mock = new \hoplite\http\RootController($globals); + $mock = new \hoplite\http\FrontController($globals); $this->assertEquals($mock->MakeURL('/path/2'), '/hoplite/webapp/path/2'); $this->assertEquals($mock->MakeURL('/', TRUE), 'https://www.bluestatic.org:8080/hoplite/webapp/'); $this->assertEquals($mock->MakeURL('/path/3', TRUE), 'https://www.bluestatic.org:8080/hoplite/webapp/path/3'); } + + public function testThreeInterceptors() + { + $interceptors = [ + new ClosureInterceptor(NULL), + new ClosureInterceptor(NULL), + new ClosureInterceptor(NULL), + ]; + + $mock = new \hoplite\http\FrontController([]); + $mock->set_url_map(new http\UrlMap($mock)); + + foreach ($interceptors as $interceptor) { + $mock->AddInterceptor($interceptor); + } + + $mock->RouteRequest($mock->request()); + + foreach ($interceptors as $interceptor) { + $this->assertTrue($interceptor->did_intercept); + } + } + + public function testInterruptInterceptor() + { + $i1 = new ClosureInterceptor(NULL); + $i2 = new ClosureInterceptor(function($controller, $action, $request, $response) { + $controller->SendResponseCode(400); + }); + $test = $this; + $i3 = new ClosureInterceptor(function($controller, $action, $request, $response) use ($test) { + $test->assertEquals(400, $response->response_code); + }); + + $mock = $this->ConfigureMock(['SendResponse'], []); + $mock->set_url_map(new http\UrlMap($mock)); + $mock->AddInterceptor($i1); + $mock->AddInterceptor($i2); + $mock->AddInterceptor($i3); + + $mock->expects($this->once()) + ->method('SendResponse'); + + $mock->RouteRequest($mock->request()); + + $this->assertEquals(400, $mock->response()->response_code); + $this->assertTrue($i1->did_intercept); + $this->assertTrue($i2->did_intercept); + // i3 would not normally run but because _Exit is mocked, the script does + // not finish. + } } diff --git a/testing/tests/http/output_filter_test.php b/testing/tests/http/output_filter_test.php index b101e24..dba9e74 100644 --- a/testing/tests/http/output_filter_test.php +++ b/testing/tests/http/output_filter_test.php @@ -31,7 +31,7 @@ class OutputFilterTest extends \PHPUnit_Framework_TestCase { public function setUp() { - $this->fixture = new TestOutputFilter(new http\RootController(array())); + $this->fixture = new TestOutputFilter(new http\FrontController(array())); } public function testEncodeXML() @@ -50,7 +50,7 @@ class OutputFilterTest extends \PHPUnit_Framework_TestCase 1bar<strong>baz</strong>moobaa XML; - + $this->assertEquals($expected, $this->fixture->T_EncodeXML($array)); $obj = new \stdClass(); diff --git a/testing/tests/http/rest_action_test.php b/testing/tests/http/rest_action_test.php index fb6c36b..afae0db 100644 --- a/testing/tests/http/rest_action_test.php +++ b/testing/tests/http/rest_action_test.php @@ -1,11 +1,11 @@ fixture = new TestRestAction(new http\RootController($globals)); + $this->fixture = new TestRestAction(new http\FrontController($globals)); $this->request = new http\Request(); $this->response = new http\Response(); } @@ -72,7 +72,7 @@ class RestActionTest extends \PHPUnit_Framework_TestCase public function testInvalid() { $globals = array(); - $mock = $this->getMock('hoplite\http\RootController', array('Stop'), array($globals)); + $mock = $this->getMock('hoplite\http\FrontController', array('Stop'), array($globals)); $this->fixture = new TestRestAction($mock); diff --git a/testing/tests/http/rest_adapter_test.php b/testing/tests/http/rest_adapter_test.php index 629d52a..7a4d01b 100644 --- a/testing/tests/http/rest_adapter_test.php +++ b/testing/tests/http/rest_adapter_test.php @@ -39,7 +39,7 @@ class RestAdapterTest extends \PHPUnit_Framework_TestCase public function setUp() { $globals = array(); - $this->controller = new http\RootController($globals); + $this->controller = new http\FrontController($globals); $this->fixture = new TestRestAdapter($this->controller); $this->request = $this->controller->request(); $this->response = $this->controller->response(); @@ -59,7 +59,7 @@ class RestAdapterTest extends \PHPUnit_Framework_TestCase { $this->request->http_method = 'GET'; $this->request->data['action'] = 'fetch'; - $this->controller->InvokeAction($this->fixture); + $this->fixture->Invoke($this->request, $this->response); $this->RestExpectSingleTrue('did_get'); } @@ -67,7 +67,7 @@ class RestAdapterTest extends \PHPUnit_Framework_TestCase { $this->request->http_method = 'POST'; $this->request->data['action'] = 'fetch'; - $this->controller->InvokeAction($this->fixture); + $this->fixture->Invoke($this->request, $this->response); $this->RestExpectSingleTrue('did_get'); } @@ -75,7 +75,7 @@ class RestAdapterTest extends \PHPUnit_Framework_TestCase { $this->request->http_method = 'PUT'; $this->request->data['action'] = 'fetch'; - $this->controller->InvokeAction($this->fixture); + $this->fixture->Invoke($this->request, $this->response); $this->RestExpectSingleTrue(NULL); $this->assertEquals(http\ResponseCode::METHOD_NOT_ALLOWED, $this->response->response_code); } @@ -84,7 +84,7 @@ class RestAdapterTest extends \PHPUnit_Framework_TestCase { $this->request->http_method = 'POST'; $this->request->data['action'] = 'update'; - $this->controller->InvokeAction($this->fixture); + $this->fixture->Invoke($this->request, $this->response); $this->RestExpectSingleTrue('did_post'); } @@ -92,7 +92,7 @@ class RestAdapterTest extends \PHPUnit_Framework_TestCase { $this->request->http_method = 'GET'; $this->request->data['action'] = 'update'; - $this->controller->InvokeAction($this->fixture); + $this->fixture->Invoke($this->request, $this->response); $this->RestExpectSingleTrue(NULL); $this->assertEquals(http\ResponseCode::METHOD_NOT_ALLOWED, $this->response->response_code); } @@ -101,7 +101,7 @@ class RestAdapterTest extends \PHPUnit_Framework_TestCase { $this->request->http_method = 'POST'; $this->request->data['action'] = 'delete'; - $this->controller->InvokeAction($this->fixture); + $this->fixture->Invoke($this->request, $this->response); $this->RestExpectSingleTrue('did_delete'); } @@ -109,7 +109,7 @@ class RestAdapterTest extends \PHPUnit_Framework_TestCase { $this->request->http_method = 'GET'; $this->request->data['action'] = 'delete'; - $this->controller->InvokeAction($this->fixture); + $this->fixture->Invoke($this->request, $this->response); $this->RestExpectSingleTrue(NULL); $this->assertEquals(http\ResponseCode::METHOD_NOT_ALLOWED, $this->response->response_code); } @@ -118,7 +118,7 @@ class RestAdapterTest extends \PHPUnit_Framework_TestCase { $this->request->http_method = 'POST'; $this->request->data['action'] = 'insert'; - $this->controller->InvokeAction($this->fixture); + $this->fixture->Invoke($this->request, $this->response); $this->RestExpectSingleTrue('did_put'); } @@ -126,7 +126,7 @@ class RestAdapterTest extends \PHPUnit_Framework_TestCase { $this->request->http_method = 'GET'; $this->request->data['action'] = 'insert'; - $this->controller->InvokeAction($this->fixture); + $this->fixture->Invoke($this->request, $this->response); $this->RestExpectSingleTrue(NULL); $this->assertEquals(http\ResponseCode::METHOD_NOT_ALLOWED, $this->response->response_code); } @@ -134,17 +134,17 @@ class RestAdapterTest extends \PHPUnit_Framework_TestCase public function testInvalid() { $globals = array(); - $mock = $this->getMock('hoplite\http\RootController', array('Stop'), array($globals)); + $mock = $this->getMock('hoplite\http\FrontController', array('SendResponse'), array($globals)); $this->fixture = new TestRestAdapter($mock); $mock->expects($this->once()) - ->method('Stop'); + ->method('SendResponse'); $this->request->http_method = 'HEAD'; - $this->controller->InvokeAction($this->fixture); + $this->fixture->Invoke($mock->request(), $mock->response()); $this->RestExpectSingleTrue('___none___'); - $this->assertEquals(http\ResponseCode::NOT_FOUND, $this->response->response_code); + $this->assertEquals(http\ResponseCode::NOT_FOUND, $mock->response()->response_code); } } diff --git a/testing/tests/http/url_map_test.php b/testing/tests/http/url_map_test.php index 72fbaf2..01fd813 100644 --- a/testing/tests/http/url_map_test.php +++ b/testing/tests/http/url_map_test.php @@ -29,7 +29,7 @@ class UrlMapTest extends \PHPUnit_Framework_TestCase public function setUp() { $globals = array(); - $this->fixture = new http\UrlMap(new http\RootController($globals)); + $this->fixture = new http\UrlMap(new http\FrontController($globals)); } public function testSimpleEvaluate() -- 2.22.5 From ba8a84f9e7b94fe006209a901293d900851ca6a9 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sun, 31 May 2015 17:20:28 -0400 Subject: [PATCH 10/16] Fix views\Template accessing RootController rather than FrontController. --- views/template.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/views/template.php b/views/template.php index da6857d..3b4b9c9 100644 --- a/views/template.php +++ b/views/template.php @@ -328,14 +328,14 @@ class TemplateBuiltins echo TemplateLoader::Fetch($template)->Render($vars); } - /*! @brief Creates a URL via RootController::MakeURL(). + /*! @brief Creates a URL via FrontController::MakeURL(). This requires the root controller be set in the $GLOBALS as - hoplite\http\RootController. + hoplite\http\FrontController. @param string Path. */ static public function MakeURL($path) { - echo $GLOBALS['hoplite\http\RootController']->MakeURL($path, FALSE); + echo $GLOBALS['hoplite\http\FrontController']->MakeURL($path, FALSE); } } -- 2.22.5 From dde46188cf710ef8aa053f580238dbbe20a8f42e Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sun, 31 May 2015 17:20:55 -0400 Subject: [PATCH 11/16] Restore FrontController::InvokeAction(). --- http/front_controller.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/http/front_controller.php b/http/front_controller.php index b84f298..d31f1ff 100644 --- a/http/front_controller.php +++ b/http/front_controller.php @@ -173,7 +173,15 @@ class FrontController $interceptor->DoIntercept($this, $action, $request, $this->response); if ($action) - $action->Invoke($request, $this->response); + $this->InvokeAction($action); + } + + /*! + Invokes the action with the Controller's request and response objects. + */ + public function InvokeAction(Action $action) + { + $action->Invoke($this->request, $this->response); } /*! -- 2.22.5 From db920ccbd814f3068a7447f2f28673d437007bd4 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sun, 31 May 2015 17:26:17 -0400 Subject: [PATCH 12/16] NotFoundInterceptor needs to permit a NULL Action. --- http/not_found_interceptor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http/not_found_interceptor.php b/http/not_found_interceptor.php index a49fd41..31f0fa4 100644 --- a/http/not_found_interceptor.php +++ b/http/not_found_interceptor.php @@ -22,7 +22,7 @@ require_once HOPLITE_ROOT . '/http/response_code.php'; class NotFoundInterceptor implements Interceptor { public function DoIntercept(FrontController $controller, - Action $action, + Action $action = NULL, Request $request, Response $response) { -- 2.22.5 From e970ef8ce98c72a93ca8eebae88dcce5387b6c16 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sat, 18 Nov 2017 16:45:49 -0500 Subject: [PATCH 13/16] The FileCacheBackend should cache with template basename. --- views/file_cache_backend.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/file_cache_backend.php b/views/file_cache_backend.php index b4c9952..479ea2a 100644 --- a/views/file_cache_backend.php +++ b/views/file_cache_backend.php @@ -70,6 +70,6 @@ class FileCacheBackend implements CacheBackend /*! Returns the cache path for a given template name. */ protected function _CachePath($name) { - return $this->cache_path . $name . '.phpi'; + return $this->cache_path . basename($name) . '.phpi'; } } -- 2.22.5 From 5ba19f743d5b8763421f83b45fdfb4ce2fe8b733 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sat, 18 Nov 2017 16:46:07 -0500 Subject: [PATCH 14/16] Remove trailing vars, $vars); $render = function () use ($__template_data, $__template_vars) { extract($__template_vars); - eval('?>' . $__template_data . '<' . '?'); + eval('?>' . $__template_data); }; ob_start(); -- 2.22.5 From ff05b82d3a08a586febb9b8ac090fafb34f67250 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sat, 18 Nov 2017 21:51:56 -0500 Subject: [PATCH 15/16] RestAction: Support OPTIONS and fix old StopWithCode. --- http/rest_action.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/http/rest_action.php b/http/rest_action.php index b3d2c5d..ca6e870 100644 --- a/http/rest_action.php +++ b/http/rest_action.php @@ -30,11 +30,10 @@ class RestAction extends Action */ public function Invoke(Request $request, Response $response) { - $valid_methods = array('get', 'post', 'delete', 'put'); + $valid_methods = array('options', 'get', 'post', 'delete', 'put'); $method = strtolower($request->http_method); if (!in_array($method, $valid_methods)) { - $response->response_code = ResponseCode::METHOD_NOT_ALLOWED; - $this->controller()->Stop(); + $this->controller()->SendResponseCode(ResponseCode::METHOD_NOT_ALLOWED); return; } @@ -43,23 +42,27 @@ class RestAction extends Action } /*! Methods for each of the different HTTP methods. */ + + + public function DoOptions(Request $request, Response $response) {} + public function DoGet(Request $request, Response $response) { - $this->controller()->StopWithCode(ResponseCode::METHOD_NOT_ALLOWED); + $this->controller()->SendReseponseCode(ResponseCode::METHOD_NOT_ALLOWED); } public function DoPost(Request $request, Response $response) { - $this->controller()->StopWithCode(ResponseCode::METHOD_NOT_ALLOWED); + $this->controller()->SendReseponseCode(ResponseCode::METHOD_NOT_ALLOWED); } public function DoDelete(Request $request, Response $response) { - $this->controller()->StopWithCode(ResponseCode::METHOD_NOT_ALLOWED); + $this->controller()->SendReseponseCode(ResponseCode::METHOD_NOT_ALLOWED); } public function DoPut(Request $request, Response $response) { - $this->controller()->StopWithCode(ResponseCode::METHOD_NOT_ALLOWED); + $this->controller()->SendReseponseCode(ResponseCode::METHOD_NOT_ALLOWED); } } -- 2.22.5 From 93e85e14f3b30d033324277751a9ba4c655b4399 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sun, 19 Nov 2017 00:00:08 -0500 Subject: [PATCH 16/16] Add an interceptor for CORS OPTIONS preflighting. --- http/cors_options_interceptor.php | 51 +++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 http/cors_options_interceptor.php diff --git a/http/cors_options_interceptor.php b/http/cors_options_interceptor.php new file mode 100644 index 0000000..6b3d899 --- /dev/null +++ b/http/cors_options_interceptor.php @@ -0,0 +1,51 @@ +. + +namespace hoplite\http; + +require_once HOPLITE_ROOT . '/http/interceptor.php'; +require_once HOPLITE_ROOT . '/http/response_code.php'; + +class CorsOptionsInterceptor implements Interceptor +{ + private $allowed_origins = []; + + public function __construct($allowed_origins = []) { + $this->allowed_origins = $allowed_origins; + } + + public function DoIntercept(FrontController $controller, + Action $action = NULL, + Request $request, + Response $response) + { + if ($action === NULL) { + return; + } + + // If a CORS pre-flight is in process, interrupt the action flow and + // permit the request. + if ($request->http_method == 'OPTIONS' && + isset($request->data['_SERVER']['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) { + if (in_array($request->data['_SERVER']['HTTP_ORIGIN'], $this->allowed_origins)) { + $controller->SendResponseCode(ResponseCode::OK); + } else { + $controller->SendResponseCode(ResponseCode::FORBIDDEN); + return; + } + } + } +} -- 2.22.5