Пример #1
0
 /**
  * @covers \Cougar\RestService\RestService::bindFromObject
  * @covers \Cougar\RestService\RestService::handleRequest
  */
 public function testHTML()
 {
     $_SERVER["SERVER_PROTOCOL"] = "HTTP/1.1";
     $_SERVER["REQUEST_METHOD"] = "GET";
     $_SERVER["REQUEST_URI"] = "/get/SimpleCase";
     $_SERVER["PHP_SELF"] = "/request_handler";
     $_SERVER["HTTP_HOST"] = "localhost";
     $_SERVER["HTTP_ACCEPT"] = "text/html";
     $object = new AnnotatedRestServiceGetTests();
     $this->expectOutputString(Xml::toXml($object->simpleCase())->asXML());
     $service = new AnnotatedRestService(new Security());
     $service->bindFromObject($object);
     $service->handleRequest();
 }
Пример #2
0
 /**
  * @covers \Cougar\Util\Xml::toObject
  * @depends testToXmlWithComplexObject
  */
 public function testToObjectWithComplexObject()
 {
     $object = new \stdClass();
     $object->property1 = "value1";
     $object->property2 = "value2";
     $object->property3 = "value3";
     $object->property4 = "value4";
     $object->subClass = new \stdClass();
     $object->subClass->subProperty1 = "subvalue1";
     $object->subClass->subProperty2 = "subvalue2";
     $object->array = array("a", "b", "c", "d", "e");
     $object->jaggedArray = array("a", "b", "c", "d", "e", array("x", "y", "z"));
     $object->associativeArray = $array = array("a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5);
     $object->jaggedAssociativeArray = array("a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5, "extra" => array("x" => "foo", "y" => "bar", "z" => "baz"));
     $xml = Xml::toXml($object);
     $new_object = Xml::toObject($xml);
     $object->array = json_decode(json_encode($object->array));
     $object->jaggedArray = json_decode(json_encode($object->jaggedArray));
     $object->associativeArray = json_decode(json_encode($object->associativeArray));
     $object->jaggedAssociativeArray = json_decode(json_encode($object->jaggedAssociativeArray));
     $this->assertEquals($object, $new_object);
 }
Пример #3
0
 /**
  * Returns the body of the request, optionally parsing it as a specified
  * type of object. These are:
  *
  *   XML    - Parse the body as XML and return as a SimpleXML object
  *   OBJECT - Parse the body as a JSON, XML or PHP serialized object and
  *            return as an object
  *   ARRAY  - Parse the body as a JSON or XML object and return as an assoc.
  *            array
  *   PHP    - Parse the body as serialized PHP data
  *
  * If parsing fails the call will throw a BadRequestException.
  *
  * If no parse type is specified, the body will be returned as a string.
  *
  * @history
  * 2013.09.30:
  *   (AT)  Initial release
  * 2014.08:06:
  *   (AT)  Allow conversion of XML to object or array, or PHP to object
  *
  * @version 2014.08.06
  * @author (AT) Alberto Trevino, Brigham Young Univ. <*****@*****.**>
  *
  * @param string $parse_type xml|object|array|php
  * @throws \Cougar\Exceptions\BadRequestException
  * @return mixed Body
  */
 public function body($parse_type = null)
 {
     if ($this->body === null) {
         # Get the body
         if ($this->__testMode) {
             # In test mode, read the body from the $_BODY variable
             global $_BODY;
             $this->body = trim($_BODY);
         } else {
             $this->body = trim(file_get_contents("php://input"));
         }
     }
     # See if we will be parsing the data
     if ($this->body) {
         switch (strtolower($parse_type)) {
             case "xml":
                 return new \SimpleXMLElement($this->body);
                 break;
             case "object":
                 try {
                     return Xml::toObject(new \SimpleXMLElement($this->body));
                 } catch (\Exception $e) {
                     try {
                         return unserialize($this->body);
                     } catch (\Exception $e) {
                         $object = json_decode($this->body);
                         if ($object === null) {
                             throw new BadRequestException("Body must be a valid JSON, XML or " . "serialized PHP object");
                         }
                         return $object;
                     }
                 }
                 break;
             case "array":
                 try {
                     return Xml::toArray(new \SimpleXMLElement($this->body));
                 } catch (\Exception $e) {
                     $object = json_decode($this->body, true);
                     if ($object === null) {
                         throw new BadRequestException("Body must be a valid JSON or XML object");
                     }
                     return $object;
                 }
                 break;
             case "php":
                 return unserialize($this->body);
                 break;
             default:
                 return $this->body;
                 break;
         }
     } else {
         return $this->body;
     }
 }
Пример #4
0
 /**
  * @covers \Cougar\RestService\RestService::body
  */
 public function testBodyXmlAsObject()
 {
     global $_BODY;
     $xml = new \SimpleXMLElement("<unit_test/>");
     $xml->addChild("key", "value");
     $_BODY = $xml->asXML();
     $this->assertEquals(Xml::toObject($xml), $this->object->body("object"));
 }
Пример #5
0
 /**
  * Handles the incoming request with one of the bound objects. This is a
  * terminal call, meaning that the proper method will be called and will
  * automatically send the data to the browser. If an error occurs, it will
  * be caught and sent to the browser.
  *
  * @history
  * 2013.09.30:
  *   (AT)  Initial release
  * 2013.11.21:
  *   (AT)  Add __toHtml() and __toXml() support when converting method
  *         response to HTML or XML
  * 2014.02.26:
  *   (AT)  Fix bug where continue would only exit case statement rather than
  *         going to the next binding when evaluating content-type
  * 2014.03.12:
  *   (AT)  Sort bindings by number of parameters, number of literal
  *         parameters and finally by pattern; improves binding accuracy
  * 2014.03.14:
  *   (AT)  Remove single trailing slashes from the URI path
  *   (AT)  Use the backtick (`) as the regex delimiter to allow the colon
  *         to be used in regex classes
  * 2014.03.24:
  *   (AT)  Access path and method variables directly from the object
  * 2014.08.05:
  *   (AT)  Handle JSON, XML and PHP Returns types properly
  *
  * @version 2014.08.05
  * @author (AT) Alberto Trevino, Brigham Young Univ. <*****@*****.**>
  *
  * @throws \Cougar\Exceptions\Exception
  * @throws \Cougar\Exceptions\AuthenticationRequiredException
  * @throws \Cougar\Exceptions\BadRequestException
  * @throws \Cougar\Exceptions\MethodNotAllowedException
  * @throws \Cougar\Exceptions\NotAcceptableException
  */
 public function handleRequest()
 {
     # Remove trailing slash from the path
     if (mb_strlen($this->path) > 1) {
         $this->path = preg_replace(':/$:', "", $this->path);
     }
     # Sort the bindings from most specific to least specific
     $path_argument_counts = array();
     $literal_path_argument_counts = array();
     $patterns = array();
     foreach ($this->bindings as $pattern => $method_bindings) {
         $path_argument_counts[$pattern] = $method_bindings[0]->pathArgumentCount;
         $literal_path_argument_counts[$pattern] = $method_bindings[0]->literalPathArgumentCount;
         $patterns[$pattern] = $pattern;
     }
     array_multisort($path_argument_counts, SORT_DESC, $literal_path_argument_counts, SORT_DESC, $patterns, SORT_NATURAL, $this->bindings);
     # Go through the bindings and find those which match the URI pattern and
     # HTTP method
     $method_list = array();
     $http_method_mismatch = false;
     foreach ($this->bindings as $pattern => $method_bindings) {
         # See if the pattern matches
         if (preg_match("`" . $pattern . "`u", $this->path)) {
             # See if we are dealing with an OPTIONS request
             if ($this->method == "OPTIONS") {
                 /* Either we need to continue to iterate through all methods
                  * or we need to gather all the information and generate the
                  * response after the loop. Since this was originally
                  * written for CORS support, we will simply return the list
                  * of all methods for now. */
                 /*
                 # Get the list of methods the end point will support
                 $methods = array();
                 foreach($method_bindings as $binding)
                 {
                     foreach($binding->http_methods as $method)
                     {
                         if (! in_array($method, $methods))
                         {
                             $methods[] = $method;
                         }
                     }
                 }
                 */
                 # Return all basic methods for now
                 $methods = array("GET", "POST", "PUT", "DELETE");
                 # Return the response
                 $this->sendResponse(204, null, array("Allow" => implode(", ", $methods)));
             } else {
                 # Go through each binding and get potential candidates
                 foreach ($method_bindings as $binding) {
                     # See if this binding can handle this method
                     if (!in_array($this->method, $binding->http_methods)) {
                         $http_method_mismatch = true;
                         continue;
                     }
                     # See if there is a specific mimetype this method
                     # accepts
                     switch ($binding->accepts) {
                         case "json":
                             if ($this->header("Content-type") != "application/json") {
                                 # This binding doesn't accept this type;
                                 # go to the next binding
                                 continue 2;
                             }
                             break;
                         case "xml":
                             if ($this->header("Content-type") != "application/xml" && $this->header("Content-type") != "text/xml") {
                                 # This binding doesn't accept this type;
                                 # go to the next binding
                                 continue 2;
                             }
                             break;
                         case "php":
                             if ($this->header("Content-type") != "application/vnd.php.serialized") {
                                 # This binding doesn't accept this type;
                                 # go to the next one
                                 continue 2;
                             }
                             break;
                         case "":
                             # No binding specified; allow binding
                             break;
                         default:
                             # Check the content type directly
                             if (!$binding->accepts != $this->header("Content-type")) {
                                 # This binding doesn't accept this type;
                                 # go to the next binding
                                 continue 2;
                             }
                             break;
                     }
                     $method_list[] = $binding;
                 }
             }
         }
     }
     # See if we have any methods that can respond to our request
     if (count($method_list) == 0) {
         # See if we had methods that matched the pattern but couldn't
         # support the HTTP method
         if ($http_method_mismatch) {
             throw new MethodNotAllowedException("The resource does not support " . $this->method . " operations");
         } else {
             # Return a 400 error
             throw new BadRequestException("Your request could not be mapped to a known resource");
         }
     }
     # Go through the potential method bindings and extract the response
     # type; if none is defined, do JSON, XML, HTML
     # TODO: add version information once version methodology is defined
     $response_types = array();
     foreach ($method_list as $binding) {
         if ($binding->returns) {
             switch ($binding->returns) {
                 case "json":
                     $response_types[] = "application/json";
                     break;
                 case "xml":
                     $response_types[] = "application/xml";
                     $response_types[] = "text/xml";
                     break;
                 case "php":
                     $response_types[] = "application/vnd.php.serialized";
                     break;
                 default:
                     $response_types[] = $binding->returns;
                     break;
             }
         } else {
             if (!in_array("application/json", $response_types)) {
                 $response_types[] = "application/json";
             }
             if (!in_array("application/vnd.php.serialized", $response_types)) {
                 $response_types[] = "application/vnd.php.serialized";
             }
             if (!in_array("application/xml", $response_types)) {
                 $response_types[] = "application/xml";
             }
             if (!in_array("text/html", $response_types)) {
                 $response_types[] = "text/html";
             }
         }
     }
     # Negotiate the response
     $output_response_types = $this->negotiateResponseType($response_types);
     # Find the binding that best fits
     # TODO improve detection
     $binding = null;
     foreach ($output_response_types as $response_type) {
         foreach ($method_list as $potential_binding) {
             if ($potential_binding->returns == $response_type) {
                 $binding = $potential_binding;
                 break 2;
             } else {
                 if ($potential_binding->returns == "json" && $response_type == "application/json") {
                     $binding = $potential_binding;
                     break 2;
                 } else {
                     if ($potential_binding->returns == "xml" && $response_type == "application/xml") {
                         $binding = $potential_binding;
                         break 2;
                     } else {
                         if ($potential_binding->returns == "xml" && $response_type == "text/xml") {
                             $binding = $potential_binding;
                             break 2;
                         } else {
                             if (!$potential_binding->returns) {
                                 $binding = $potential_binding;
                                 break 2;
                             }
                         }
                     }
                 }
             }
         }
     }
     # If we don't have a binding, send a NotAcceptable exception
     if (!$binding) {
         throw new NotAcceptableException("The requested resource cannot be represented by any " . "of the acceptable representations requested by the client");
     }
     # If we've made it this far, we have found our optimal binding
     # Get the object associated with the binding
     if (!array_key_exists($binding->object, $this->objects)) {
         throw new Exception("Could not find object!");
     }
     $object = $this->objects[$binding->object];
     $r_object = new \ReflectionClass($object);
     # Get the method associated with the binding
     if (!$r_object->hasMethod($binding->method)) {
         throw new Exception("Could not find method!");
     }
     $r_method = $r_object->getMethod($binding->method);
     # Assemble the method parameters into an array
     $params = array();
     foreach ($r_method->getParameters() as $r_param) {
         # Get the default value of the parameter (if it has one)
         $default_param_value = null;
         if ($r_param->isOptional()) {
             $default_param_value = $r_param->getDefaultValue();
         }
         # See if we have a binding for this parameter
         if (array_key_exists($r_param->name, $binding->parameters)) {
             # Get the parameter information
             $param_info = $binding->parameters[$r_param->name];
             # See where the value is coming from
             switch ($param_info->source) {
                 case "URI":
                     # See if we need to make an array with the remaining
                     # parameters
                     if ($param_info->array) {
                         $params[] = array_slice($this->uri, $param_info->index);
                     } else {
                         $params[] = $this->uriValue($param_info->index, $param_info->type, $default_param_value);
                     }
                     break;
                 case "GET":
                     if ($param_info->array) {
                         $params[] = $_GET;
                     } else {
                         $params[] = $this->getValue($param_info->index, $param_info->type, $default_param_value);
                     }
                     break;
                 case "POST":
                     if ($param_info->array) {
                         $params[] = $_POST;
                     } else {
                         $params[] = $this->postValue($param_info->index, $param_info->type, $default_param_value);
                     }
                     break;
                 case "BODY":
                     $params[] = $this->body($param_info->type);
                     break;
                 case "QUERY":
                     $params[] = $this->getQuery();
                     break;
                 case "IDENTITY":
                     break;
                 default:
                     throw new Exception("Invalid parameter source");
             }
         } else {
             # We don't have a binding; pass the default value
             $params[] = $default_param_value;
         }
     }
     # See if the call requires authentication
     switch ($binding->authentication) {
         case "required":
             $auth_success = $this->security->authenticate();
             if (!$auth_success) {
                 throw new AuthenticationRequiredException();
             }
             break;
         case "optional":
             $this->security->authenticate();
             break;
         default:
             # No need to do anything
             break;
     }
     # Call the method
     $data = call_user_func_array(array($object, $binding->method), $params);
     # Send the data in the appropriate data type
     if ($data !== null) {
         switch ($response_type) {
             case "application/json":
                 $this->sendResponse(200, json_encode($data), array(), $response_type);
                 break;
             case "application/vnd.php.serialized":
                 $this->sendResponse(200, serialize($data), array(), $response_type);
                 break;
             case "application/xml":
             case "text/xml":
                 # TODO: Implement XSD
                 # See if we have an object
                 if (is_object($data)) {
                     # See if this is a SimpleXMLElement
                     if ($data instanceof \SimpleXMLElement) {
                         $xml = $data->asXML();
                     } else {
                         if (method_exists($data, "__toXml")) {
                             $xml = $data->__toXml();
                         } else {
                             $xml = Xml::toXml($data, $binding->xmlRootElement, $binding->xmlObjectName, $binding->xmlObjectList);
                         }
                     }
                 } else {
                     # Convert data to XML
                     $xml = Xml::toXml($data, $binding->xmlRootElement, $binding->xmlObjectName, $binding->xmlObjectList);
                 }
                 if (is_object($xml)) {
                     if ($xml instanceof \SimpleXMLElement) {
                         $xml = $xml->asXML();
                     }
                 }
                 # Send the response
                 $this->sendResponse(200, $xml, array(), $response_type);
                 break;
             case "text/html":
                 # See if this is an object
                 $html = null;
                 $xml = null;
                 if (is_object($data)) {
                     # See if object has __toHtml() method
                     if (method_exists($data, "__toHtml")) {
                         $html = $data->__toHtml();
                     } else {
                         if ($data instanceof \SimpleXMLElement) {
                             $xml = $data->asXML();
                         } else {
                             if (method_exists($data, "__toXml")) {
                                 $xml = $data->__toXml();
                             }
                         }
                     }
                 }
                 if ($html === null && $xml === null) {
                     # Convert data to XML
                     $xml = Xml::toXml($data, $binding->xmlRootElement, $binding->xmlObjectName, $binding->xmlObjectList);
                     # See if we have an XSL transform
                     if ($binding->xsl) {
                         $xsl = new \SimpleXMLElement($binding["xsl"]);
                         $xslt = new \XSLTProcessor();
                         $xslt->importStylesheet($xsl);
                         $html = $xslt->transformToXml($xml);
                     }
                 }
                 # See which kind of response we have
                 if ($html !== null) {
                     $this->sendResponse(200, $html, array(), $response_type);
                 } else {
                     if (is_object($xml)) {
                         if ($xml instanceof \SimpleXMLElement) {
                             $xml = $xml->asXML();
                         }
                     }
                     $this->sendResponse(200, $xml, array(), "text/xml");
                 }
                 break;
             default:
                 $this->sendResponse(200, $data, array(), $response_type);
         }
     } else {
         $this->sendResponse(204, null, array(), $response_type);
     }
 }