├── views
└── rest
│ └── html.php
├── config
└── rest.php
├── classes
├── rest
│ ├── model.php
│ ├── controller.php
│ ├── auth.php
│ ├── content
│ │ ├── csv.php
│ │ ├── rss.php
│ │ ├── xml.php
│ │ ├── atom.php
│ │ ├── html.php
│ │ ├── json.php
│ │ └── rdf.php
│ ├── cors.php
│ ├── method
│ │ ├── get.php
│ │ ├── put.php
│ │ ├── head.php
│ │ ├── patch.php
│ │ ├── post.php
│ │ ├── trace.php
│ │ ├── delete.php
│ │ ├── options.php
│ │ ├── basic.php
│ │ └── all.php
│ └── core.php
├── rest.php
├── controller
│ ├── rest.php
│ └── template
│ │ └── rest.php
└── model
│ └── rest
│ └── test.php
├── init.php
└── README.md
/views/rest/html.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/config/rest.php:
--------------------------------------------------------------------------------
1 | FALSE
6 | );
7 |
--------------------------------------------------------------------------------
/classes/rest/model.php:
--------------------------------------------------------------------------------
1 | ((/)(.))')
14 | ->defaults(array(
15 | 'controller' => 'rest'
16 | ));
17 |
--------------------------------------------------------------------------------
/classes/controller/template/rest.php:
--------------------------------------------------------------------------------
1 | _rest = REST::instance($this)
29 | ->method_override(TRUE)
30 | ->content_override(TRUE)
31 | ->execute();
32 | }
33 |
34 | public function action_html()
35 | {
36 | $values = $this->_rest->result();
37 | $view = View::factory('rest/html', array('values' => $values));
38 | $this->response->body($view);
39 | }
40 |
41 | public function action_json()
42 | {
43 | $json = $this->_rest->etag()->result_json();
44 | $this->response->body($json);
45 | }
46 |
47 | public function action_xml()
48 | {
49 | $xml = $this->_rest->etag()->result_xml();
50 | $this->response->body($xml->asXML());
51 | }
52 |
53 | public function action_csv()
54 | {
55 | $csv = $this->_rest->result_csv();
56 | $this->response->body($csv);
57 | }
58 |
59 | }
--------------------------------------------------------------------------------
/classes/model/rest/test.php:
--------------------------------------------------------------------------------
1 | 1, 'title' => 'one', 'author' => 'Jeremy', 'content' => 'Hi'),
18 | array('id' => 2, 'title' => 'two', 'author' => 'Tara', 'content' => 'Hello'),
19 | array('id' => 3, 'title' => 'three', 'author' => 'Isaac', 'content' => 'Hey'),
20 | array('id' => 4, 'title' => 'four', 'author' => 'Zander', 'content' => 'Yo'),
21 | array('id' => 5, 'title' => 'five', 'author' => 'Bryan', 'content' => 'Holla'),
22 | array('id' => 6, 'title' => 'six', 'author' => 'Jon', 'content' => 'Ciao'),
23 | array('id' => 7, 'title' => 'seven', 'author' => 'Leah', 'content' => 'How do you do?'),
24 | array('id' => 8, 'title' => 'eight', 'author' => 'Sean', 'content' => 'Bonjour'),
25 | array('id' => 9, 'title' => 'nine', 'author' => 'Scott', 'content' => 'what\'s up')
26 | );
27 |
28 | public function rest_auth(Rest $rest)
29 | {
30 | $user = Auth::instance()->get_user();
31 | return $rest->method() == 'OPTIONS' OR $user !== FALSE;
32 | }
33 |
34 | /**
35 | * Cross-Origin Resource Sharing
36 | *
37 | * @param Rest $rest
38 | */
39 | public function rest_cors(Rest $rest)
40 | {
41 | $origin = $rest->request()->headers('Origin');
42 | if (in_array($origin, self::$origin))
43 | {
44 | $rest->cors(array('origin' => $origin, 'creds' => 'true'));
45 | }
46 | }
47 |
48 | /**
49 | * Cross-Origin Resource Sharing
50 | *
51 | * @param Rest $rest
52 | */
53 | public function rest_options(Rest $rest)
54 | {
55 | $rest->send_code(200);
56 | }
57 |
58 | /**
59 | * Returns test data
60 | *
61 | * @param Rest $rest
62 | */
63 | public function rest_get(Rest $rest)
64 | {
65 | $data = Session::instance()->get('rest_test_data', $this->_data);
66 | $id = $rest->param('id');
67 | if ( ! empty($id))
68 | {
69 | if ( ! isset($data[$id]))
70 | {
71 | throw new Http_Exception_404('Resource not found, ID: :id', array(':id' => $id));
72 | }
73 | return $data[$id];
74 |
75 | }
76 | else
77 | {
78 | return $data;
79 | }
80 | }
81 |
82 | /**
83 | *
84 | * @param Rest $rest
85 | */
86 | public function rest_put(Rest $rest)
87 | {
88 | $id = $rest->param('id');
89 | if ( ! empty($id))
90 | {
91 | $data = Session::instance()->get('rest_test_data', $this->_data);
92 | $data[$id] = $rest->body('json', TRUE);
93 | Session::instance()->set('rest_test_data', $data);
94 | return $data[$id];
95 | }
96 | else
97 | {
98 | // TODO
99 | $rest->send_code(403); //Forbidden
100 | }
101 | }
102 |
103 | /**
104 | *
105 | * @param Rest $rest
106 | */
107 | public function rest_post(Rest $rest)
108 | {
109 | $data = Session::instance()->get('rest_test_data', $this->_data);
110 | $post = $rest->post();
111 | if (empty($post))
112 | {
113 | $post = $rest->body('json', TRUE);
114 | }
115 | $data[] = $post;
116 | Session::instance()->set('rest_test_data', $data);
117 | $rest->send_created(count($data)+1);
118 | }
119 |
120 | /**
121 | *
122 | * @param Rest $rest
123 | */
124 | public function rest_delete(Rest $rest)
125 | {
126 | $id = $rest->param('id');
127 | $data = Session::instance()->get('rest_test_data', $this->_data);
128 | if (isset($data[$id]))
129 | {
130 | unset($data[$id]);
131 | Session::instance()->set('rest_test_data', $data);
132 | }
133 | $rest->send_code(204);
134 | }
135 |
136 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #Kohana RESTful Web Service Library
2 | * Author: Jeremy Fowler
3 | * Copyright: (c) 2012 Jeremy Fowler
4 | * License: http://www.opensource.org/licenses/BSD-3-Clause
5 |
6 | ##Requires
7 | * Kohana >= 3.2
8 | * PHP >= 5.3
9 |
10 | ##Features
11 | * X-HTTP-METHOD-OVERRIDE support
12 | * Cross-Origin Resource Sharing
13 | * ETags
14 |
15 | ##Installation
16 |
17 | * `cd modules`
18 | * `git clone git://github.com/jerfowler/REST.git`
19 | * Enable the REST module in bootstrap
20 | * Create REST extended models in classes/model/rest
21 | * Optional: create your own custom controller_rest
22 |
23 | ##Controllers handle content type and output
24 | Each controller must implement one or more of the REST_Content Interfaces
25 |
26 | ```php
27 | class Controller_Template_REST extends Controller
28 | implements REST_Content_HTML,
29 | REST_Content_JSON,
30 | REST_Content_XML,
31 | REST_Content_CSV {
32 | ```
33 | ###The Rest module gets instantiated in the before method of the controller
34 | * The model is determined by the Controller's action
35 | * REST supports X-HTTP-METHOD-OVERRIDE by using `method_override(TRUE)`
36 |
37 | ```php
38 | /**
39 | * Rest object
40 | * @var Rest
41 | */
42 | protected $_rest;
43 |
44 | public function before()
45 | {
46 | parent::before();
47 |
48 | $this->_rest = REST::instance($this)
49 | ->method_override(TRUE)
50 | ->content_override(TRUE)
51 | ->execute();
52 | }
53 | ```
54 |
55 | ####Content-Type is auto-detected by the headers (as is Language & Charset)
56 | This can be overridden by using `content_override(TRUE)` and using a special route
57 |
58 | ```php
59 | Route::set('rest', 'rest/((/)(.))')
60 | ->defaults(array(
61 | 'controller' => 'rest'
62 | ));
63 | ```
64 |
65 | ###Output is handled by the various REST_Content Interface Methods
66 |
67 | * Models pass the values generated by the HTTP method which is then retrieved by `result()` and then various other `result_x()` helper functions.
68 | * ETags read/generated using the `etag()` method
69 |
70 | ```php
71 | public function action_html()
72 | {
73 | $values = $this->_rest->result();
74 | $view = View::factory('rest/html', array('values' => $values));
75 | $this->response->body($view);
76 | }
77 |
78 | public function action_json()
79 | {
80 | $json = $this->_rest->etag()->result_json();
81 | $this->response->body($json);
82 | }
83 |
84 | public function action_xml()
85 | {
86 | $xml = $this->_rest->etag()->result_xml();
87 | $this->response->body($xml->asXML());
88 | }
89 |
90 | public function action_csv()
91 | {
92 | $csv = $this->_rest->result_csv();
93 | $this->response->body($csv);
94 | }
95 | ```
96 |
97 | ##Models handle the HTTP methods
98 | * Each model must implement one or more of the REST_Method Interfaces
99 | * Model names are pluralized
100 |
101 | ```php
102 | class Model_REST_Users
103 | implements REST_CORS,
104 | REST_Method_Get,
105 | REST_Method_Post {
106 | ```
107 | ###Each HTTP method is handled by the corresponding Interface method
108 |
109 | ```php
110 | /**
111 | * Cross-Origin Resource Sharing
112 | */
113 | public function rest_cors(Rest $rest)
114 | {
115 | $origin = $rest->request()->headers('Origin');
116 | if (in_array($origin, self::$origin))
117 | {
118 | $rest->cors(array('origin' => $origin, 'creds' => 'true'));
119 | }
120 | }
121 |
122 | public function rest_options(Rest $rest)
123 | {
124 | $rest->send_code(200);
125 | }
126 |
127 | public function rest_get(Rest $rest)
128 | {
129 | $data = Session::instance()->get('rest_test_data', $this->_data);
130 | $id = $rest->param('id');
131 | if ( ! empty($id))
132 | {
133 | if ( ! isset($data[$id]))
134 | {
135 | $rest->send_code(404); //Not Found
136 | }
137 | return $data[$id];
138 |
139 | }
140 | else
141 | {
142 | return $data;
143 | }
144 | }
145 |
146 | public function rest_put(Rest $rest)
147 | {
148 | $id = $rest->param('id');
149 | if ( ! empty($id))
150 | {
151 | $data = Session::instance()->get('rest_test_data', $this->_data);
152 | $data[$id] = $rest->body('json', TRUE);
153 | Session::instance()->set('rest_test_data', $data);
154 | return $data[$id];
155 | }
156 | else
157 | {
158 | $rest->send_code(403); //Forbidden
159 | }
160 |
161 | }
162 |
163 | public function rest_post(Rest $rest)
164 | {
165 | $data = Session::instance()->get('rest_test_data', $this->_data);
166 | $post = $rest->post();
167 | if (empty($post))
168 | {
169 | $post = $rest->body('json', TRUE);
170 | }
171 | $data[] = $post;
172 | Session::instance()->set('rest_test_data', $data);
173 | $rest->send_created(count($data)+1);
174 | }
175 |
176 | public function rest_delete(Rest $rest)
177 | {
178 | $id = $rest->param('id');
179 | $data = Session::instance()->get('rest_test_data', $this->_data);
180 | if (isset($data[$id]))
181 | {
182 | unset($data[$id]);
183 | Session::instance()->set('rest_test_data', $data);
184 | }
185 | $rest->send_code(204);
186 | }
187 | ```
--------------------------------------------------------------------------------
/classes/rest/core.php:
--------------------------------------------------------------------------------
1 | 'rest_',
18 | 'model' => 'Model_REST_',
19 | 'method' => 'REST_Method_',
20 | 'content' => 'REST_Content_'
21 | );
22 | public static $_methods = array(
23 | 'GET',
24 | 'PUT',
25 | 'POST',
26 | 'DELETE',
27 | 'HEAD',
28 | 'TRACE',
29 | 'PATCH',
30 | 'OPTIONS'
31 | );
32 | public static $_types = array(
33 | 'text/html' => 'html',
34 | 'application/json' => 'json',
35 | 'application/xml' => 'xml',
36 | 'application/rdf+xml' => 'rdf',
37 | 'application/rss+xml' => 'rss',
38 | 'application/atom+xml' => 'atom',
39 | 'application/vnd.ms-excel' => 'csv'
40 | );
41 | public static $_cors = array(
42 | 'origin' => '*',
43 | 'methods' => null,
44 | 'headers' => array('Origin', 'Accept', 'Accept-Language', 'Content-Type', 'X-Requested-With', 'X-CSRF-Token'),
45 | 'expose' => null,
46 | 'creds' => null,
47 | 'age' => null
48 | );
49 |
50 | public static function prefix($name, $value)
51 | {
52 | if (is_null($name))
53 | {
54 | return self::$_prefix;
55 | }
56 |
57 | if (is_null($value))
58 | {
59 | return Arr::get(self::$_prefix, $name, NULL);
60 | }
61 |
62 | self::$_prefix[$name] = $value;
63 | }
64 |
65 | /**
66 | * Singleton pattern
67 | *
68 | * @return Rest
69 | */
70 | public static function instance(REST_Controller $controller, $config = array())
71 | {
72 | if (!isset(Rest::$_instance))
73 | {
74 | // Create a new session instance
75 | Rest::$_instance = new Rest($controller, $config);
76 | }
77 |
78 | return Rest::$_instance;
79 | }
80 |
81 | /**
82 | * Adds prefixes to common names to return the full class name
83 | *
84 | * @param string $type The class type
85 | * @param string $name The Common name
86 | * @return string
87 | */
88 | public static function class_name($type, $name)
89 | {
90 | $prefix = isset(Rest::$_prefix[$type]) ? Rest::$_prefix[$type] : '';
91 | return strtolower($prefix . $name);
92 | }
93 |
94 | /**
95 | * Removes prefixes of class names to return the common name
96 | *
97 | * @param string $type The class type
98 | * @param object|string $name An instance of a class or the class name
99 | * @return string
100 | */
101 | public static function common_name($type, $name)
102 | {
103 | $name = is_object($name) ? get_class($name) : $name;
104 | $prefix = isset(Rest::$_prefix[$type]) ? Rest::$_prefix[$type] : '';
105 | return substr($name, strlen($prefix));
106 | }
107 |
108 | public static function join($values, $glue = ', ')
109 | {
110 | return is_array($values) ? implode($glue, $values) : $values;
111 | }
112 |
113 | /**
114 | * @var Array configuration options
115 | */
116 | protected $_config;
117 |
118 | /**
119 | * @var REST_Controller model
120 | */
121 | protected $_controller;
122 |
123 | /**
124 | * @var REST_Model model
125 | */
126 | protected $_model;
127 |
128 | /**
129 | * @var Request request instance
130 | */
131 | protected $_request;
132 |
133 | /**
134 | * @var Kohana_Response response instance
135 | */
136 | protected $_response;
137 |
138 | /**
139 | * @var String method HTTP method
140 | */
141 | protected $_method;
142 |
143 | /**
144 | * @var String content HTTP Accept type
145 | */
146 | protected $_content;
147 |
148 | /**
149 | * @var String charset HTTP Accept charset
150 | */
151 | protected $_charset;
152 |
153 | /**
154 | * @var String language HTTP Accept language
155 | */
156 | protected $_language;
157 |
158 | /**
159 | * @var Mixed result from the model's method
160 | */
161 | protected $_result;
162 |
163 | /**
164 | * Loads Session and configuration options.
165 | *
166 | * @param REST_Controller $controller
167 | * @param mixed $config
168 | */
169 | public function __construct(REST_Controller $controller, $config = array())
170 | {
171 | $default = Kohana::$config->load('rest')->as_array();
172 | $config = Arr::merge($default, $config);
173 |
174 | $this->request($request = Arr::get($config, 'request', Request::initial()));
175 | $this->response(Arr::get($config, 'response', $request->response()));
176 | $this->method(Arr::get($config, 'method', $request->method()));
177 | $this->model(Arr::get($config, 'model', $request->action()));
178 | unset($config['request'], $config['response'], $config['method'], $config['model']);
179 |
180 | $this->controller($controller);
181 |
182 | $this->content(Arr::get($config, 'types', $this->accept()));
183 | $this->charset(Arr::get($config, 'charsets', array(Kohana::$charset)));
184 | $this->language(Arr::get($config, 'languages', array(I18n::$lang)));
185 |
186 | // Save the config in the object
187 | $this->_config = $config;
188 | }
189 |
190 | /**
191 | * Execute the REST model and save the results
192 | *
193 | * @param void
194 | * @return mixed
195 | */
196 | public function execute()
197 | {
198 | // Delay verifying method until execute in the event of an override
199 | $method = Rest::class_name('method', $this->_method);
200 | if (!$this->_model instanceof $method)
201 | {
202 | // Send the "Method Not Allowed" response
203 | $this->_response->headers('Allow', $this->allowed());
204 | $this->send_code(405, array('Method :method not allowed.', array(':method' => $this->_method)));
205 | }
206 |
207 | // Check if this is a Cross-Origin Resource Sharing Model
208 | if ($this->_model instanceof REST_CORS)
209 | {
210 | $this->_model->rest_cors($this);
211 | }
212 |
213 | // Check if this is an Authorized Model
214 | if ($this->_model instanceof REST_AUTH)
215 | {
216 | if (FALSE === $this->_model->rest_auth($this))
217 | {
218 | // Unauthorized
219 | $this->send_code(401);
220 | }
221 | }
222 |
223 | // Execute the model's method, save the result
224 | $exec = Rest::class_name('exec', $this->_method);
225 | $this->_result = $this->_model->$exec($this);
226 |
227 | // Set the action of the controller to the content type
228 | $this->request()->action(Rest::$_types[$this->_content]);
229 |
230 | // Set the Content headers
231 | $type = $this->_content . '; charset=' . $this->_charset;
232 | $this->response()->headers('Content-Type', $type);
233 | $this->response()->headers('Content-Language', $this->_language);
234 |
235 | return $this;
236 | }
237 |
238 | /**
239 | * Returns the result of the model's method
240 | *
241 | * @return mixed
242 | */
243 | public function result()
244 | {
245 | return $this->_result;
246 | }
247 |
248 | /**
249 | * Returns JSON encoded string of the result of the model's method
250 | *
251 | * @return String
252 | */
253 | public function result_json()
254 | {
255 | return json_encode($this->_result);
256 | }
257 |
258 | /**
259 | * Generates SimpleXML object from the result of the model's method
260 | *
261 | * @param string $name Optional name of the root node, defaults to model's name
262 | * @return SimpleXMLElement
263 | */
264 | public function result_xml($name = NULL)
265 | {
266 | $values = $this->result();
267 | if(is_null($name))
268 | {
269 | $model = $this->model();
270 | $name = strtolower(Rest::common_name('model', $model));
271 | }
272 |
273 | $walk = function(Array $vars, $xml, $node) use (&$walk)
274 | {
275 | foreach ($vars as $name => $value)
276 | {
277 | if (is_array($value))
278 | {
279 | $name = is_int($name) ? $node : $name;
280 | $sub = $xml->addChild($name);
281 | $walk($value, $sub, Inflector::singular($name));
282 | }
283 | else
284 | {
285 | $xml->addChild($name, htmlentities($value, ENT_QUOTES));
286 | }
287 | }
288 | };
289 |
290 | // Check for associative array
291 | if (array_keys($values) !== range(0, count($values) - 1))
292 | {
293 | $xml = new SimpleXMLElement('<' . Inflector::singular($name) . '/>');
294 | }
295 | else
296 | {
297 | $xml = new SimpleXMLElement('<' . $name . '/>');
298 | }
299 |
300 | $walk($values, $xml, Inflector::singular($name));
301 | return $xml;
302 | }
303 |
304 | /**
305 | * Generates MS Excel Formated CSV string
306 | *
307 | * @param string $filename Optional filename of the CSV, defaults to model's name
308 | * @return string
309 | */
310 | public function result_csv($filename = NULL)
311 | {
312 | $model = $this->model();
313 | if (is_null($filename))
314 | {
315 | $filename = strtolower(Rest::common_name('model', $model));
316 | }
317 | $this->response()->headers('Content-disposition', 'filename='.$filename.'.csv');
318 |
319 | $csv = '';
320 | $values = $this->result();
321 | if (empty($values)) return $csv;
322 |
323 | $titles = function(Array $vars, $node) use (&$titles)
324 | {
325 | $result = array();
326 | foreach ($vars as $name => $value)
327 | {
328 | if (is_array($value))
329 | {
330 | $name = is_int($name) ? $node.'_'.$name : $name;
331 | $result[] = $titles($value, $name);
332 | }
333 | else
334 | {
335 | $result[] = empty($node) ? $name : $node.'.'.$name;
336 | }
337 | }
338 | return implode('","', $result);
339 | };
340 |
341 | $walk = function(Array $vars) use (&$walk)
342 | {
343 | $result = array();
344 | foreach ($vars as $name => $value)
345 | {
346 | if (is_array($value))
347 | {
348 | $result[] = $walk($value);
349 | }
350 | else
351 | {
352 | $result[] = str_replace('"', '""', $value);
353 | }
354 | }
355 | return implode('","', $result);
356 | };
357 |
358 | // Check for associative array
359 | if (array_keys($values) !== range(0, count($values) - 1))
360 | {
361 | $csv = '"'.$titles($values, '')."\"\n";
362 | $csv .= '"'.$walk($values)."\"\n";
363 | }
364 | else
365 | {
366 | $csv = '"'.$titles($values[0], '')."\"\n";
367 | foreach ($values as $row)
368 | {
369 | $csv .= '"'.$walk($row)."\"\n";
370 | }
371 | }
372 | return $csv;
373 | }
374 |
375 | /**
376 | * Checks ETag, sends 304 on match, generates ETag header
377 | *
378 | * @param string $hash The hash used to generate the ETag, defaults to sha1
379 | * @return REST_Core
380 | */
381 | public function etag($hash = 'sha1')
382 | {
383 | $match = $this->request()->headers('If-None-Match');
384 | $etag = $hash($this->result_json());
385 | if ($match === $etag)
386 | {
387 | $this->send_code(304);
388 | }
389 | else
390 | {
391 | $this->response()->headers('ETag', $etag);
392 | }
393 |
394 | return $this;
395 | }
396 |
397 | /**
398 | * Returns the accepted content types based on Controller's interfaces
399 | *
400 | * @return mixed
401 | */
402 | public function accept()
403 | {
404 | $accept = array();
405 | foreach (Rest::$_types as $type => $value)
406 | {
407 | $content = Rest::class_name('content', $value);
408 | if ($this->_controller instanceof $content)
409 | {
410 | $accept[] = $type;
411 | }
412 | }
413 | return $accept;
414 | }
415 |
416 | /**
417 | * Return the allowed methods of the model
418 | *
419 | * @param void
420 | * @return mixed
421 | */
422 | public function allowed()
423 | {
424 | $allowed = array();
425 | foreach (Rest::$_methods as $method)
426 | {
427 | $class = Rest::class_name('method', $method);
428 | if ($this->_model instanceof $class)
429 | {
430 | $allowed[] = $method;
431 | }
432 | }
433 | return $allowed;
434 | }
435 |
436 | /**
437 | * Get the short-name content type of the request
438 | * @return string
439 | */
440 | public function type()
441 | {
442 | $type = $this->request()->headers('Content-Type');
443 | return Arr::get(Rest::$_types, $type, $type);
444 | }
445 |
446 | /**
447 | * Retrieves a value from the route parameters.
448 | *
449 | * $id = $request->param('id');
450 | *
451 | * @param string $key Key of the value
452 | * @param mixed $default Default value if the key is not set
453 | * @return mixed
454 | */
455 | public function param($key = NULL, $default = NULL)
456 | {
457 | return $this->request()->param($key, $default);
458 | }
459 |
460 | /**
461 | * Gets HTTP POST parameters to the request.
462 | *
463 | * @param mixed $key Key or key value pairs to set
464 | * @return mixed
465 | */
466 | public function post($key = NULL)
467 | {
468 | return $this->request()->post($key);
469 | }
470 |
471 | /**
472 | * Gets HTTP query string.
473 | *
474 | * @param mixed $key Key or key value pairs to set
475 | * @return mixed
476 | */
477 | public function query($key = NULL, $array = TRUE)
478 | {
479 | if (is_null($key))
480 | {
481 | $query = $this->request()->query($key);
482 | foreach ($query as $name => $value)
483 | {
484 | if ($value == '')
485 | {
486 | return json_decode($name, $array);
487 | }
488 | }
489 | return $query;
490 | }
491 | return $this->request()->query($key);
492 | }
493 |
494 | /**
495 | * Gets HTTP body to the request or response. The body is
496 | * included after the header, separated by a single empty new line.
497 | *
498 | * @param string $content Content to set to the object
499 | * @param boolean $array Return an associative array, json only
500 | * @return mixed
501 | */
502 | public function body($type = NULL, $array = FALSE)
503 | {
504 | $body = $this->request()->body();
505 | $type = ($type) ? $type : $this->type();
506 | switch ($type)
507 | {
508 | case 'json':
509 | return json_decode($body, $array);
510 | break;
511 | case 'xml':
512 | return new SimpleXMLElement($body);
513 | break;
514 | default:
515 | return $body;
516 | break;
517 | }
518 | }
519 |
520 | /**
521 | * Set or get the request
522 | *
523 | * @param Request $request Request
524 | * @return Request
525 | * @return void
526 | */
527 | public function request(Request $request = NULL)
528 | {
529 | if ($request === NULL)
530 | {
531 | // Act as a getter
532 | return $this->_request;
533 | }
534 |
535 | // Act as a setter
536 | $this->_request = $request;
537 |
538 | return $this;
539 | }
540 |
541 | /**
542 | * Set or get the response
543 | *
544 | * @param Response $response Response
545 | * @return Response
546 | * @return void
547 | */
548 | public function response(Response $response = NULL)
549 | {
550 | if ($response === NULL)
551 | {
552 | // Act as a getter
553 | return $this->_response;
554 | }
555 |
556 | // Act as a setter
557 | $this->_response = $response;
558 |
559 | return $this;
560 | }
561 |
562 | /**
563 | * Set or get the method
564 | *
565 | * @param String $method Method
566 | * @return String
567 | * @return void
568 | */
569 | public function method($method = NULL)
570 | {
571 | if ($method === NULL)
572 | {
573 | // Act as a getter
574 | return $this->_method;
575 | }
576 |
577 | // Act as a setter
578 | $this->_method = $method;
579 |
580 | return $this;
581 | }
582 |
583 | /**
584 | * Set or get the model
585 | *
586 | * @param mixed $model Model
587 | * @return REST_Model
588 | * @return void
589 | */
590 | public function model($model = NULL)
591 | {
592 | if ($model === NULL)
593 | {
594 | // Act as a getter
595 | return $this->_model;
596 | }
597 |
598 | // Act as a setter
599 | if (is_object($model))
600 | {
601 | $this->_model = $model;
602 | }
603 | else
604 | {
605 | $class = Rest::class_name('model', $model);
606 | if (FALSE === class_exists($class))
607 | {
608 | // Send the "Model Not Found" response
609 | $this->send_code(404, array('Resource ":model" not found.', array(':model' => $model)));
610 | }
611 | $this->_model = new $class;
612 | }
613 |
614 | if (!$this->_model instanceof REST_Model)
615 | {
616 | // Send the Internal Server Error response
617 | $this->send_code(500, array('Class :class does not implement REST_Model.', array(':class' => get_class($this->_model))));
618 | }
619 |
620 | return $this;
621 | }
622 |
623 | /**
624 | * Set or get the controller
625 | *
626 | * @param REST_Controller $controller Controller
627 | * @return REST_Controller
628 | * @return void
629 | */
630 | public function controller(REST_Controller $controller = NULL)
631 | {
632 | if ($controller === NULL)
633 | {
634 | // Act as a getter
635 | return $this->_controller;
636 | }
637 |
638 | // Act as a setter
639 | $this->_controller = $controller;
640 |
641 | return $this;
642 | }
643 |
644 | /**
645 | * Set or get the content type
646 | *
647 | * @param Array $content Content
648 | * @return String
649 | * @return void
650 | */
651 | public function content(Array $types = NULL)
652 | {
653 | if ($types === NULL)
654 | {
655 | // Act as a getter
656 | return $this->_content;
657 | }
658 |
659 | $request = $this->request();
660 |
661 | // Act as a setter
662 | $this->_content = $request->headers()->preferred_accept($types);
663 |
664 | if (FALSE === $this->_content)
665 | {
666 | $this->send_code(406, array('Supplied Accept types: :accept not supported. Supported types: :types',
667 | array(
668 | ':accept' => $request->headers('Accept'),
669 | ':types' => implode(', ', $types)
670 | )));
671 | }
672 |
673 | return $this;
674 | }
675 |
676 | /**
677 | * Set or get the charset
678 | *
679 | * @param array $charsets
680 | * @return REST_Core
681 | */
682 | public function charset(Array $charsets = NULL)
683 | {
684 | if ($charsets === NULL)
685 | {
686 | // Act as a getter
687 | return $this->_charset;
688 | }
689 |
690 | $request = $this->request();
691 |
692 | // Act as a setter
693 | $this->_charset = $request->headers()->preferred_charset($charsets);
694 |
695 | if (FALSE === $this->_charset)
696 | {
697 | $this->send_code(406, array('Supplied Accept-Charset: :accept not supported. Supported types: :types',
698 | array(
699 | ':accept' => $request->headers('Accept-Charset'),
700 | ':types' => implode(', ', $charsets)
701 | )));
702 | }
703 |
704 | return $this;
705 | }
706 |
707 | /**
708 | * Set or get the language
709 | *
710 | * @param array $charsets
711 | * @return REST_Core
712 | */
713 | public function language(Array $languages = NULL)
714 | {
715 | if ($languages === NULL)
716 | {
717 | // Act as a getter
718 | return $this->_language;
719 | }
720 |
721 | $request = $this->request();
722 |
723 | // Act as a setter
724 | $this->_language = $request->headers()->preferred_language($languages);
725 |
726 | if (FALSE === $this->_language)
727 | {
728 | $this->send_code(406, array('Supplied Accept-Language: :accept not supported. Supported languages: :types',
729 | array(
730 | ':accept' => $request->headers('Accept-Language'),
731 | ':types' => implode(', ', $languages)
732 | )));
733 | }
734 |
735 | return $this;
736 | }
737 |
738 | /**
739 | * Allows setting the method from the X-HTTP-METHOD-OVERRIDE header
740 | *
741 | * @param boolean $override
742 | * @return Rest
743 | */
744 | public function method_override($override = FALSE)
745 | {
746 | $request = $this->request();
747 | $method = $request->headers('X-HTTP-METHOD-OVERRIDE');
748 | $method = (isset($method) AND $override) ? $method : $request->method();
749 | $this->method($method);
750 | return $this;
751 | }
752 |
753 | /**
754 | * Allows setting the content type from the Request param content_type
755 | *
756 | * @param boolean $override
757 | * @return Rest
758 | */
759 | public function content_override($override = FALSE)
760 | {
761 | $types = $this->accept();
762 |
763 | if (FALSE === $override)
764 | {
765 | $this->content(Arr::get($this->_config, 'types', $types));
766 | return $this;
767 | }
768 |
769 | $content = $this->request()->param('content_type', FALSE);
770 | if (FALSE === $content)
771 | {
772 | // No content_type param used...
773 | return $this;
774 | }
775 |
776 | $key = array_search($content, Rest::$_types);
777 | if (FALSE === $key)
778 | {
779 | $this->send_code(406, array('Supplied Override Type: :accept not supported. Supported types: :types',
780 | array(
781 | ':accept' => $content,
782 | ':types' => implode(', ', $types)
783 | )));
784 | }
785 |
786 | if (!in_array($key, $types))
787 | {
788 | $this->send_code(406, array('Supplied Content Type: :accept not supported. Supported types: :types',
789 | array(
790 | ':accept' => $key,
791 | ':types' => implode(', ', $types)
792 | )));
793 | }
794 |
795 | $this->_content = $key;
796 | return $this;
797 | }
798 |
799 | /**
800 | * Cross-Origin Resource Sharing Helper
801 | *
802 | * @param array $values
803 | * @return Rest
804 | */
805 | public function cors(Array $values = array())
806 | {
807 | $cors = self::$_cors;
808 | $cors['methods'] = $this->allowed();
809 | $cors = Arr::merge($cors, $values);
810 |
811 | $response = $this->response();
812 |
813 | if (isset($cors['origin']))
814 | {
815 | $response->headers('Access-Control-Allow-Origin', self::join($cors['origin']));
816 | }
817 |
818 | if (isset($cors['methods']))
819 | {
820 | $response->headers('Access-Control-Allow-Methods', self::join($cors['methods']));
821 | }
822 |
823 | if (isset($cors['headers']))
824 | {
825 | $response->headers('Access-Control-Allow-Headers', self::join($cors['headers']));
826 | }
827 |
828 | if (isset($cors['expose']))
829 | {
830 | $response->headers('Access-Control-Expose-Headers', self::join($cors['expose']));
831 | }
832 |
833 | if (isset($cors['creds']))
834 | {
835 | $response->headers('Access-Control-Allow-Credentials', $cors['creds']);
836 | }
837 |
838 | if (isset($cors['age']))
839 | {
840 | $response->headers('Access-Control-Max-Age', $cors['age']);
841 | }
842 |
843 | return $this;
844 | }
845 |
846 | /**
847 | * Sends the created response (POST)
848 | *
849 | * @param type $id
850 | * @param type $code
851 | */
852 | public function send_created($id, $code = 201)
853 | {
854 | $request = $this->request();
855 | $url = array(
856 | $request->directory(),
857 | $request->controller(),
858 | strtolower(Rest::common_name('model', $this->_model)),
859 | $id
860 | );
861 | $url = URL::site(implode('/', $url), TRUE, Kohana::$index_file);
862 | $this->request()->redirect($url, $code);
863 | }
864 |
865 | /**
866 | * Sends the response code and exits the application
867 | *
868 | * @param type $code
869 | * @param mixed $body
870 | */
871 | public function send_code($code = 204, $body = NULL)
872 | {
873 | // Echo response and exit if we aren't using exceptions
874 | if (FALSE === Arr::get($this->_config, 'exceptions', FALSE))
875 | {
876 | if (is_array($body))
877 | {
878 | list($str, $pairs) = $body;
879 | $body = strtr($str, $pairs);
880 | }
881 | echo $this->response()
882 | ->status($code)
883 | ->send_headers()
884 | ->body($body);
885 |
886 | // Stop execution
887 | exit;
888 | }
889 | else
890 | {
891 | // See if special exception class exists
892 | $class = 'Http_Exception_'.$code;
893 | if (class_exists($class))
894 | {
895 | if (is_array($body))
896 | {
897 | list($str, $pairs) = $body;
898 | throw new $class($str, $pairs);
899 | }
900 | else
901 | {
902 | throw new $class($body);
903 | }
904 | }
905 | else
906 | {
907 | if (is_array($body))
908 | {
909 | list($str, $pairs) = $body;
910 | throw new HTTP_Exception($str, $pairs, $code);
911 | }
912 | else
913 | {
914 | throw new HTTP_Exception($body, NULL, $code);
915 | }
916 | }
917 | }
918 | }
919 | }
920 | // End Rest
--------------------------------------------------------------------------------