dwing

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