Skip to content

Commit

Permalink
Major new content-negotiation features, view selection logic, and !Pr…
Browse files Browse the repository at this point in the history
…efix

replacing !View and !RoutesPrefix. Should not break backwards compatibility
except for if data is being posted as JSON. Inbound content-type handling
is on its way.

Changed name for Annotation helper method validOnInstancesOf to validOnSubclassesOf

Major refactoring to view selection pipeline.

Adding RecessView back for backwards compatibility with old edge. This will disappear in 0.3

New content-negotiation system should now be backwards compatible with pre-0.2
  • Loading branch information
KrisJordan committed Jun 12, 2009
1 parent a4da91f commit d3f8f0c
Show file tree
Hide file tree
Showing 32 changed files with 302 additions and 144 deletions.
17 changes: 11 additions & 6 deletions apps/welcome/controllers/WelcomeHomeController.class.php
Expand Up @@ -2,17 +2,22 @@
Library::import('recess.framework.controllers.Controller');

/**
* !View Native, Prefix: home/
* !RespondWith Layouts, Json
* !Prefix Routes: /, Views: home/
*/
class WelcomeHomeController extends Controller {

/** !Route GET */
/**
* !Route GET, /
*/
function index() {

$this->flash = 'Welcome to your new Recess app!';

}


/**
* !Route GET, foo
*/
function foo() {

}
}
?>
8 changes: 3 additions & 5 deletions recess/recess/Recess.class.php
Expand Up @@ -24,16 +24,14 @@ public static function main(Request $request, IPolicy $policy, array $apps, RtNo
print_r($calls);
die('Forwarding loop in main?');
}

$pluggedPolicy = $policy;

$request = $pluggedPolicy->preprocess($request);
$request = $policy->preprocess($request);

$controller = $pluggedPolicy->getControllerFor($request, $apps, $routes);
$controller = $policy->getControllerFor($request, $apps, $routes);

$response = $controller->serve($request);

$view = $pluggedPolicy->getViewFor($response);
$view = $policy->getViewFor($response);

ob_start();

Expand Down
Expand Up @@ -3,8 +3,8 @@
Library::import('recess.database.pdo.RecessType');

