Exemplo n.º 1
0
 /**
  * Extracts the resource from a method's method_info
  *
  * @history
  * 2014.04.29:
  *   (AT)  Initial implementation
  * 2014.05.29:
  *   (AT)  Added short description to the resource
  *
  * @version 2014.05.29
  * @author (AT) Alberto Trevino, Brigham Young Univ. <*****@*****.**>
  *
  * @param array $method_info
  *   Method information
  * @return \Cougar\RestService\Models\Resource Resource object
  */
 protected function extractResource(array $method_info)
 {
     // Initialize the resource variable
     $resource = null;
     if ($method_info["resource"]) {
         // See if we have a class
         if ($method_info["resource"]["class"]) {
             // Create the resource
             $resource = new Resource();
             $resource->class = $method_info["resource"]["class"];
             // See if we have an alternate name
             if ($method_info["resource"]["name"]) {
                 // Set the alternate resource name
                 $resource->name = $method_info["resource"]["name"];
             } else {
                 // Set the resource name from the class name
                 $resource->name = $this->generateResourceName($method_info["resource"]["class"]);
             }
         }
     }
     // See if we had a resource
     if (!$resource) {
         // See if we have a return annotation
         if ($method_info["return"]) {
             // See if the return type is a primitive
             if (!$this->isPrimitive($method_info["return"]["type"])) {
                 // Create the resource from the return type
                 $resource = new Resource();
                 $resource->class = $method_info["return"]["type"];
                 $resource->name = $this->generateResourceName($resource->class);
             }
         } else {
             if (count($method_info["param"]) == 1 && $method_info["param"][0]["type"] && !$this->isPrimitive($method_info["param"][0]["type"])) {
                 // Get the resource name from the first and only parameter
                 $resource = new Resource();
                 $resource->class = $method_info["param"][0]["type"];
                 $resource->name = $this->generateResourceName($resource->class);
             }
         }
     }
     // If we still don't have a resource, create a new unnamed one
     if (!$resource) {
         $resource = new Resource();
         $resource->name = "Resource-" . (count($this->resources) + 1);
     }
     // Set the Resource ID from the name
     $resource->resourceId = $resource->name;
     // Set the short description from the class name
     if ($resource->class) {
         try {
             $class_annotations = Annotations::extractFromObject($resource->class);
             foreach ($class_annotations->class as $annotation) {
                 if ($annotation->name == "_comment") {
                     // Extract the first sentence
                     $first_period_pos = mb_strpos($annotation->value, ".");
                     if ($first_period_pos) {
                         // The description is the first sentence
                         $resource->shortDescription = substr($annotation->value, 0, $first_period_pos);
                     } else {
                         // The entire comment is the description
                         $resource->shortDescription = $annotation->value;
                     }
                     break;
                 }
             }
         } catch (\Exception $e) {
             // Ignore the error
         }
     }
     # Return the resource
     return $resource;
 }
Exemplo n.º 2
0
 /**
  * @covers Cougar\\Util\\Annotations::extractFromObject
  */
 public function testInterfaceAnnotationsFromObjectWithInheritance()
 {
     # Mock the cache
     $local_cache = $this->getMock("\\Cougar\\Cache\\Cache");
     $local_cache->expects($this->any())->method("get")->will($this->returnValue(false));
     $local_cache->expects($this->any())->method("set")->will($this->returnValue(false));
     Annotations::$cache = $local_cache;
     $annotations = Annotations::extractFromObjectWithInheritance(__NAMESPACE__ . "\\BasicClassFromInterface");
     $this->assertInstanceOf("Cougar\\Util\\ClassAnnotations", $annotations);
     $this->assertCount(2, $annotations->class);
     $this->assertEquals(new Annotation("Interface", "Interface annotation"), $annotations->class[0]);
     $this->assertCount(2, $annotations->class);
     $this->assertEquals(new Annotation("Class", "Class annotation"), $annotations->class[1]);
     $this->assertCount(0, $annotations->properties);
     $this->assertCount(1, $annotations->methods);
     $this->assertArrayHasKey("doSomething", $annotations->methods);
     $this->assertCount(4, $annotations->methods["doSomething"]);
     $this->assertEquals(new Annotation("_comment", "This is a method in an interface"), $annotations->methods["doSomething"][0]);
     $this->assertEquals(new Annotation("param", "int \$number"), $annotations->methods["doSomething"][1]);
     $this->assertEquals(new Annotation("_comment", "This is the description in the implementation"), $annotations->methods["doSomething"][2]);
     $this->assertEquals(new Annotation("param", "int \$number Some number"), $annotations->methods["doSomething"][3]);
 }
