| 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 ?>
|