dwing

annotate inc/rest.class.php @ 186:cfd4d1dcece4

theoretically support OpenID AX with both axschema and schema.openid.net, thank god that no provider has support for this yet somehow
author Arpad Borsos <arpad.borsos@googlemail.com>
date Thu Aug 05 12:04:49 2010 +0200 (21 months ago)
parents 80aa23316572
children
rev   line source
arpad@94 1 <?php
arpad@94 2 /*
arpad@94 3 * dWing - a cms aimed to be as bleeding edge as possible
arpad@94 4 * Copyright (C) 2007-2008 Arpad Borsos <arpad.borsos@googlemail.com>
arpad@94 5 *
arpad@94 6 * This program is free software: you can redistribute it and/or modify
arpad@94 7 * it under the terms of the GNU General Public License as published by
arpad@94 8 * the Free Software Foundation, either version 3 of the License, or
arpad@94 9 * (at your option) any later version.
arpad@94 10 *
arpad@94 11 * This program is distributed in the hope that it will be useful,
arpad@94 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
arpad@94 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
arpad@94 14 * GNU General Public License for more details.
arpad@94 15 *
arpad@94 16 * You should have received a copy of the GNU General Public License
arpad@94 17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
arpad@94 18 */
arpad@94 19
arpad@94 20 /*
arpad@94 21 * TODO:
arpad@94 22 * hook this up with dwings template system, probably use the RESTDispatcher
arpad@94 23 * as only method of answering requests
arpad@94 24 * 1. check if there is a template file with the name of the requested resource
arpad@94 25 * -> display it
arpad@94 26 * 2. if the resource throws an exception or does not exist, display a error page
arpad@94 27 * 3. check if there is a template file with the name "Resource.RequestType.Method"
arpad@94 28 * -> 415 if there is a requested type without a corresponding template
arpad@94 29 * 4. if there is no RequestType given:
arpad@94 30 * -> fall back to Resource.xhtml.Method -> the resource may display different
arpad@94 31 * xhtml representations depending on the Method (GET -> full page, POST -> fragment)
arpad@94 32 * -> fall back to Resource.json
arpad@94 33 * -> fall back to just call object->toJSON()
arpad@94 34 *
arpad@94 35 * Problems:
arpad@94 36 * - Error Messages in XHTML or plaintext?
arpad@94 37 * - object arrays?
arpad@94 38 */
arpad@94 39
arpad@94 40 class NotFoundException extends Exception // 404
arpad@94 41 {
arpad@94 42 public $httpCode = 404;
arpad@94 43 protected $message = 'Ressource not found';
arpad@94 44 }
arpad@94 45 // 415 Unsupported Media Type ???
arpad@94 46 class UnauthorizedException extends Exception // 401
arpad@94 47 {
arpad@94 48 public $httpCode = 401;
arpad@94 49 protected $message = 'Unauthorized';
arpad@94 50 }
arpad@94 51 class NotImplementedException extends Exception // 501
arpad@94 52 {
arpad@94 53 public $httpCode = 501;
arpad@94 54 protected $message = 'Method not implemented';
arpad@94 55 }
arpad@94 56
arpad@112 57 class NoDispatcher extends Exception
arpad@112 58 {
arpad@112 59 protected $message = 'No RESTful Dispatcher found';
arpad@112 60 }
arpad@112 61
arpad@116 62 class UseTemplateException extends Exception
arpad@116 63 {}
arpad@133 64 // TODO: maybe use 201 Created?
arpad@116 65
arpad@167 66 /**
arpad@167 67 * Interface of a Resource that can react to the 4 basic HTTP methods
arpad@167 68 * Each one of those methods corresponds to the specified HTTP method
arpad@167 69 * Each one of those methods returns either a String, a JSONable object or
arpad@167 70 * throws one of the exceptions mentioned on top
arpad@167 71 */
arpad@94 72 interface RESTful
arpad@94 73 {
arpad@167 74 public static function doGET(RESTDispatcher $dispatcher);
arpad@167 75 public static function doPOST(RESTDispatcher $dispatcher);
arpad@167 76 public static function doPUT(RESTDispatcher $dispatcher);
arpad@167 77 public static function doDELETE(RESTDispatcher $dispatcher);
arpad@94 78 }
arpad@94 79
arpad@167 80 /**
arpad@167 81 * This is the main Object that handles all the incoming requests and dispatches
arpad@167 82 * Them to Object dispatcher implementing the RESTful interface
arpad@167 83 */
arpad@94 84 class RESTDispatcher
arpad@94 85 {
arpad@94 86 public $requestedType;
arpad@94 87 protected $resources = array();
arpad@94 88 protected $current = 0;
arpad@94 89
arpad@94 90 public function __construct()
arpad@94 91 {
arpad@112 92 $url = !empty($_SERVER['PATH_INFO']) ? trim($_SERVER['PATH_INFO'], '/') : '';
arpad@112 93
arpad@94 94 $frags = explode('.', $url);
arpad@94 95 if(!empty($frags[1]))
arpad@94 96 $this->requestedType = $frags[1];
arpad@94 97 $frags = explode('/', $frags[0]);
arpad@94 98 for($i = 0; $i < count($frags); $i+=2)
arpad@94 99 {
arpad@94 100 if(empty($frags[$i]))
arpad@94 101 break;
arpad@94 102 $temp = array('resource' => strtolower($frags[$i]));
arpad@94 103 if(!empty($frags[$i+1]))
arpad@94 104 $temp['id'] = $frags[$i+1];
arpad@94 105 array_push($this->resources, $temp);
arpad@94 106 }
arpad@112 107 // do initial dispatching here
arpad@112 108 //var_dump($this->resources);
arpad@112 109 try
arpad@112 110 {
arpad@112 111 if(empty($this->resources))
arpad@112 112 {
arpad@112 113 // no resource requested -> display index
arpad@112 114 Core::$tpl->display('index.tpl.php');
arpad@112 115 return;
arpad@112 116 }
arpad@112 117 $obj = $this->dispatch();
arpad@112 118 $res = $this->resources[$this->current]['resource'];
arpad@128 119 if(is_object($obj) && Core::$tpl->template_exists($res.'.tpl.php'))
arpad@112 120 {
arpad@112 121 // we have a template named like the resource -> the template knows how
arpad@112 122 // to display it
arpad@112 123 Core::$tpl->assign('object', $obj);
arpad@112 124 Core::$tpl->display($res.'.tpl.php');
arpad@112 125 }
arpad@112 126 else
arpad@112 127 {
arpad@118 128 if(!is_object($obj))
arpad@118 129 {
arpad@118 130 header('Content-Type: text/javascript; charset=utf-8');
arpad@118 131 echo $obj;
arpad@118 132 }
arpad@118 133 else if(!method_exists($obj, 'toJSON'))
arpad@112 134 throw new NotImplementedException();
arpad@118 135 else
arpad@118 136 {
arpad@118 137 header('Content-Type: text/javascript; charset=utf-8');
arpad@118 138 echo $obj->toJSON();
arpad@118 139 }
arpad@112 140 }
arpad@112 141 }
arpad@112 142 catch(Exception $e)
arpad@112 143 {
arpad@116 144 if($e instanceof UseTemplateException)
arpad@116 145 {
arpad@116 146 // Use the template inside the Exception to display the page
arpad@116 147 $tpl = $e->getMessage();
arpad@116 148 unset($e);
arpad@116 149 if(Core::$tpl->template_exists($tpl.'.tpl.php'))
arpad@116 150 Core::$tpl->display($tpl.'.tpl.php');
arpad@116 151 else
arpad@116 152 $e = new NotFoundException();
arpad@116 153 }
arpad@116 154 else if($e instanceof NoDispatcher && $this->current == 0)
arpad@112 155 {
arpad@112 156 // no RESTful class found -> maybe we have a template with this name?
arpad@112 157 unset($e);
arpad@112 158 $res = $this->resources[$this->current]['resource'];
arpad@112 159 if(Core::$tpl->template_exists($res.'.tpl.php'))
arpad@112 160 Core::$tpl->display($res.'.tpl.php');
arpad@112 161 else
arpad@112 162 $e = new NotFoundException();
arpad@112 163 }
arpad@112 164 // error occured -> display error page
arpad@112 165 if(isset($e))
arpad@112 166 {
arpad@112 167 Core::$tpl->assign('error', $e);
arpad@112 168 Core::$tpl->display('error.tpl.php');
arpad@112 169 }
arpad@112 170 }
arpad@94 171 }
arpad@112 172 public function assignObject($aObj)
arpad@94 173 {
arpad@94 174 $this->resources[$this->current]['obj'] = $aObj;
arpad@94 175 }
arpad@94 176 public function current()
arpad@94 177 {
arpad@94 178 if(!empty($this->resources[$this->current]))
arpad@94 179 return $this->resources[$this->current];
arpad@94 180 }
arpad@94 181 public function next()
arpad@94 182 {
arpad@94 183 if(!empty($this->resources[$this->current+1]))
arpad@94 184 {
arpad@94 185 $this->current++;
arpad@94 186 return $this->resources[$this->current];
arpad@94 187 }
arpad@94 188 }
arpad@167 189 public function peekNext()
arpad@167 190 {
arpad@167 191 return !empty($this->resources[$this->current+1]) ?
arpad@167 192 $this->resources[$this->current+1] : null;
arpad@167 193 }
arpad@94 194 public function previous()
arpad@94 195 {
arpad@94 196 if($this->current > 0)
arpad@94 197 {
arpad@94 198 $this->current--;
arpad@94 199 return $this->resources[$this->current];
arpad@94 200 }
arpad@94 201 }
arpad@167 202 public function peekPrevious()
arpad@167 203 {
arpad@167 204 return $this->current > 0 ? $this->resources[$this->current-1] : null;
arpad@167 205 }
arpad@94 206 public function dispatch()
arpad@94 207 {
arpad@112 208 $className = $this->resources[$this->current]['resource'].'Dispatcher';
arpad@170 209 if(!class_exists($className) || !Utils::doesImplement($className, 'RESTful'))
arpad@94 210 {
arpad@112 211 throw new NoDispatcher();
arpad@94 212 }
arpad@167 213 return call_user_func(array($className, 'do'.$_SERVER['REQUEST_METHOD']), $this);
arpad@167 214 }
arpad@167 215 /**
arpad@167 216 * Returns the JSON object passed in as POST/PUT body
arpad@167 217 */
arpad@167 218 public function getJSON()
arpad@167 219 {
arpad@167 220 return json_decode(file_get_contents('php://input'), true);
arpad@94 221 }
arpad@94 222 }
arpad@94 223
arpad@167 224 /**
arpad@167 225 * REST is the abstract base class implementing the RESTful interface
arpad@167 226 * you may override the methods to specify a more suitable behavior.
arpad@167 227 */
arpad@112 228 abstract class REST implements RESTful
arpad@94 229 {
arpad@167 230 /**
arpad@167 231 * TODO: make the base class work with parent/child resources.
arpad@167 232 * Use the new ContentProvider interfaces for that.
arpad@167 233 *
arpad@94 234 * the abstract class cannot deal with parent-resources and fails.
arpad@94 235 * it automatically forwards the request to any child-resources.
arpad@94 236 * override the methods if other behavior is wanted.
arpad@167 237 */
arpad@167 238 /**
arpad@94 239 * the abstract class cannot deal with getting multiple objects.
arpad@94 240 * override this method to deal with that.
arpad@167 241 */
arpad@94 242 public static function GET(RESTDispatcher $dispatcher)
arpad@94 243 {
arpad@94 244 $current = $dispatcher->current();
arpad@94 245 if($parent = $dispatcher->previous())
arpad@94 246 throw new NotImplementedException(); // can't deal with parent-resources
arpad@94 247 if(empty($current['id']))
arpad@94 248 throw new NotImplementedException(); // listing not implemented
arpad@94 249 $obj = new $current['resource']($current['id']);
arpad@94 250 $dispatcher->assignObject($obj);
arpad@94 251
arpad@94 252 $child = $dispatcher->next();
arpad@94 253 if(!$child)
arpad@94 254 return $obj;
arpad@94 255 else
arpad@94 256 return $dispatcher->dispatch();
arpad@94 257 }
arpad@94 258 public static function POST(RESTDispatcher $dispatcher)
arpad@94 259 {
arpad@94 260 $current = $dispatcher->current();
arpad@94 261 if($parent = $dispatcher->previous())
arpad@94 262 throw new NotImplementedException(); // can't deal with parent-resources
arpad@167 263 $child = $dispatcher->peekNext();
arpad@94 264 if(!$child && !empty($current['id']))
arpad@94 265 throw new NotImplementedException(); // can't POST to a existing resource
arpad@94 266 if(!$child)
arpad@94 267 {
arpad@167 268 $obj = new $current['resource']($dispatcher->getJSON());
arpad@94 269 $obj->save();
arpad@94 270 return $obj;
arpad@94 271 }
arpad@167 272 // else: have $child
arpad@94 273 $obj = new $current['resource']($current['id']);
arpad@94 274 $dispatcher->assignObject($obj);
arpad@94 275
arpad@167 276 $dispatcher->next(); // goes forward to the child
arpad@94 277 return $dispatcher->dispatch();
arpad@94 278 }
arpad@94 279 public static function PUT(RESTDispatcher $dispatcher)
arpad@94 280 {
arpad@94 281 $current = $dispatcher->current();
arpad@94 282 if($parent = $dispatcher->previous())
arpad@94 283 throw new NotImplementedException(); // can't deal with parent-resources
arpad@94 284 if(empty($current['id']))
arpad@94 285 throw new NotImplementedException(); // we need a existing resource
arpad@94 286
arpad@94 287 $obj = new $current['resource']($current['id']);
arpad@94 288 $dispatcher->assignObject($obj);
arpad@94 289
arpad@94 290 $child = $dispatcher->next();
arpad@94 291 if(!$child) // update the current resource
arpad@94 292 {
arpad@167 293 $obj->assignData($dispatcher->getJSON());
arpad@94 294 $obj->save();
arpad@94 295 return $obj;
arpad@94 296 }
arpad@94 297 // have $child
arpad@94 298 return $dispatcher->dispatch(); // dispatch to child
arpad@94 299 }
arpad@94 300 public static function DELETE(RESTDispatcher $dispatcher)
arpad@94 301 {
arpad@94 302 $current = $dispatcher->current();
arpad@94 303 if($parent = $dispatcher->previous())
arpad@94 304 throw new NotImplementedException(); // can't deal with parent-resources
arpad@94 305 if(empty($current['id']))
arpad@94 306 throw new NotImplementedException(); // we need a existing resource
arpad@94 307
arpad@94 308 $obj = new $current['resource']($current['id']);
arpad@94 309 $dispatcher->assignObject($obj);
arpad@94 310
arpad@94 311 $child = $dispatcher->next();
arpad@94 312 if(!$child) // destroy the current resource
arpad@94 313 {
arpad@174 314 return $obj->delete(true);
arpad@94 315 }
arpad@94 316 // have $child
arpad@94 317 return $dispatcher->dispatch(); // dispatch to child
arpad@94 318 }
arpad@94 319 }
arpad@94 320 ?>