Exemplo n.º 3
0
 /**
  * Extracts the annotation for the class and parses them into the
  * __-prefixed protected properties.
  *
  * @history
  * 2013.09.30:
  *   (AT)  Initial release
  * 2014.02.26:
  *   (AT)  Extract annotations with extractFromObjectWithInheritance()
  * 2014.03.05:
  *   (AT)  Don't clobber cached annotations when loading parsed annotations
  *         from cache
  *   (AT)  Switch from using __defaultValues to __previousValues
  * 2014.08.06:
  *   (AT)  Turn the execution cache into a proper memory cache
  *
  * @version 2014.08.06
  * @author (AT) Alberto Trevino, Brigham Young Univ. <*****@*****.**>
  *
  * @param mixed $object
  *   Assoc. array or object with initial values
  * @param string $view
  *   Set the given view once values are loaded
  * @param bool $strict
  *   Whether to perform strict property checking (on by default)
  * @throws Exception
  * @throws BadRequestException
  */
 public function __construct($object = null, $view = null, $strict = true)
 {
     # Store the value of the requested view (avoid clobbering later)
     $requested_view = $view;
     # Get a local cache and a memory cache
     # TODO: Set through static property(?)
     $local_cache = CacheFactory::getLocalCache();
     $execution_cache = CacheFactory::getMemoryCache();
     # Create our cache keys
     $class = get_class($this) . ".Model";
     $cache_key = Annotations::$annotationsCachePrefix . "." . $class;
     # See if the execution cache has the object properties
     $parsed_annotations = $execution_cache->get($cache_key);
     if (!$parsed_annotations) {
         # Get the annotations
         $this->__annotations = Annotations::extractFromObjectWithInheritance($this, array(), true, false);
         # See if the annotations came from the cache
         $parsed_annotations = false;
         if ($this->__annotations->cached) {
             $parsed_annotations = $local_cache->get($cache_key);
         }
     }
     # See if we have pre-parsed annotations
     if ($parsed_annotations === false) {
         # Go through the class annotations
         $view_list = array("__default__");
         foreach ($this->__annotations->class as $annotation) {
             switch ($annotation->name) {
                 case "CaseInsensitive":
                     $this->__caseInsensitive = true;
                     break;
                 case "Views":
                     # See which views are defined
                     $views = preg_split('/\\s+/u', $annotation->value, null, PREG_SPLIT_NO_EMPTY);
                     # Create the views (if we have any)
                     foreach ($views as $view) {
                         $this->__views[$view] = $this->__views["__default__"];
                         $view_list[] = $view;
                     }
                     break;
             }
         }
         # Go through the public properties of the object
         foreach (array_keys($this->__annotations->properties) as $property_name) {
             # Add the property to the list of properties
             $this->__properties[] = $property_name;
             # Set the default property options
             $this->__type[$property_name] = "string";
             $this->__readOnly[$property_name] = false;
             $this->__null[$property_name] = true;
             $this->__regex[$property_name] = array();
             $this->__alias[$property_name] = $property_name;
             # See if the properties are case-insensitive
             if ($this->__caseInsensitive) {
                 # Store the lowercase property name as an alias
                 $this->__alias[strtolower($property_name)] = $property_name;
             }
             # Set the view-based values
             foreach ($view_list as $view) {
                 $this->__views[$view]["optional"][$property_name] = false;
                 $this->__views[$view]["visible"][$property_name] = true;
                 $this->__views[$view]["exportAlias"][$property_name] = $property_name;
             }
             # Go through the annotations
             foreach ($this->__annotations->properties[$property_name] as $annotation) {
                 switch ($annotation->name) {
                     case "Alias":
                     case "Column":
                         $this->__alias[$annotation->value] = $property_name;
                         if ($this->__caseInsensitive) {
                             $this->__alias[strtolower($annotation->value)] = $property_name;
                         }
                         break;
                     case "NotNull":
                         $this->__null[$property_name] = false;
                         break;
                     case "Regex":
                         $this->__regex[$property_name][] = $annotation->value;
                         break;
                     case "Optional":
                         # Set the option in all views
                         foreach ($view_list as $view) {
                             $this->__views[$view]["optional"][$property_name] = true;
                         }
                         break;
                     case "DateTimeFormat":
                         $this->__dateTimeFormat[$property_name] = $annotation->value;
                         break;
                     case "View":
                         # Separate the values
                         $view_values = preg_split('/\\s+/u', $annotation->value);
                         # Extract the view (first value)
                         $view = array_shift($view_values);
                         # Make sure the view exists
                         if (!array_key_exists($view, $this->__views)) {
                             throw new Exception($property_name . " property defines \"" . $view . "\" but the view does not exist.");
                         }
                         # Go through the rest of the options
                         $export_alias_set = false;
                         foreach ($view_values as $index => $value) {
                             switch (strtolower($value)) {
                                 case "hidden":
                                     $this->__views[$view]["visible"][$property_name] = false;
                                     break;
                                 case "optional":
                                     $this->__views[$view]["optional"][$property_name] = true;
                                     break;
                                 default:
                                     # Add the real value (not lowercase) as
                                     # the export alias and as an alias
                                     if (!$export_alias_set) {
                                         $this->__views[$view]["exportAlias"][$property_name] = $view_values[$index];
                                         $this->__alias[$view_values[$index]] = $property_name;
                                         if ($this->__caseInsensitive) {
                                             $this->__alias[strtolower($view_values[$index])] = $property_name;
                                         }
                                     }
                                     $export_alias_set = true;
                                     break;
                             }
                         }
                         break;
                     case "var":
                         # Separate the variable name from the comment
                         $var_values = preg_split('/\\s+/u', $annotation->value);
                         switch ($var_values[0]) {
                             case "string":
                             case "":
                                 # Type is already set to string
                                 break;
                             case "int":
                             case "integer":
                                 $this->__type[$property_name] = "int";
                                 break;
                             case "float":
                             case "double":
                                 $this->__type[$property_name] = "float";
                                 break;
                             case "bool":
                             case "boolean":
                                 $this->__type[$property_name] = "bool";
                                 break;
                             case "DateTime":
                                 $this->__type[$property_name] = "DateTime";
                                 if (!array_key_exists($property_name, $this->__dateTimeFormat)) {
                                     $this->__dateTimeFormat[$property_name] = "";
                                 }
                                 break;
                             default:
                                 $this->__type[$property_name] = $var_values[0];
                         }
                         break;
                 }
             }
         }
         # Get the default values
         foreach ($this->__properties as $property) {
             $this->__defaultValues[$property] = $this->{$property};
         }
         # Store the record properties in the caches
         $parsed_annotations = array("annotations" => $this->__annotations, "properties" => $this->__properties, "type" => $this->__type, "readOnly" => $this->__readOnly, "null" => $this->__null, "dateTimeFormat" => $this->__dateTimeFormat, "regex" => $this->__regex, "alias" => $this->__alias, "caseInsensitive" => $this->__caseInsensitive, "view" => $this->__views, "defaultValues" => $this->__defaultValues);
         $execution_cache->set($cache_key, $parsed_annotations);
         $local_cache->set($cache_key, $parsed_annotations, Annotations::$cacheTime);
     } else {
         # Make sure we don't clobber any previous annotations
         #  (otherwise we may lose the cached setting)
         if (!$this->__annotations) {
             $this->__annotations = $parsed_annotations["annotations"];
         }
         # Restore the property values
         $this->__properties = $parsed_annotations["properties"];
         $this->__type = $parsed_annotations["type"];
         $this->__readOnly = $parsed_annotations["readOnly"];
         $this->__null = $parsed_annotations["null"];
         $this->__dateTimeFormat = $parsed_annotations["dateTimeFormat"];
         $this->__regex = $parsed_annotations["regex"];
         $this->__alias = $parsed_annotations["alias"];
         $this->__caseInsensitive = $parsed_annotations["caseInsensitive"];
         $this->__views = $parsed_annotations["view"];
         $this->__defaultValues = $parsed_annotations["defaultValues"];
     }
     # Set the previous values from the default values
     $this->__previousValues = $this->__defaultValues;
     # See if we have an incoming object or array
     if (is_array($object) || is_object($object)) {
         # Load the incoming values
         $this->__import($object, $strict);
     } else {
         if (!is_null($object)) {
             throw new BadRequestException("Casting from object requires an object or array");
         }
     }
     # Set the desired view
     if ($requested_view) {
         $this->__setView($requested_view);
     } else {
         # Point the protected properties to the values in the default view
         $this->__exportAlias =& $this->__views["__default__"]["exportAlias"];
         $this->__optional =& $this->__views["__default__"]["optional"];
         $this->__visible =& $this->__views["__default__"]["visible"];
     }
 }