/**
* !View Recess, Prefix: apps/
* !RoutesPrefix apps/
* !RespondWith Layouts, Json
* !Prefix apps/
*/
class RecessToolsAppsController extends Controller {

Expand Down
Expand Up @@ -10,8 +10,8 @@
Library::import('recess.apps.tools.models.RecessReflectorMethod');

/**
* !View Recess, Prefix: code/
* !RoutesPrefix code/
* !RespondWith Layouts, Json
* !Prefix code/
*/
class RecessToolsCodeController extends Controller {

Expand Down
Expand Up @@ -4,8 +4,8 @@
Library::import('recess.database.pdo.PdoDataSource');

/**
* !View Recess, Prefix: database/
* !RoutesPrefix database/
* !RespondWith Layouts, Json
* !Prefix database/
*/
class RecessToolsDatabaseController extends Controller {

Expand Down
Expand Up @@ -2,7 +2,8 @@
Library::import('recess.framework.controllers.Controller');

/**
* !View Recess, Prefix: home/
* !RespondWith Layouts, Json
* !Prefix Views: home/, Routes: /
*/
class RecessToolsHomeController extends Controller {

Expand Down
Expand Up @@ -2,8 +2,8 @@
Library::import('recess.framework.controllers.Controller');

/**
* !View Recess, Prefix: routes/
* !RoutesPrefix routes/
* !RespondWith Layouts, Json
* !Prefix routes/
*/
class RecessToolsRoutesController extends Controller {

Expand Down
Expand Up @@ -2,11 +2,10 @@
Library::import('recess.framework.controllers.Controller');

/**
* !View Recess, Prefix: tests/
* !RoutesPrefix tests/
* !RespondWith Layouts, Json
* !Prefix tests/
*/
class RecessToolsTestsController extends Controller {

public function init() {
if(RecessConf::$mode == RecessConf::PRODUCTION) {
throw new RecessResponseException('Tools are available only during development.', ResponseCodes::HTTP_NOT_FOUND, array());
Expand All @@ -17,7 +16,5 @@ public function init() {
public function home() {

}

}

?>
Expand Up @@ -2,7 +2,7 @@
Library::import('recess.framework.controllers.Controller');

/**
* !View Prefix: home/
* !Prefix Views: home/, Routes: /
*/
class {{programmaticName}}HomeController extends Controller {

Expand Down
Expand Up @@ -3,8 +3,8 @@
Library::import('recess.framework.forms.ModelForm');

/**
* !View Prefix: {{viewsPrefix}}/
* !RoutesPrefix {{routesPrefix}}/
* !RespondWith Layouts
* !Prefix {{routesPrefix}}/
*/
class {{modelName}}Controller extends Controller {

Expand Down
Expand Up @@ -24,7 +24,7 @@ public function isFor() {
protected function validate($class) {
$this->acceptsNoKeyedValues();
$this->exactParameterCount(1);
$this->validOnInstancesOf($class, Model::CLASSNAME);
$this->validOnSubclassesOf($class, Model::CLASSNAME);
}

protected function expand($class, $reflection, $descriptor) {
Expand Down
Expand Up @@ -24,7 +24,7 @@ public function isFor() {
protected function validate($class) {
$this->acceptsNoKeyedValues();
$this->exactParameterCount(1);
$this->validOnInstancesOf($class, Model::CLASSNAME);
$this->validOnSubclassesOf($class, Model::CLASSNAME);
}

protected function expand($class, $reflection, $descriptor) {
Expand Down
15 changes: 8 additions & 7 deletions recess/recess/framework/AbstractController.class.php
@@ -1,12 +1,13 @@
<?php
Library::import('recess.lang.Object');
Library::import('recess.lang.reflection.RecessReflectionClass');
Library::import('recess.lang.Annotation', true);
Library::import('recess.lang.Annotation');
Library::import('recess.framework.interfaces.IController');
Library::import('recess.framework.controllers.annotations.ViewAnnotation', true);
Library::import('recess.framework.controllers.annotations.RouteAnnotation', true);
Library::import('recess.framework.controllers.annotations.RoutesPrefixAnnotation', true);

Library::import('recess.framework.controllers.annotations.ViewAnnotation');
Library::import('recess.framework.controllers.annotations.RouteAnnotation');
Library::import('recess.framework.controllers.annotations.RoutesPrefixAnnotation');
Library::import('recess.framework.controllers.annotations.PrefixAnnotation');
Library::import('recess.framework.controllers.annotations.RespondWithAnnotation');
/**
* The controller is responsible for interpretting a preprocessed Request,
* performing some action in response to the Request (usually CRUDS), and
Expand All @@ -23,8 +24,8 @@ public static function getViewClass($class) {
return self::getClassDescriptor($class)->viewClass;
}

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

public static function getRoutes($class) {
Expand Down
11 changes: 4 additions & 7 deletions recess/recess/framework/AbstractView.class.php
Expand Up @@ -14,6 +14,8 @@
abstract class AbstractView extends Object {
protected $response;

public abstract function canRespondWith(Response $response);

/**
* The entry point from the Recess with a Response to be rendered.
* Delegates the two steps in rendering a view: 1) Send Headers, 2) Render Body
Expand All @@ -26,12 +28,7 @@ public final function respondWith(Response $response) {

if(ResponseCodes::canHaveBody($response->code) && !$response instanceof ForwardingResponse) {
$this->response = $response;

while(($format = $response->request->accepts->nextFormat()) !== false) {
if($this->render($format, $response)) {
return true;
}
}
$this->render($response);
}
}

Expand Down Expand Up @@ -105,7 +102,7 @@ protected final function sendHeadersFor(Response $response) {
* @param Response $response
* @abstract
*/
protected abstract function render($format, Response $response);
protected abstract function render(Response $response);

}
?>
4 changes: 2 additions & 2 deletions recess/recess/framework/Application.class.php
@@ -1,6 +1,6 @@
<?php
Library::import('recess.framework.controllers.Controller');
Library::import('recess.framework.views.RecessView');
Library::import('recess.framework.views.LayoutsView');
Library::import('recess.database.orm.Model');

abstract class Application {
Expand Down Expand Up @@ -38,7 +38,7 @@ abstract class Application {
/**
* OVERRIDE THIS with the routing prefix to your application
*
* @var unknown_type
* @var string
*/
public $routingPrefix = '/';

Expand Down
40 changes: 35 additions & 5 deletions recess/recess/framework/DefaultPolicy.class.php
@@ -1,10 +1,14 @@
<?php
Library::import('recess.framework.controllers.Controller');
Library::import('recess.framework.views.RecessView');
Library::import('recess.framework.views.LayoutsView');
Library::import('recess.framework.views.NativeView');
Library::import('recess.framework.views.JsonView');
Library::import('recess.framework.interfaces.IPolicy');
Library::import('recess.framework.http.MimeTypes');

// TODO: Remove this import in 0.3
Library::import('recess.framework.views.RecessView');

class DefaultPolicy implements IPolicy {
protected $controller;

Expand All @@ -25,7 +29,6 @@ public function preprocess(Request &$request) {
}

public function getControllerFor(Request &$request, array $applications, RtNode $routes) {

$routeResult = $routes->findRouteFor($request);

if($routeResult->routeExists) {
Expand All @@ -44,9 +47,36 @@ public function getControllerFor(Request &$request, array $applications, RtNode
}

public function getViewFor(Response &$response) {
$view = new $response->meta->viewClass;
$response->meta->viewDir = $response->meta->app->getViewsDir() . $response->meta->viewPrefix;
return $view;
// TODO: When version 0.3 is released, remove this conditional
// and break backwards compatibility with versions <= 0.12
if(!isset($response->meta->respondWith) || empty($response->meta->respondWith)) {
$view = new $response->meta->viewClass;
$response->meta->respondWith = array($view);
if($view != 'LayoutsView') {
$response->meta->respondWith[] = $view;
}
$response->meta->respondWith[] = 'JsonView';
}

// Here we select a view that can respond in the desired format
$viewClasses = $response->meta->respondWith;
$views = array();
foreach($viewClasses as $viewClass) {
$views[] = new $viewClass();
}

$accepts = $response->request->accepts;
$accepts->resetFormats();
do {
$format = $accepts->nextFormat();
foreach($views as $view) {
if($view->canRespondWith($response)) {
return $view;
}
}
} while ($format !== false);

throw new RecessResponseException('Unable to provide with desired content-type.', ResponseCodes::HTTP_NOT_ACCEPTABLE, array());
}

/////////////////////////////////////////////////////////////////////////
Expand Down
16 changes: 11 additions & 5 deletions recess/recess/framework/controllers/Controller.class.php
Expand Up @@ -5,6 +5,8 @@
Library::import('recess.framework.controllers.annotations.ViewAnnotation');
Library::import('recess.framework.controllers.annotations.RouteAnnotation');
Library::import('recess.framework.controllers.annotations.RoutesPrefixAnnotation');
Library::import('recess.framework.controllers.annotations.PrefixAnnotation');
Library::import('recess.framework.controllers.annotations.RespondWithAnnotation');

/**
* The controller is responsible for interpretting a preprocessed Request,
Expand Down Expand Up @@ -38,8 +40,9 @@ protected static function initClassDescriptor($class) {
$descriptor->routes = array();
$descriptor->methodUrls = array();
$descriptor->routesPrefix = '';
$descriptor->viewClass = 'recess.framework.views.NativeView';
$descriptor->viewPrefix = '';
$descriptor->viewClass = 'recess.framework.views.LayoutsView';
$descriptor->viewsPrefix = '';
$descriptor->respondWith = array();
return $descriptor;
}

Expand Down Expand Up @@ -147,8 +150,8 @@ function wrappedServe(Request $request) {

$shortWiredResponse = $this->init();
if($shortWiredResponse instanceof Response) {
$shortWiredResponse->meta->viewClass = 'RecessView';
$shortWiredResponse->meta->viewPrefix = '';
$shortWiredResponse->meta->viewClass = 'LayoutsView';
$shortWiredResponse->meta->viewsPrefix = '';
return $shortWiredResponse;
}

Expand Down Expand Up @@ -184,8 +187,11 @@ function wrappedServe(Request $request) {

$descriptor = self::getClassDescriptor($this);
if(!isset($response->meta->viewName)) $response->meta->viewName = $methodName;
// TODO: Remove this deprecated viewClass at 0.3
$response->meta->viewClass = $descriptor->viewClass;
$response->meta->viewPrefix = $descriptor->viewPrefix;
$response->meta->viewsPrefix = $descriptor->viewsPrefix;

$response->meta->respondWith = $descriptor->respondWith;
if(empty($response->data)) $response->data = get_object_vars($this);
$response->data['controller'] = $this;
if(is_array($this->headers)) { foreach($this->headers as $header) $response->addHeader($header); }
Expand Down
@@ -0,0 +1,37 @@
<?php
Library::import('recess.lang.Annotation');

class PrefixAnnotation extends Annotation {

public function usage() {
return '!Prefix prefix/of/route/ [, Views: prefix/, Routes: prefix/]';
}

public function isFor() {
return Annotation::FOR_CLASS;
}

protected function validate($class) {
$this->acceptedKeys(array('views', 'routes'));
$this->minimumParameterCount(1);
$this->maximumParameterCount(3);
$this->validOnSubclassesOf($class, Controller::CLASSNAME);
}

protected function expand($class, $reflection, $descriptor) {
if(isset($this->values[0])) {
$viewsPrefix = $routesPrefix = $this->values[0];
} else {
$viewsPrefix = $routesPrefix = '';
}

if(isset($this->views)) { $viewsPrefix = $this->views; }
if($viewsPrefix == '/') { $viewsPrefix = ''; }
$descriptor->viewsPrefix = $viewsPrefix;

if(isset($this->routes)) { $routesPrefix = $this->routes; }
if($routesPrefix == '/') { $routesPrefix = ''; }
$descriptor->routesPrefix = $routesPrefix;
}
}
?>

0 comments on commit d3f8f0c

Please sign in to comment.