Added the zend framework 2 library, the path is specified in line no.26 in zend_modul...
[openemr.git] / interface / modules / zend_modules / library / Zend / Mvc / Controller / AbstractRestfulController.php
blobf868594e43e9bd1ba7c6332e1b8632a39fd67abb
1 <?php
2 /**
3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
8 */
9 namespace Zend\Mvc\Controller;
11 use Zend\Http\Request as HttpRequest;
12 use Zend\Json\Json;
13 use Zend\Mvc\Exception;
14 use Zend\Mvc\MvcEvent;
15 use Zend\Stdlib\RequestInterface as Request;
16 use Zend\Stdlib\ResponseInterface as Response;
18 /**
19 * Abstract RESTful controller
21 abstract class AbstractRestfulController extends AbstractController
24 const CONTENT_TYPE_JSON = 'json';
26 /**
27 * @var string
29 protected $eventIdentifier = __CLASS__;
31 /**
32 * @var array
34 protected $contentTypes = array(
35 self::CONTENT_TYPE_JSON => array(
36 'application/hal+json',
37 'application/json'
41 /**
42 * Name of request or query parameter containing identifier
44 * @var string
46 protected $identifierName = 'id';
48 /**
49 * @var int From Zend\Json\Json
51 protected $jsonDecodeType = Json::TYPE_ARRAY;
53 /**
54 * Map of custom HTTP methods and their handlers
56 * @var array
58 protected $customHttpMethodsMap = array();
60 /**
61 * Set the route match/query parameter name containing the identifier
63 * @param string $name
64 * @return self
66 public function setIdentifierName($name)
68 $this->identifierName = (string) $name;
69 return $this;
72 /**
73 * Retrieve the route match/query parameter name containing the identifier
75 * @return string
77 public function getIdentifierName()
79 return $this->identifierName;
82 /**
83 * Create a new resource
85 * @param mixed $data
86 * @return mixed
88 public function create($data)
90 $this->response->setStatusCode(405);
92 return array(
93 'content' => 'Method Not Allowed'
97 /**
98 * Delete an existing resource
100 * @param mixed $id
101 * @return mixed
103 public function delete($id)
105 $this->response->setStatusCode(405);
107 return array(
108 'content' => 'Method Not Allowed'
113 * Delete the entire resource collection
115 * Not marked as abstract, as that would introduce a BC break
116 * (introduced in 2.1.0); instead, raises an exception if not implemented.
118 * @return mixed
120 public function deleteList()
122 $this->response->setStatusCode(405);
124 return array(
125 'content' => 'Method Not Allowed'
130 * Return single resource
132 * @param mixed $id
133 * @return mixed
135 public function get($id)
137 $this->response->setStatusCode(405);
139 return array(
140 'content' => 'Method Not Allowed'
145 * Return list of resources
147 * @return mixed
149 public function getList()
151 $this->response->setStatusCode(405);
153 return array(
154 'content' => 'Method Not Allowed'
159 * Retrieve HEAD metadata for the resource
161 * Not marked as abstract, as that would introduce a BC break
162 * (introduced in 2.1.0); instead, raises an exception if not implemented.
164 * @param null|mixed $id
165 * @return mixed
167 public function head($id = null)
169 $this->response->setStatusCode(405);
171 return array(
172 'content' => 'Method Not Allowed'
177 * Respond to the OPTIONS method
179 * Typically, set the Allow header with allowed HTTP methods, and
180 * return the response.
182 * Not marked as abstract, as that would introduce a BC break
183 * (introduced in 2.1.0); instead, raises an exception if not implemented.
185 * @return mixed
187 public function options()
189 $this->response->setStatusCode(405);
191 return array(
192 'content' => 'Method Not Allowed'
197 * Respond to the PATCH method
199 * Not marked as abstract, as that would introduce a BC break
200 * (introduced in 2.1.0); instead, raises an exception if not implemented.
202 * @param $id
203 * @param $data
205 public function patch($id, $data)
207 $this->response->setStatusCode(405);
209 return array(
210 'content' => 'Method Not Allowed'
215 * Replace an entire resource collection
217 * Not marked as abstract, as that would introduce a BC break
218 * (introduced in 2.1.0); instead, raises an exception if not implemented.
220 * @param mixed $data
221 * @return mixed
223 public function replaceList($data)
225 $this->response->setStatusCode(405);
227 return array(
228 'content' => 'Method Not Allowed'
233 * Modify a resource collection withou completely replacing it
235 * Not marked as abstract, as that would introduce a BC break
236 * (introduced in 2.2.0); instead, raises an exception if not implemented.
238 * @param mixed $data
239 * @return mixed
241 public function patchList($data)
243 $this->response->setStatusCode(405);
245 return array(
246 'content' => 'Method Not Allowed'
251 * Update an existing resource
253 * @param mixed $id
254 * @param mixed $data
255 * @return mixed
257 public function update($id, $data)
259 $this->response->setStatusCode(405);
261 return array(
262 'content' => 'Method Not Allowed'
267 * Basic functionality for when a page is not available
269 * @return array
271 public function notFoundAction()
273 $this->response->setStatusCode(404);
275 return array(
276 'content' => 'Page not found'
281 * Dispatch a request
283 * If the route match includes an "action" key, then this acts basically like
284 * a standard action controller. Otherwise, it introspects the HTTP method
285 * to determine how to handle the request, and which method to delegate to.
287 * @events dispatch.pre, dispatch.post
288 * @param Request $request
289 * @param null|Response $response
290 * @return mixed|Response
291 * @throws Exception\InvalidArgumentException
293 public function dispatch(Request $request, Response $response = null)
295 if (! $request instanceof HttpRequest) {
296 throw new Exception\InvalidArgumentException(
297 'Expected an HTTP request');
300 return parent::dispatch($request, $response);
304 * Handle the request
306 * @todo try-catch in "patch" for patchList should be removed in the future
307 * @param MvcEvent $e
308 * @return mixed
309 * @throws Exception\DomainException if no route matches in event or invalid HTTP method
311 public function onDispatch(MvcEvent $e)
313 $routeMatch = $e->getRouteMatch();
314 if (! $routeMatch) {
316 * @todo Determine requirements for when route match is missing.
317 * Potentially allow pulling directly from request metadata?
319 throw new Exception\DomainException(
320 'Missing route matches; unsure how to retrieve action');
323 $request = $e->getRequest();
325 // Was an "action" requested?
326 $action = $routeMatch->getParam('action', false);
327 if ($action) {
328 // Handle arbitrary methods, ending in Action
329 $method = static::getMethodFromAction($action);
330 if (! method_exists($this, $method)) {
331 $method = 'notFoundAction';
333 $return = $this->$method();
334 $e->setResult($return);
335 return $return;
338 // RESTful methods
339 $method = strtolower($request->getMethod());
340 switch ($method) {
341 // Custom HTTP methods (or custom overrides for standard methods)
342 case (isset($this->customHttpMethodsMap[$method])):
343 $callable = $this->customHttpMethodsMap[$method];
344 $action = $method;
345 $return = call_user_func($callable, $e);
346 break;
347 // DELETE
348 case 'delete':
349 $id = $this->getIdentifier($routeMatch, $request);
350 if ($id !== false) {
351 $action = 'delete';
352 $return = $this->delete($id);
353 break;
356 $action = 'deleteList';
357 $return = $this->deleteList();
358 break;
359 // GET
360 case 'get':
361 $id = $this->getIdentifier($routeMatch, $request);
362 if ($id !== false) {
363 $action = 'get';
364 $return = $this->get($id);
365 break;
367 $action = 'getList';
368 $return = $this->getList();
369 break;
370 // HEAD
371 case 'head':
372 $id = $this->getIdentifier($routeMatch, $request);
373 if ($id === false) {
374 $id = null;
376 $action = 'head';
377 $this->head($id);
378 $response = $e->getResponse();
379 $response->setContent('');
380 $return = $response;
381 break;
382 // OPTIONS
383 case 'options':
384 $action = 'options';
385 $this->options();
386 $return = $e->getResponse();
387 break;
388 // PATCH
389 case 'patch':
390 $id = $this->getIdentifier($routeMatch, $request);
391 $data = $this->processBodyContent($request);
393 if ($id !== false) {
394 $action = 'patch';
395 $return = $this->patch($id, $data);
396 break;
399 // TODO: This try-catch should be removed in the future, but it
400 // will create a BC break for pre-2.2.0 apps that expect a 405
401 // instead of going to patchList
402 try {
403 $action = 'patchList';
404 $return = $this->patchList($data);
405 } catch (Exception\RuntimeException $ex) {
406 $response = $e->getResponse();
407 $response->setStatusCode(405);
408 return $response;
410 break;
411 // POST
412 case 'post':
413 $action = 'create';
414 $return = $this->processPostData($request);
415 break;
416 // PUT
417 case 'put':
418 $id = $this->getIdentifier($routeMatch, $request);
419 $data = $this->processBodyContent($request);
421 if ($id !== false) {
422 $action = 'update';
423 $return = $this->update($id, $data);
424 break;
427 $action = 'replaceList';
428 $return = $this->replaceList($data);
429 break;
430 // All others...
431 default:
432 $response = $e->getResponse();
433 $response->setStatusCode(405);
434 return $response;
437 $routeMatch->setParam('action', $action);
438 $e->setResult($return);
439 return $return;
443 * Process post data and call create
445 * @param Request $request
446 * @return mixed
448 public function processPostData(Request $request)
450 if ($this->requestHasContentType($request, self::CONTENT_TYPE_JSON)) {
451 $data = Json::decode($request->getContent(), $this->jsonDecodeType);
452 } else {
453 $data = $request->getPost()->toArray();
456 return $this->create($data);
460 * Check if request has certain content type
462 * @param Request $request
463 * @param string|null $contentType
464 * @return bool
466 public function requestHasContentType(Request $request, $contentType = '')
468 /** @var $headerContentType \Zend\Http\Header\ContentType */
469 $headerContentType = $request->getHeaders()->get('content-type');
470 if (!$headerContentType) {
471 return false;
474 $requestedContentType = $headerContentType->getFieldValue();
475 if (strstr($requestedContentType, ';')) {
476 $headerData = explode(';', $requestedContentType);
477 $requestedContentType = array_shift($headerData);
479 $requestedContentType = trim($requestedContentType);
480 if (array_key_exists($contentType, $this->contentTypes)) {
481 foreach ($this->contentTypes[$contentType] as $contentTypeValue) {
482 if (stripos($contentTypeValue, $requestedContentType) === 0) {
483 return true;
488 return false;
492 * Register a handler for a custom HTTP method
494 * This method allows you to handle arbitrary HTTP method types, mapping
495 * them to callables. Typically, these will be methods of the controller
496 * instance: e.g., array($this, 'foobar'). The typical place to register
497 * these is in your constructor.
499 * Additionally, as this map is checked prior to testing the standard HTTP
500 * methods, this is a way to override what methods will handle the standard
501 * HTTP methods. However, if you do this, you will have to retrieve the
502 * identifier and any request content manually.
504 * Callbacks will be passed the current MvcEvent instance.
506 * To retrieve the identifier, you can use "$id =
507 * $this->getIdentifier($routeMatch, $request)",
508 * passing the appropriate objects.
510 * To retrieve the body content data, use "$data = $this->processBodyContent($request)";
511 * that method will return a string, array, or, in the case of JSON, an object.
513 * @param string $method
514 * @param Callable $handler
515 * @return AbstractRestfulController
517 public function addHttpMethodHandler($method, /* Callable */ $handler)
519 if (!is_callable($handler)) {
520 throw new Exception\InvalidArgumentException(sprintf(
521 'Invalid HTTP method handler: must be a callable; received "%s"',
522 (is_object($handler) ? get_class($handler) : gettype($handler))
525 $method = strtolower($method);
526 $this->customHttpMethodsMap[$method] = $handler;
527 return $this;
531 * Retrieve the identifier, if any
533 * Attempts to see if an identifier was passed in either the URI or the
534 * query string, returning it if found. Otherwise, returns a boolean false.
536 * @param \Zend\Mvc\Router\RouteMatch $routeMatch
537 * @param Request $request
538 * @return false|mixed
540 protected function getIdentifier($routeMatch, $request)
542 $identifier = $this->getIdentifierName();
543 $id = $routeMatch->getParam($identifier, false);
544 if ($id !== false) {
545 return $id;
548 $id = $request->getQuery()->get($identifier, false);
549 if ($id !== false) {
550 return $id;
553 return false;
557 * Process the raw body content
559 * If the content-type indicates a JSON payload, the payload is immediately
560 * decoded and the data returned. Otherwise, the data is passed to
561 * parse_str(). If that function returns a single-member array with a key
562 * of "0", the method assumes that we have non-urlencoded content and
563 * returns the raw content; otherwise, the array created is returned.
565 * @param mixed $request
566 * @return object|string|array
568 protected function processBodyContent($request)
570 $content = $request->getContent();
572 // JSON content? decode and return it.
573 if ($this->requestHasContentType($request, self::CONTENT_TYPE_JSON)) {
574 return Json::decode($content, $this->jsonDecodeType);
577 parse_str($content, $parsedParams);
579 // If parse_str fails to decode, or we have a single element with key
580 // 0, return the raw content.
581 if (!is_array($parsedParams)
582 || (1 == count($parsedParams) && isset($parsedParams[0]))
584 return $content;
587 return $parsedParams;