Skip to content

Commit

Permalink
In the middle of significant refactoring - branching.
Browse files Browse the repository at this point in the history
  • Loading branch information
KrisJordan committed Nov 10, 2008
1 parent 691f68d commit f7bf1dd
Show file tree
Hide file tree
Showing 19 changed files with 654 additions and 9 deletions.
2 changes: 1 addition & 1 deletion lib/recess/framework/Coordinator.class.php
Expand Up @@ -18,7 +18,7 @@ final class Coordinator {
*/
public static function main(Request $request) {

$convention = Application::getConvention($request);
$convention = Application::getPolicy($request);

$preprocessor = $convention->getPreprocessor();

Expand Down
Expand Up @@ -21,7 +21,7 @@ public function getPreprocessor() {
return new DefaultPreprocessor();
}

public function getControllerFor(Request $request) {
public function getControllerFor(DefaultRequest $request) {
// TODO: Add support for plugins!
// TODO: Make Request => StandardRequest and add fields for controller, function, function_args
if(isset($request->meta['controller'])) {
Expand Down
@@ -1,6 +1,6 @@
<?php

interface IConvention {
interface IPolicy {
public function getPreprocessor();

public function getControllerFor(Request $request);
Expand Down
2 changes: 1 addition & 1 deletion lib/recess/framework/interfaces/IPreprocessor.class.php
Expand Up @@ -20,6 +20,6 @@ interface IPreprocessor {
* @param Request The Request to preprocess.
* @return Request The preprocessed Request.
*/
public function process(Request $request);
public function process(Request &$request);
}
?>
31 changes: 31 additions & 0 deletions lib/recess/framework/policies/DefaultPolicy.class.php
@@ -0,0 +1,31 @@
<?php

Library::import('recess.framework.policies.default.DefaultPreprocessor');
Library::import('recess.framework.policies.default.DefaultRequest');
Library::import('recess.framework.policies.default.DefaultResponse');
Library::import('recess.framework.policies.default.Controller');
Library::import('recess.framework.policies.default.View');
Library::import('recess.framework.interfaces.IPolicy');

class DefaultPolicy implements IPolicy {

public function getPreprocessor() {
return new DefaultPreprocessor();
}

public function getControllerFor(Request $request) {
Application::loadController($request->meta->controllerClass);
return new $request->meta->controllerClass;
}

public function getViewFor(Response $response) {
if(Library::classExists('recess.framework.policies.default.' . $response->meta->viewClass)) {
return new $response->meta->viewClass;
} else {
Application::loadView($response->meta->viewClass);
return new $response->meta->viewClass;
}
}
}

?>
152 changes: 152 additions & 0 deletions lib/recess/framework/policies/default/Controller.class.php
@@ -0,0 +1,152 @@
<?php
Library::import('recess.lang.RecessClass');
Library::import('recess.lang.RecessReflectionClass');
Library::import('recess.framework.policies.default.annotations.ViewAnnotation', true);
Library::import('recess.framework.policies.default.annotations.RouteAnnotation', true);
class ControllerDescriptor extends RecessClassDescriptor {
public $routes;
public $viewClass = 'NativeView';
public $viewPrefix = '';
}

/**
* The controller is responsible for interpretting a preprocessed Request,
* performing some action in response to the Request (usually CRUDS), and
* returning a Response which contains relevant state for a view to render
* the Response.
*
* @author Kris Jordan
*/
abstract class Controller extends RecessClass {
protected $request;
protected $data;

public static function getViewClass($class) {
return self::getClassDescriptor($class)->viewClass;
}

public static function getViewPrefix($class) {
return self::getClassDescriptor($class)->viewPrefix;
}

public static function getRoutes($class) {
return self::getClassDescriptor($class)->routes;
}

protected static function buildClassDescriptor($class) {
$descriptor = new ControllerDescriptor($class);

try {
$reflection = new RecessReflectionClass($class);
} catch(ReflectionException $e) {
throw new RecessException('Class "' . $class . '" has not been declared.', get_defined_vars());
}

$annotations = $reflection->getAnnotations();
foreach($annotations as $annotation) {
if($annotation instanceof ControllerAnnotation) {
$annotation->massage($class, '', $descriptor);
}
}

$reflectedMethods = $reflection->getMethods(false);
$methods = array();
foreach($reflectedMethods as $reflectedMethod) {
$annotations = $reflectedMethod->getAnnotations();
foreach($annotations as $annotation) {
if($annotation instanceof ControllerAnnotation) {
$annotation->massage($class, $reflectedMethod->name, $descriptor);
}
}
}

return $descriptor;
}

/**
* The serve method is where inversion of control occurs which delegates
* control to another method in the controller.
*
* The method name and arguments should have been extracted in the
* preprocessing step. Here we ensure that the method exists and that all
* required parameters are provided as arguments from the request string.
*
* Call the method and return its response.
*
* @param DefaultRequest $request The HTTP request being served.
* @final
*/
final function serve(Request $request) {
$this->request = $request;

$methodName = $request->meta->controllerMethod;
$methodArguments = $request->meta->controllerMethodArguments;
$useAssociativeArguments = $request->meta->useAssociativeArguments;

// Does method exist? Do arguments match?
if (method_exists($this, $methodName)) {
$method = new ReflectionMethod($this, $methodName);
$parameters = $method->getParameters();

$callArguments = array();
try {
if($useAssociativeArguments) {
$callArguments = $this->getCallArgumentsAssociative($parameters, $methodArguments);
} else {
$callArguments = $this->getCallArgumentsSequential($parameters, $methodArguments);
}
} catch(RecessException $e) {
throw new RecessException('Error calling method "' . $methodName . '" in "' . get_class($this) . '". ' . $e->getMessage(), array());
}

$response = $method->invokeArgs($this, $callArguments);
} else {
throw new RecessException('Error calling method "' . $methodName . '" in "' . get_class($this) . '". Method does not exist.', array());
}

if($response instanceof Response) {
$descriptor = self::getClassDescriptor($this);
$response->meta->viewClass = $descriptor->viewClass;
$response->meta->viewPrefix = $descriptor->viewPrefix;
$response->meta->viewName = $methodName;
return $response;
} else {
return new BadRequestResponse($request);
}
}

private function getCallArgumentsAssociative($parameters, $arguments) {
$callArgs = array();
foreach($parameters as $parameter) {
if(!isset($arguments[$parameter->getName()])) {
if(!$parameter->isOptional()) {
throw new RecessException('Expects ' . count($parameters) . ' arguments, given ' . count($arguments) . ' and missing required parameter: "' . $parameter->name . '"', array());
}
} else {
$callArgs[] = $arguments[$parameter->getName()];
}
}
return $callArgs;
}

private function getCallArgumentsSequential($parameters, $arguments) {
$callArgs = array();
$parameterCount = count($parameters);
for($i = 0; $i < $parameterCount; $i++) {
if(!isset($arguments[$i])) {
if(!$parameters[$i]->isOptional()) {
throw new RecessException('Expects ' . count($parameters) . ' arguments, given ' . count($arguments) . ' and missing required parameter # ' . ($i + 1) . ' named: "' . $parameters[$i]->name . '"', array());
}
} else {
$callArgs[] = $arguments[$i];
}
}
return $callArgs;
}

protected function ok() {
return new OkResponse($this->request, $this->data);
}

}
?>
126 changes: 126 additions & 0 deletions lib/recess/framework/policies/default/DefaultPreprocessor.class.php
@@ -0,0 +1,126 @@
<?php

Library::import('recess.framework.interfaces.IPreprocessor');

class DefaultPreprocessor implements IPreprocessor {

/**
* Used to pre-process a request.
* This may involve extracting information and transforming values.
* For example, Transforming the HTTP method from POST to PUT based on a POSTed field.
*
* @param Request The Request to refine.
* @return Request The refined Request.
*/
public function process(Request &$request) {

$this->getHttpMethodFromPost($request);

$this->getFormatFromResourceString($request);

if($request->format != Formats::xhtml) {
$this->reparameterizeForFormat($request);
}

$this->selectControllerAndView($request);

return $request;
}

/////////////////////////////////////////////////////////////////////////
// Helper Methods

const HTTP_METHOD_FIELD = '_METHOD';

protected function getHttpMethodFromPost(Request &$request) {
if(array_key_exists(self::HTTP_METHOD_FIELD, $request->post)) {
$request->method = $request->post[self::HTTP_METHOD_FIELD];
unset($request->post[self::HTTP_METHOD_FIELD]);
if($request->method == Methods::PUT) {
$request->put = $request->post;
}
}
return $request;
}

protected function getFormatFromResourceString(Request &$request) {
$lastPartIndex = count($request->resourceParts) - 1;
if($lastPartIndex < 0) return $request;

$lastPart = $request->resourceParts[$lastPartIndex];

$lastDotPosition = strrpos($lastPart, Library::dotSeparator);
if($lastDotPosition !== false) {
$substring = substr($lastPart, $lastDotPosition + 1);
if(in_array($substring, Formats::$all)) {
$request->format = $substring;
$request->setResource(substr($request->resource, 0, strrpos($request->resource, Library::dotSeparator)));
} else {
$request->format = Formats::xhtml;
}
}
return $request;
}

protected function reparameterizeForFormat(Request &$request) {
// TODO: Think about how parameter passing via json/xml/post-vars can be streamlined
if($request->format == Formats::json) {
if(array_key_exists('json', $request->post)){
$request->post = json_decode($request->post['json']);
}
} else if ($request->format == Formats::xml) {
// TODO: XML reparameterization in request transformer
}
return $request;
}

protected function selectControllerAndView(Request &$request) {
$router = Application::getRouter(); // TODO: change this

$routerResult = $router->findRouteFor($request);
if($routerResult->routeExists) {
$request = $this->selectControllerAndViewUsingRoutingResult($request, $routerResult);
} else {
$request = $this->selectControllerAndViewUsingDefaults($request);
}

return $request;
}

protected function selectControllerAndViewUsingRoutingResult(Request &$request, RoutingResult &$routerResult) {
$request->meta->useAssociativeArguments = true;
if($routerResult->methodIsSupported) {
$request->meta->controllerClass = $routerResult->route->controllerClass;
$request->meta->controllerMethod = $routerResult->route->function;
$request->meta->controllerMethodArguments = $routerResult->arguments;
} else {
// TODO: Exception Case - Need a means of shortcutting a "METHOD NOT SUPPORTED"
throw new RecessException($request->method . ' HTTP method is not supported for url: ' . $request->resource);
}
return $request;
}

protected function selectControllerAndViewUsingDefaults(Request &$request) {
$request->meta->useAssociativeArguments = false;
if(isset($request->resourceParts[0])) {
$request->meta->controllerClass = $request->resourceParts[0] . 'Controller';
if(isset($request->resourceParts[1])) {
$request->meta->controllerMethod = $request->resourceParts[1];
} else {
$request->meta->controllerMethod = 'home';
}
if(count($request->resourceParts) > 2) {
$request->meta->controllerMethodArguments = array_slice($request->resourceParts, 2);
} else {
$request->meta->controllerMethodArguments = array();
}
} else {
$request->meta->controllerClass = 'HomeController';
$request->meta->controllerMethod = 'home';
$request->meta->controllerMethodArguments = array();
}
return $request;
}
}

?>
11 changes: 11 additions & 0 deletions lib/recess/framework/policies/default/DefaultRequest.class.php
@@ -0,0 +1,11 @@
<?php
Library::import('recess.http.Request');

class DefaultRequest extends Request {
public $controllerClass;
public $controllerMethod;
public $controllerMethodArguments;
public $viewClass;
public $viewMethod;
}
?>
20 changes: 20 additions & 0 deletions lib/recess/framework/policies/default/NativeView.class.php
@@ -0,0 +1,20 @@
<?php
Library::import('recess.framework.views.AbstractView');

class NativeView extends AbstractView {
/**
* Realizes HTTP's body content based on the Response parameter. Responsible
* for returning content in the format desired. The render method likely uses
* inversion of control which delegates to another method within the view to
* realize the Response.
*
* @param Response $response
* @abstract
*/
protected function render(Response $response) {
$data = $response->data;
// TODO: Set more interesting variables here.
include_once(Application::getSetting('View.dir') . $response->meta->viewPrefix . $response->meta->viewName . '.php');
}
}
?>
@@ -0,0 +1,9 @@
<?php
Library::import('recess.lang.Annotation');

abstract class ControllerAnnotation extends Annotation {

abstract function massage($controller, $method, ControllerDescriptor $descriptor);
}

?>

0 comments on commit f7bf1dd

Please sign in to comment.