Exemplo n.º 4
0
 /**
  * Extracts the annotations from the class, property and method blocks and
  * stores them in the protected __annotations property.
  *
  * @history
  * 2013.09.30:
  *   (AT)  Initial release
  * 2014.02.26:
  *   (AT)  Extract annotations with extractFromObjectWithInheritance()
  *
  * @version 2014.02.26
  * @author (AT) Alberto Trevino, Brigham Young Univ. <*****@*****.**>
  */
 public function __construct()
 {
     $this->__annotations = Annotations::extractFromObjectWithInheritance($this, array(), true, false);
 }
Exemplo n.º 5
0
 /**
  * Binds all the services in the given object. This call can be made as
  * many times as as necessary to bind all necessary services.
  *
  * @history
  * 2013.09.30:
  *   (AT)  Initial release
  * 2013.10.16:
  *   (AT)  Fix clobbering issue where calling the method a second time
  *         deletes previous bindings
  * 2014.02.26:
  *   (AT)  Extract annotations using extractFromObjectWithInheritance()
  *         method and enable inheritance from interfaces
  * 2014.03.10:
  *   (AT)  Add ^/ to the path regex to match explicitly on the beginning of
  *         the path
  * 2014.03.12:
  *   (AT)  Require a URI parameter to contain at least one character
  *   (AT)  Add the number of URI parameters and literals to the binding
  * 2014.03.14:
  *   (AT)  Fix matching of root paths (those are simply /)
  *   (AT)  Add $ to end of regex to be more exact on path matching
  *   (AT)  Make sure to set the parameter name when the parameter contains a
  *         regular expression
  * 2014.08.05:
  *   (AT)  Internally convert the Returns and Accepts annotations to
  *         lowercase to improve and simplify mimetype detection
  *
  * @version 2014.08.05
  * @author (AT) Alberto Trevino, Brigham Young Univ. <*****@*****.**>
  * 
  * @param object $object_reference
  *   Reference to the object that will be bound
  * @throws \Cougar\Exceptions\Exception;
  */
 public function bindFromObject(&$object_reference)
 {
     # Make sure this is an object
     if (!is_object($object_reference)) {
         throw new Exception("Object reference must be an object");
     }
     # Get the class name
     $class = get_class($object_reference);
     # Skip if this class is already in the object list
     if (array_key_exists($class, $this->objects)) {
         throw new Exception("You have attempted to bind an object twice " . "or bind two objects of the same class; please verify your " . "object bindings");
     }
     # Create our own cache key
     $cache_key = Annotations::$annotationsCachePrefix . $class . ".AnnotatedRestService.Bindings";
     # Get the annotations
     $annotations = Annotations::extractFromObjectWithInheritance($object_reference, array(), true, true);
     # See if we have pre-parsed bindings
     $bindings = false;
     if ($annotations->cached) {
         $bindings = $this->localCache->get($cache_key);
     }
     # See if we need to extract the bindings from the annotations
     if ($bindings === false) {
         # Start a blank bindings list
         $bindings = array();
         # Go through the object's methods
         foreach ($annotations->methods as $method => $annotations) {
             # Create the binding and initialize the paths and methods array
             $binding = new Binding();
             $paths = array();
             # Add the class and method information about the binding
             $binding->object = $class;
             $binding->method = $method;
             $binding->http_methods = array("GET", "POST", "PUT", "DELETE");
             # Extract the property's annotations
             foreach ($annotations as $annotation) {
                 switch ($annotation->name) {
                     case "Path":
                         $paths[] = $annotation->value;
                         break;
                     case "Methods":
                         $binding->http_methods = preg_split('/\\s+/u', mb_strtoupper($annotation->value));
                         break;
                     case "Accepts":
                         $binding->accepts = mb_strtolower($annotation->value);
                         break;
                     case "Returns":
                         $binding->returns = mb_strtolower($annotation->value);
                         break;
                     case "XmlRootElement":
                     case "RootElement":
                         $binding->xmlRootElement = $annotation->value;
                         break;
                     case "XmlObjectName":
                     case "ObjectName":
                         $binding->xmlObjectName = $annotation->value;
                         break;
                     case "XmlObjectList":
                         $binding->xmlObjectList = $annotation->value;
                         break;
                     case "XSD":
                         $binding->xsd = file_get_contents($annotation->value, true);
                         break;
                     case "XSL":
                         $binding->xsl = file_get_contents($annotation->value, true);
                         break;
                     case "UriArray":
                         $parameter = new Parameter();
                         $parameter->source = "URI";
                         $parameter->index = 0;
                         $parameter->array = true;
                         $binding->parameters[$annotation->value] = $parameter;
                         break;
                     case "GetArray":
                         $parameter = new Parameter();
                         $parameter->source = "GET";
                         $parameter->index = 0;
                         $parameter->array = true;
                         $binding->parameters[$annotation->value] = $parameter;
                         break;
                     case "GetValue":
                         # Define the new entry
                         $parameter = new Parameter();
                         # Split the values at word boundaries
                         $values = preg_split('/\\s+/u', $annotation->value, 3);
                         # See how many values we have
                         switch (count($values)) {
                             case 3:
                                 # type variable_name method_parameter_name
                                 $param_name = $values[2];
                                 $parameter->source = "GET";
                                 $parameter->index = $values[1];
                                 $parameter->type = $values[0];
                                 break;
                             case 2:
                                 # type get_variable_name
                                 $param_name = $values[1];
                                 $parameter->source = "GET";
                                 $parameter->index = $values[1];
                                 $parameter->type = $values[0];
                                 break;
                             case 1:
                                 # get_variable_name
                                 $param_name = $values[0];
                                 $parameter->source = "GET";
                                 $parameter->index = $values[0];
                                 $parameter->type = "string";
                                 break;
                             default:
                                 throw new InvalidAnnotationException("Invalid GetValue: " . $annotation->value);
                         }
                         # Add the parameter
                         $binding->parameters[$param_name] = $parameter;
                         break;
                     case "GetQuery":
                         # Define the new entry
                         $parameter = new Parameter();
                         # Split the values at word boundaries
                         $values = preg_split('/\\s+/u', $annotation->value, 3);
                         # See how many values we have
                         switch (count($values)) {
                             case 1:
                                 # get_variable_name
                                 $param_name = $values[0];
                                 $parameter->source = "QUERY";
                                 $parameter->type = "array";
                                 break;
                             default:
                                 throw new InvalidAnnotationException("Invalid GetQuery: " . $annotation->value);
                         }
                         # Add the parameter
                         $binding->parameters[$param_name] = $parameter;
                         break;
                     case "PostArray":
                         $parameter = new Parameter();
                         $parameter->source = "POST";
                         $parameter->index = 0;
                         $parameter->array = true;
                         $binding->parameters[$annotation->value] = $parameter;
                         break;
                     case "PostValue":
                         # Define the new entry
                         $parameter = new Parameter();
                         # Split the values at word boundaries
                         $values = preg_split('/\\s+/u', $annotation->value, 3);
                         # See how many values we have
                         switch (count($values)) {
                             case 3:
                                 # type variable_name method_parameter_name
                                 $param_name = $values[2];
                                 $parameter->source = "POST";
                                 $parameter->index = $values[1];
                                 $parameter->type = $values[0];
                                 break;
                             case 2:
                                 # type get_variable_name
                                 $param_name = $values[1];
                                 $parameter->source = "POST";
                                 $parameter->index = $values[1];
                                 $parameter->type = $values[0];
                                 break;
                             case 1:
                                 # get_variable_name
                                 $param_name = $values[0];
                                 $parameter->source = "POST";
                                 $parameter->index = $values[0];
                                 $parameter->type = "string";
                                 break;
                             default:
                                 throw new InvalidAnnotationException("Invalid PostValue: " . $annotation->value);
                         }
                         # Add the parameter
                         $binding->parameters[$param_name] = $parameter;
                         break;
                     case "Body":
                         # Define the new entry
                         $parameter = new Parameter();
                         # Split the values at word boundaries
                         $values = preg_split('/\\s+/u', $annotation->value, 2);
                         # See how many values we have
                         switch (count($values)) {
                             case 2:
                                 # parameter_name type
                                 $param_name = $values[0];
                                 $parameter->source = "BODY";
                                 $parameter->type = $values[1];
                                 break;
                             case 1:
                                 $param_name = $values[0];
                                 $parameter->source = "BODY";
                                 break;
                             default:
                                 throw new InvalidAnnotationException("Invalid Body: " . $annotations->value);
                         }
                         # Add the parameter
                         $binding->parameters[$param_name] = $parameter;
                         break;
                     case "Authentication":
                         if (mb_strtolower($annotation->value == "required")) {
                             $binding->authentication = "required";
                         } else {
                             $binding->authentication = "optional";
                         }
                         break;
                 }
             }
             # Go through each path
             foreach ($paths as $str_path) {
                 # Clone the binding
                 $real_binding = clone $binding;
                 # Remote leading and trailing slashes on the path
                 $str_path = preg_replace(':^/|/$:', "", $str_path);
                 # Separate the path elements by the backslash
                 $path = explode("/", $str_path);
                 # See if the first element is blank (usually the case)
                 if ($path[0] === "") {
                     # Get rid of the element
                     array_shift($path);
                 }
                 # Check for root path (a simple / for the path)
                 if (count($path) == 1 && $path[0] === "") {
                     # The number of URI parameters is 0
                     $real_binding->pathArgumentCount = 0;
                 } else {
                     # Add the number of URI parameters to the binding
                     $real_binding->pathArgumentCount = count($path);
                 }
                 # Go through each part of the path
                 foreach ($path as $index => &$subpath) {
                     # Split the subpath into its parts
                     $subpath_parts = explode(":", $subpath, 4);
                     $subpath_count = count($subpath_parts);
                     # See if this is a literal argument
                     if ($subpath_count < 2) {
                         # See if we had a value (useful for root path)
                         # Increase the literal argument count
                         $real_binding->literalPathArgumentCount++;
                         # Go on to the next argument
                         continue;
                     }
                     # Define the new parameter, its name and regex
                     # expression, and add the number of path parameters
                     $parameter = new Parameter();
                     $param_name = "";
                     if (mb_substr($subpath, -1) == "+") {
                         $param_regex = ".*";
                         # Declare the number of arguments to be 1000
                         $real_binding->pathArgumentCount = 1000;
                     } else {
                         $param_regex = "[^/]+";
                     }
                     # See how many parts we have
                     switch ($subpath_count) {
                         case 4:
                             # :param_name:type:regex
                             $param_name = $subpath_parts[1];
                             $parameter->source = "URI";
                             $parameter->index = $index;
                             $parameter->type = $subpath_parts[2];
                             $param_regex = $subpath_parts[3];
                             break;
                         case 3:
                             # :param_name:type
                             $param_name = $subpath_parts[1];
                             $parameter->source = "URI";
                             $parameter->index = $index;
                             $parameter->type = $subpath_parts[2];
                             break;
                         case 2:
                             # :param_name
                             $param_name = $subpath_parts[1];
                             $parameter->source = "URI";
                             $parameter->index = $index;
                             $parameter->type = "string";
                             break;
                     }
                     # See if the have a + at the end of this parameter
                     if (mb_substr($param_name, -1) == "+") {
                         # Get rid of the +
                         $param_name = mb_substr($param_name, 0, -1);
                         # Set the _array flag
                         $parameter->array = true;
                     }
                     # Add the parameter
                     $real_binding->parameters[$param_name] = $parameter;
                     # Replace the value of the parameter with its regular
                     # expression
                     $subpath = $param_regex;
                 }
                 # Reconstruct the path from the new regex values
                 $new_path = "^/" . implode("/", $path) . "\$";
                 # Add the binding
                 $bindings[$new_path][] = $real_binding;
             }
         }
         # Store the parsed bindings
         $this->localCache->set($cache_key, $bindings, Annotations::$cacheTime);
     }
     # Add the bindings to our bindings list
     $this->bindings = array_merge($this->bindings, $bindings);
     # Store the object reference
     $this->objects[$class] = $object_reference;
 }