/** * @covers Cougar\Cache\CacheFactory::getApplicationCache */ public function testGetApplicationCache() { $test_memcache = false; if (!defined("APPLICATION_CACHE_CONFIGURATION")) { define("APPLICATION_CACHE_CONFIGURATION", "memcache 127.0.0.1:11211"); $test_memcache = true; } $cache = CacheFactory::getApplicationCache(); $this->assertinstanceOf("Cougar\\Cache\\Cache", $cache); if ($test_memcache) { $this->assertEquals("memcache", $cache->getCacheType()); } }
/** * Returns the annotations for the class and public methods and properties * from the given object. It will also aggregate the annotations in parent * classes and optionally from traits and/or interfaces that are directly * used by the class. * * @history * 2014.02.26: * (AT) Initial implementation from deprecated extract() method * 2014.03.05: * (AT) Make sure to set the cached flag on the empty annotations object * so that the child annotation object cached flags may be preserved * 2014.03.17: * (AT) Cache inherited annotations directly to improve performance * 2014.03.19: * (AT) Clone annotations object before storing in execution cache * 2014.05.08: * (AT) Make sure trait and interface filenames are checked for changes * * @version 2014.05.08 * @author (AT) Alberto Trevino, Brigham Young Univ. <*****@*****.**> * * @param mixed $object * Interface name, class name or object to extract from * @param array $exclude_class_list * List of classes to exclude from in the object's inheritance tree * @param bool $inherit_from_traits * Whether to extract annotations in the trait document block * @param bool $inherit_from_interfaces * Whether to extract annotations in the interface document block and its * methods * @throws \Cougar\Exceptions\Exception * @return \Cougar\Util\ClassAnnotations * ClassAnnotations object with annotations */ public static function extractFromObjectWithInheritance($object, array $exclude_class_list = array(), $inherit_from_traits = true, $inherit_from_interfaces = true) { // Make sure we have a cache if (!self::$cache instanceof iCache) { self::$cache = CacheFactory::getLocalCache(); } // Get the name of the object, class or interface if (is_object($object)) { $object_class_name = get_class($object); } else { if (is_string($object)) { $object_class_name = $object; } else { throw new Exception("Object must be an object reference or class name"); } } // Figure out the cache key $cache_key = self::$annotationsCachePrefix . "." . $object_class_name . ".Inherited." . implode(",", $exclude_class_list) . "." . (int) $inherit_from_traits . "." . (int) $inherit_from_interfaces; // See if we have an entry in the execution cache if (array_key_exists($cache_key, self::$executionCache)) { // Return the annotations from the execution cache return clone self::$executionCache[$cache_key]; } // Reflect the object $r_object = new ReflectionClass($object_class_name); // Start the class hierarchy, trait list and filename list $class_hierarchy = array(); $trait_list = array(); $filename_list = array(); // Recursively get the parent classes $parent = $r_object; do { // See if class is in the Cougar\Model namespace or the exclude list if (substr($parent->name, 0, 12) !== "Cougar\\Model" && !in_array($parent->name, $exclude_class_list)) { // Add the class to the hierarchy array_unshift($class_hierarchy, $parent->name); // Get the class filename $filename_list[$parent->name] = $parent->getFileName(); // See if we are extracting from traits if ($inherit_from_traits) { // Go through the traits foreach ($parent->getTraits() as $trait => $r_trait) { // Skip if in Cougar\Model or exclude_class_list if (substr($trait, 0, 12) !== "Cougar\\Model" && !in_array($trait, $exclude_class_list)) { // Add the trait to the class hierarchy array_unshift($class_hierarchy, $trait); // Add the trait to the list of traits $trait_list[] = $trait; // Add the file name to the filename list $filename_list[] = $r_trait->getFileName(); } } } // See if we are extracting from interfaces if ($inherit_from_interfaces) { // Go through the interfaces foreach ($parent->getInterfaces() as $interface => $r_interface) { // Skip if in Cougar\Model or exclude_class_list if (substr($interface, 0, 12) !== "Cougar\\Model" && !in_array($interface, $exclude_class_list)) { // Add the interface to the class hierarchy array_unshift($class_hierarchy, $interface); // Add the file name to the filename list $filename_list[] = $r_interface->getFileName(); } } } // Get the parent $parent = $parent->getParentClass(); } else { // Don't get the next parent since this class was excluded $parent = false; } } while ($parent !== false); // See if the files have been modified if (!self::filesHaveChanged(array_unique($filename_list), false)) { // See if we have an entry in the local cache $annotations = self::$cache->get($cache_key); if ($annotations !== false) { // Store the annotations in the execution cache self::$executionCache[$cache_key] = clone $annotations; // Return the cached annotations return $annotations; } } // Define an empty set of annotations and consider them cached $annotations = new ClassAnnotations(); $annotations->cached = true; // Go through each class, trait and interface foreach ($class_hierarchy as $class) { // See if we are inheriting from traits and if this is a trait if ($inherit_from_traits && in_array($class, $trait_list)) { // Get the annotations for the trait $trait_annotations = self::extractFromObject($class, false); // Trait functions are automatically included in the class; we // only need the annotations from the trait definition $trait_annotations->properties = array(); $trait_annotations->methods = array(); // Merge the annotations self::merge($annotations, $trait_annotations); } else { // Merge the annotations self::merge($annotations, self::extractFromObject($class, false)); } } // Store the annotations in the cache $orig_cache_flag = $annotations->cached; $annotations->cached = true; self::$executionCache[$cache_key] = clone $annotations; self::$cache->set($cache_key, $annotations, self::$cacheTime); $annotations->cached = $orig_cache_flag; // Return the annotations return $annotations; }
/** * Initializes the class map, either by loading it from the cache or * rebuilding it from the code. * * @history * 2013.09.30: * (AT) Initial release * 2013.11.13: * (AT) Actually build the namespace map * * @version 2013.09.30 * @author (AT) Alberto Trevino, Brigham Young Univ. <*****@*****.**> * @author (JPK) Jillian Koontz, Brigham Young Univ. <*****@*****.**> * * @param string $path * Path to build from * @param bool $rescan * Whether to ignore the cache entries and rescan the path * @throws \Cougar\Exceptions\Exception */ protected static function initializeClassMap($path, $rescan = false) { # Make sure the path exists if (!is_dir($path)) { throw new Exception("Cannot initialize class map: " . $path . " is not a valid directory"); } # Get a new local cache $cache = CacheFactory::getLocalCache(); # Define the cache key $key = self::$cachePrefix . ":" . $path; # Get the value from the cache $cached_maps = false; if (!$rescan) { $cached_maps = $cache->get($key); } # See if we got anything from the cache if (!$cached_maps) { # Get the list of PHP scripts in the given directory $file_list = array(); $dir_list = array(); self::findPhpScripts($path, $file_list, $dir_list); # Get the classes and namespaces $classes = array(); $namespaces = array(); self::extractClasses($file_list, $path, $classes, $namespaces); # Prepare the cached class map values $cached_maps = array("classes" => $classes, "directories" => $dir_list, "namespaces" => $namespaces); # Store the class map and directory list in the cache $cache->set($key, $cached_maps, self::$cacheTime); } # Merge the results self::$classMap = array_merge(self::$classMap, $cached_maps["classes"]); # Add the path to the path list self::$paths = array_merge(self::$paths, $cached_maps["directories"]); # Merge the namespaces foreach ($cached_maps["namespaces"] as $namespace => $namespace_path) { if (array_key_exists($namespace, self::$namespaceMap)) { if (!in_array($namespace_path, self::$namespaceMap[$namespace])) { self::$namespaceMap[$namespace][] = $namespace_path; } } else { self::$namespaceMap[$namespace] = array($namespace_path); } } }
/** * Initializes the database record. If the object provides values for the * primary key properties, then the record is loaded from the database. If * no, a new record is created. * * @history * 2013.09.30: * (AT) Initial release * 2013.10.24: * (AT) Change authorization() call parameter order * 2014.02.18: * (AT) Add support for the unbound annotation * 2014.04.02: * (AT) Switch from using __defaultValues to __previousValues and * __persistenceValues * 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 \Cougar\Security\iSecurity $security * Security context * @param \Cougar\Cache\iCache $cache * Cache object * @param \PDO $pdo * Database connection * @param mixed $object * Object or assoc. array of property values * @param string $view * Set view on load * @param bool $strict * Whether to perform strict property checking (on by default) * @throws \Cougar\Exceptions\Exception */ public function __construct(iSecurity $security, iCache $cache, \PDO $pdo, $object = null, $view = null, $strict = true) { # Get a local cache # TODO: Set through static property(?) $local_cache = CacheFactory::getLocalCache(); $execution_cache = CacheFactory::getMemoryCache(); # Store the object references $this->__security = $security; $this->__cache = $cache; $this->__pdo = $pdo; # Add the class name to the default cache prefix $this->__cachePrefix .= "." . get_class($this); # Create our own cache keys $class = get_class($this) . ".PdoModel"; $cache_key = Annotations::$annotationsCachePrefix . "." . $class; # Call the parent constructor $this->__constructModel(null, $view); # See if the execution cache has the object properties $parsed_annotations = $execution_cache->get($cache_key); if (!$parsed_annotations) { # See if the annotations came from the cache 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 foreach ($this->__annotations->class as $annotation) { switch ($annotation->name) { case "Table": # Only take the first table value if (!$this->__table) { $this->__table = $annotation->value; } break; case "Allow": # Set all the operation to false $this->__allowCreate = false; $this->__allowRead = false; $this->__allowUpdate = false; $this->__allowDelete = false; $this->__allowQuery = false; # See which operations will be allowed foreach (preg_split('/\\s+/u', strtolower($annotation->value)) as $operation) { switch ($operation) { case "create": case "insert": $this->__allowCreate = true; break; case "read": case "select": $this->__allowRead = true; break; case "update": $this->__allowUpdate = true; break; case "delete": $this->__allowDelete = true; break; case "query": case "list": $this->__allowQuery = true; } } break; case "Join": case "JOIN": if ($annotation->value) { # See if the join already has the word JOIN if (stripos($annotation->value, "join") === false) { $this->__joins[] = "JOIN " . $annotation->value; } else { $this->__joins[] = $annotation->value; } } break; case "PrimaryKey": if ($annotation->value) { foreach (preg_split('/\\s+/u', $annotation->value) as $property) { if (in_array($property, $this->__properties)) { $this->__primaryKey[] = $property; } else { throw new Exception("Specified primary key " . "property " . $property . " does not exist"); } } } break; case "ReadOnly": if ($annotation->value) { foreach (preg_split('/\\s+/u', $annotation->value) as $property) { if (array_key_exists($property, $this->__readOnly)) { $this->__readOnly[$property] = true; } else { throw new Exception("Specified read-only property " . $property . " does not exist"); } } } break; case "DeleteFlag": if ($annotation->value) { $tmp_array = preg_split('/\\s+/u', $annotation->value, 2); if (count($tmp_array) != 2) { throw new Exception("You must specify a " . "property name and value with " . "@DeleteFlag annotation"); } if (in_array($tmp_array[0], $this->__properties)) { $this->__deleteProperty = $tmp_array[0]; $this->__deletePropertyValue = $tmp_array[1]; } else { throw new Exception("Delete flag property "); } } break; case "QueryList": if ($annotation->value) { foreach (preg_split('/\\s+/u', $annotation->value) as $property) { if (in_array($property, $this->__properties)) { $this->__queryProperties[] = $property; } else { throw new Exception("Specified query property " . $property . " does not exist"); } } } break; case "QueryView": if ($annotation->value) { if (array_key_exists($annotation->value, $this->__views)) { $this->__queryView = $annotation->value; } } break; case "QueryUnique": $this->__queryUnique = true; break; case "NoQuery": # Here for backward compatibility $this->__allowQuery = false; break; case "CachePrefix": if ($annotation->value) { $this->__cachePrefix = $annotation->value; } break; case "CacheTime": if ($annotation->value) { $this->__cacheTime = (int) $annotation->value; } break; case "VoidCacheEntry": if ($annotation->value) { $this->__voidCacheEntries[] = $annotation->value; } break; case "NoCache": $this->__noCache = true; $this->__noQueryCache = true; break; case "NoQueryCache": $this->__noQueryCache = true; break; } } # Make sure the table name has been defined if (!$this->__table) { throw new Exception("You must specify a table name using the " . "@Table annotation in the class document block"); } # Make sure we have a primary key if (!$this->__primaryKey) { throw new Exception("You must specify the columns that make " . "up the Primary Key using the @PrimaryKey annotation in " . "the class document block"); } # Go through the properties foreach ($this->__annotations->properties as $property_name => $annotations) { # Create the property in the column map $this->__columnMap[$property_name] = $property_name; # Go through the annotations foreach ($annotations as $annotation) { switch ($annotation->name) { case "Column": $this->__columnMap[$property_name] = $annotation->value; break; case "Unbound": unset($this->__columnMap[$property_name]); break; case "ReadOnly": $this->__readOnly[$property_name] = true; } } } # See if we had query properties if (!$this->__queryProperties) { # Declare all properties are queryable $this->__queryProperties = array_keys($this->__columnMap); } # Store the record properties in the caches $parsed_annotations = array("table" => $this->__table, "primaryKey" => $this->__primaryKey, "allowSelect" => $this->__allowRead, "allowInsert" => $this->__allowCreate, "allowUpdate" => $this->__allowUpdate, "allowDelete" => $this->__allowDelete, "joins" => $this->__joins, "deleteProperty" => $this->__deleteProperty, "deletePropertyValue" => $this->__deletePropertyValue, "queryProperties" => $this->__queryProperties, "allowQuery" => $this->__allowQuery, "queryView" => $this->__queryView, "queryUnique" => $this->__queryUnique, "cachePrefix" => $this->__cachePrefix, "cacheTime" => $this->__cacheTime, "voidCacheEntries" => $this->__voidCacheEntries, "noCache" => $this->__noCache, "noQueryCache" => $this->__noQueryCache, "columnMap" => $this->__columnMap, "readOnly" => $this->__readOnly); $execution_cache->set($cache_key, $parsed_annotations); $local_cache->set($cache_key, $parsed_annotations, Annotations::$cacheTime); } else { # Restore the property values $this->__table = $parsed_annotations["table"]; $this->__primaryKey = $parsed_annotations["primaryKey"]; $this->__allowRead = $parsed_annotations["allowSelect"]; $this->__allowCreate = $parsed_annotations["allowInsert"]; $this->__allowUpdate = $parsed_annotations["allowUpdate"]; $this->__allowDelete = $parsed_annotations["allowDelete"]; $this->__joins = $parsed_annotations["joins"]; $this->__deleteProperty = $parsed_annotations["deleteProperty"]; $this->__deletePropertyValue = $parsed_annotations["deletePropertyValue"]; $this->__queryProperties = $parsed_annotations["queryProperties"]; $this->__allowQuery = $parsed_annotations["allowQuery"]; $this->__queryView = $parsed_annotations["queryView"]; $this->__queryUnique = $parsed_annotations["queryUnique"]; $this->__cachePrefix = $parsed_annotations["cachePrefix"]; $this->__cacheTime = $parsed_annotations["cacheTime"]; $this->__voidCacheEntries = $parsed_annotations["voidCacheEntries"]; $this->__noCache = $parsed_annotations["noCache"]; $this->__noQueryCache = $parsed_annotations["noQueryCache"]; $this->__columnMap = $parsed_annotations["columnMap"]; $this->__readOnly = $parsed_annotations["readOnly"]; } # See if the object we received was an object or array if (is_array($object) || is_object($object)) { # Separate the object's values into primary key and other values $pk_values = array(); $values = array(); $has_primary_key_values = false; foreach ($object as $key => $value) { if ($this->__caseInsensitive) { $key = strtolower($key); } # See if this is a value we handle if (array_key_exists($key, $this->__alias)) { # Resolve the alias $key = $this->__alias[$key]; # See if this is a primary key value if (in_array($key, $this->__primaryKey)) { # Add the value to our list of primary key values $pk_values[$key] = $value; # See if the value is not equivalent to default if ($value != $this->{$this->__alias[$key]}) { # This is a primary key value; save the value $has_primary_key_values = true; } } else { # This is another value; store it separately $values[$key] = $value; } } } # See if we have primary key values if ($has_primary_key_values) { # Set the PK properties foreach ($pk_values as $key => $value) { $this->{$key} = $value; } # Get the record; method will also cast values $this->getRecord(); # Call the authorization method; we call it after we get the # record since the authorization may be based on the values $this->authorization($this->__security, $this->__allowCreate, $this->__allowRead, $this->__allowUpdate, $this->__allowDelete, $this->__allowQuery, $this->__columnMap, $this->__readOnly, $this->__visible); # Make sure the identity is authorized to read the record if (!$this->__allowRead) { throw new AccessDeniedException("You do not have access to this record"); } } else { # Set up insert mode $this->__insertMode = true; $this->__enforceReadOnly = false; # Set the persistent values from the previous values $this->__persistentValues = $this->__previousValues; } # Set the other properties via the __import method $this->__import($values); } else { # Set the persistent values from the previous (default) values $this->__persistentValues = $this->__previousValues; # Call the authorization method $this->authorization($this->__security, $this->__allowCreate, $this->__allowRead, $this->__allowUpdate, $this->__allowDelete, $this->__allowQuery, $this->__columnMap, $this->__readOnly, $this->__visible); $this->__insertMode = true; $this->__enforceReadOnly = false; } }
/** * Loads the given config file * * @history * 2013.09.30: * (AT) Initial release * * @version 2013.09.30 * @author (AT) Alberto Trevino, Brigham Young Univ. <*****@*****.**> * * @todo: check if the file has been modified since last cached * * @param string $config_file * Filename of the config file to load; may include full or relative path * @throws \Cougar\Exceptions\ConfigurationFileNotFoundException */ public function __construct($config_file) { # Get a local cache $local_cache = CacheFactory::getLocalCache(); # Create the hash of the calling function foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 4) as $key => $caller) { # Skip ourselves if ($key == 0) { continue; } # Make sure this is not a factory method if (array_key_exists("class", $caller)) { if (strpos($caller["class"], "Factory") !== false) { continue; } } # We found the call we need break; } # Create the cache key for this call if (!array_key_exists("file", $caller)) { $caller["file"] = "(Unknown)"; } $call_cache_key = self::$cachePrefix . ".Caller." . md5($caller["file"] . ":" . $caller["function"]) . "." . $config_file; # See if we have the filename in the cache $file = $local_cache->get($call_cache_key); if ($file === false) { # See if config file name has an absolute path $filename = ""; if (substr($config_file, 0, 1) !== "/") { # Find the file foreach (self::$subdirList as $dir) { $filename = stream_resolve_include_path($dir . DIRECTORY_SEPARATOR . $config_file); if ($filename) { break; } } } else { # See if the file exists if (file_exists($config_file)) { $filename = $config_file; } } # Make sure the file exists if (!$filename) { throw new ConfigurationFileNotFoundException("File does not exist: " . $config_file); } # Create the array with the file information $file["filename"] = $filename; $file["time"] = time(); # Store the filename in the cache $local_cache->set($call_cache_key, $file); } # See if the contents of the file are in the cache $file_cache_key = self::$cachePrefix . ".FileContents." . $file["filename"]; $values = $local_cache->get($file_cache_key); # See if we need to reload the file $reload = false; if ($values === false) { $reload = true; } else { if (filemtime($file["filename"] > $file["time"])) { $reload = true; } } if ($reload) { # TODO: Block access to system files # Get the file, line by line $lines = file($file["filename"], FILE_IGNORE_NEW_LINES + FILE_SKIP_EMPTY_LINES); # Go through each line $values = array(); foreach ($lines as $line) { # See if we have a hash $hash_pos = strpos($line, "#"); if ($hash_pos !== false) { # Remove the comment $line = substr($line, 0, $hash_pos); } # Trim the line $line = trim($line); # See if we have any contents if ($line) { # Split on the equal sign $split_line = explode("=", $line, 2); # See how many values we have if (count($split_line) == 2) { # Save the name and the value $values[trim($split_line[0])] = trim($split_line[1]); } else { # Save the name only $values[trim($split_line[0])] = ""; } } } # Store the entry in the cache $local_cache->set($file_cache_key, $values); } # Save the values in the protected property $this->values = $values; }
/** * Initializes the REST resource. If properties are provided the resource * will be loaded. Otherwise, a new resource will be created on save(). * * @history * 2013.09.30: * (AT) Initial release * 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 iSecurity $security Security context * @param iCache $cache Cache object * @param iRestClient $rest_client Rest client object * @param mixed $object Object or assoc. array of property values * @param string $view Set view on load * @param bool $strict Whether to perform strict property checking (on by default) * @throws \Cougar\Exceptions\Exception */ public function __construct(iSecurity $security, iCache $cache, iRestClient $rest_client, $object = null, $view = null, $strict = true) { # Get a local and a memory cache # TODO: Set through static property(?) $local_cache = CacheFactory::getLocalCache(); $execution_cache = CacheFactory::getMemoryCache(); # Store the object references $this->__security = $security; $this->__cache = $cache; $this->__restClient = $rest_client; # Add the class name to the default cache prefix $this->__cachePrefix .= "." . get_class($this); # Create our own cache keys $class = get_class($this) . ".WsModel"; $cache_key = Annotations::$annotationsCachePrefix . "." . $class; # Call the parent constructor $this->__constructModel(null, $view); # See if the execution cache has the object properties $parsed_annotations = $execution_cache->get($cache_key); if (!$parsed_annotations) { # See if the annotations came from the cache 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 foreach ($this->__annotations->class as $annotation) { switch ($annotation->name) { case "Allow": # Set all the operation to false $this->__allowCreate = false; $this->__allowRead = false; $this->__allowUpdate = false; $this->__allowDelete = false; $this->__allowQuery = false; # See which operations will be allowed foreach (preg_split("/\\s+/u", strtolower($annotation->value)) as $operation) { switch ($operation) { case "create": $this->__allowCreate = true; break; case "read": $this->__allowRead = true; break; case "update": $this->__allowUpdate = true; break; case "delete": $this->__allowDelete = true; break; case "query": case "list": $this->__allowQuery = true; break; } } break; case "BaseUri": $values = preg_split("/\\s+/u", $annotation->value, 2); if (ENVIRONMENT == strtolower($values[0])) { $this->__baseUri = $values[1]; } else { if (ENVIRONMENT == "local" && strtolower($values[0]) == "development" && !$this->__baseUri) { $this->__baseUri = $values[1]; } } break; case "ResourceID": if ($annotation->value) { foreach (preg_split("/\\s+/u", $annotation->value) as $property) { if (in_array($property, $this->__properties)) { $this->__resourceIds[] = $property; $this->__readOnly[$property] = true; } else { throw new Exception("Specified Resource ID property " . $property . " does not exist"); } } } break; case "ReadOnly": if ($annotation->value) { foreach (preg_split("/\\s+/u", $annotation->value) as $property) { if (array_key_exists($property, $this->__readOnly)) { $this->__readOnly[$property] = true; } else { throw new Exception("Specified read-only property " . $property . " does not exist"); } } } break; case "Create": if ($annotation->value) { $values = preg_split("/\\s+/u", $annotation->value, 2); switch (count($values)) { case 2: $this->__createCall["method"] = $values[0]; $this->__createCall["uri"] = $values[1]; break; case 1: $this->__createCall["method"] = $values[0]; $this->__createCall["uri"] = ""; break; } } break; case "CreateGetFields": if ($annotation->value) { foreach (preg_split("/\\s+/u", $annotation->value) as $property) { if (in_array($property, $this->__properties)) { $this->__createCall["get"][] = $property; } } } break; case "CreatePostFields": if ($annotation->value) { foreach (preg_split("/\\s+/u", $annotation->value) as $property) { if (in_array($property, $this->__properties)) { $this->__createCall["post"][] = $property; } } } break; case "CreateBody": switch (strtolower($annotation->value)) { case "xml": $this->__createCall["bodyType"] = "xml"; break; case "json": $this->__createCall["bodyType"] = "json"; break; case "php": $this->__createCall["bodyType"] = "php"; break; default: throw new Exception("Invalid create call " . "body type: " . $annotation->value); } break; case "Read": if ($annotation->value) { $values = preg_split("/\\s+/u", $annotation->value, 2); switch (count($values)) { case 2: $this->__readCall["method"] = $values[0]; $this->__readCall["uri"] = $values[1]; break; case 1: $this->__readCall["method"] = $values[0]; $this->__readCall["uri"] = ""; break; } } break; case "ReadGetFields": if ($annotation->value) { foreach (preg_split("/\\s+/u", $annotation->value) as $property) { if (in_array($property, $this->__properties)) { $this->__readCall["get"][] = $property; } } } break; case "ReadPostFields": if ($annotation->value) { foreach (preg_split("/\\s+/u", $annotation->value) as $property) { if (in_array($property, $this->__properties)) { $this->__readCall["post"][] = $property; } } } break; case "ReadBody": switch (strtolower($annotation->value)) { case "xml": $this->__readCall["bodyType"] = "xml"; break; case "json": $this->__readCall["bodyType"] = "json"; break; case "php": $this->__readCall["bodyType"] = "php"; break; default: throw new Exception("Invalid read call " . "body type: " . $annotation->value); } break; case "Update": if ($annotation->value) { $values = preg_split("/\\s+/u", $annotation->value, 2); switch (count($values)) { case 2: $this->__updateCall["method"] = $values[0]; $this->__updateCall["uri"] = $values[1]; break; case 1: $this->__updateCall["method"] = $values[0]; $this->__updateCall["uri"] = ""; break; } } break; case "UpdateGetFields": if ($annotation->value) { foreach (preg_split("/\\s+/u", $annotation->value) as $property) { if (in_array($property, $this->__properties)) { $this->__updateCall["get"][] = $property; } } } break; case "UpdatePostFields": if ($annotation->value) { foreach (preg_split("/\\s+/u", $annotation->value) as $property) { if (in_array($property, $this->__properties)) { $this->__updateCall["post"][] = $property; } } } break; case "UpdateBody": switch (strtolower($annotation->value)) { case "xml": $this->__updateCall["bodyType"] = "xml"; break; case "json": $this->__updateCall["bodyType"] = "json"; break; case "php": $this->__updateCall["bodyType"] = "php"; break; default: throw new Exception("Invalid update call " . "body type: " . $annotation->value); } break; case "Delete": if ($annotation->value) { $values = preg_split("/\\s+/u", $annotation->value, 2); switch (count($values)) { case 2: $this->__deleteCall["method"] = $values[0]; $this->__deleteCall["uri"] = $values[1]; break; case 1: $this->__deleteCall["method"] = $values[0]; $this->__deleteCall["uri"] = ""; break; } } break; case "DeleteGetFields": if ($annotation->value) { foreach (preg_split("/\\s+/u", $annotation->value) as $property) { if (in_array($property, $this->__properties)) { $this->__deleteCall["get"][] = $property; } } } break; case "DeletePostFields": if ($annotation->value) { foreach (preg_split("/\\s+/u", $annotation->value) as $property) { if (in_array($property, $this->__properties)) { $this->__deleteCall["post"][] = $property; } } } break; case "DeleteBody": switch (strtolower($annotation->value)) { case "xml": $this->__deleteCall["bodyType"] = "xml"; break; case "json": $this->__deleteCall["bodyType"] = "json"; break; case "php": $this->__deleteCall["bodyType"] = "php"; break; default: throw new Exception("Invalid delete call " . "body type: " . $annotation->value); } break; case "Query": if ($annotation->value) { $values = preg_split("/\\s+/u", $annotation->value, 2); switch (count($values)) { case 2: $this->__queryCall["method"] = $values[0]; $this->__queryCall["uri"] = $values[1]; break; case 1: $this->__queryCall["method"] = $values[0]; $this->__queryCall["uri"] = ""; break; } } break; case "QueryGetFields": if ($annotation->value) { foreach (preg_split("/\\s+/u", $annotation->value) as $property) { if (in_array($property, $this->__properties)) { $this->__queryCall["get"][] = $property; } } } break; case "QueryPostFields": if ($annotation->value) { foreach (preg_split("/\\s+/u", $annotation->value) as $property) { if (in_array($property, $this->__properties)) { $this->__queryCall["post"][] = $property; } } } break; case "QueryBody": switch (strtolower($annotation->value)) { case "xml": $this->__queryCall["bodyType"] = "xml"; break; case "json": $this->__queryCall["bodyType"] = "json"; break; case "php": $this->__queryCall["bodyType"] = "php"; break; default: throw new Exception("Invalid query call " . "body type: " . $annotation->value); } break; case "QueryList": if ($annotation->value) { foreach (preg_split("/\\s+/u", $annotation->value) as $property) { if (in_array($property, $this->__properties)) { $this->__queryProperties[] = $property; } else { throw new Exception("Specified query property " . $property . " does not exist"); } } } break; case "QueryView": if ($annotation->value) { if (array_key_exists($annotation->value, $this->__views)) { $this->__queryView = $annotation->value; } } break; case "NoQuery": # Here for backward compatibility $this->__allowQuery = false; break; case "CachePrefix": if ($annotation->value) { $this->__cachePrefix = $annotation->value; } break; case "CacheTime": if ($annotation->value) { $this->__cacheTime = (int) $annotation->value; } break; case "VoidCacheEntry": if ($annotation->value) { $this->__voidCacheEntries[] = $annotation->value; } break; case "NoCache": $this->__noCache = true; break; } } # Make sure we have a Base URI if (!$this->__baseUri) { throw new Exception("BaseURI must be specified in " . get_class($this)); } # See if we had query properties if (!$this->__queryProperties) { $this->__queryProperties = $this->__properties; } # Store the record properties in the caches $parsed_annotations = array("baseUri" => $this->__baseUri, "resourceIds" => $this->__resourceIds, "createCall" => $this->__createCall, "readCall" => $this->__readCall, "updateCall" => $this->__updateCall, "deleteCall" => $this->__deleteCall, "allowCreate" => $this->__allowCreate, "allowRead" => $this->__allowRead, "allowUpdate" => $this->__allowUpdate, "allowDelete" => $this->__allowDelete, "allowQuery" => $this->__allowQuery, "queryProperties" => $this->__queryProperties, "queryView" => $this->__queryView, "readOnly" => $this->__readOnly, "cachePrefix" => $this->__cachePrefix, "cacheTime" => $this->__cacheTime, "voidCacheEntries" => $this->__voidCacheEntries, "noCache" => $this->__noCache); $execution_cache->set($cache_key, $parsed_annotations); $local_cache->set($cache_key, $parsed_annotations, Annotations::$cacheTime); } else { # Restore the property values $this->__baseUri = $parsed_annotations["baseUri"]; $this->__resourceIds = $parsed_annotations["resourceIds"]; $this->__createCall = $parsed_annotations["createCall"]; $this->__readCall = $parsed_annotations["readCall"]; $this->__updateCall = $parsed_annotations["updateCall"]; $this->__deleteCall = $parsed_annotations["deleteCall"]; $this->__allowCreate = $parsed_annotations["allowCreate"]; $this->__allowRead = $parsed_annotations["allowRead"]; $this->__allowUpdate = $parsed_annotations["allowUpdate"]; $this->__allowDelete = $parsed_annotations["allowDelete"]; $this->__allowQuery = $parsed_annotations["allowQuery"]; $this->__queryProperties = $parsed_annotations["queryProperties"]; $this->__queryView = $parsed_annotations["queryView"]; $this->__readOnly = $parsed_annotations["readOnly"]; $this->__cachePrefix = $parsed_annotations["cachePrefix"]; $this->__cacheTime = $parsed_annotations["cacheTime"]; $this->__voidCacheEntries = $parsed_annotations["voidCacheEntries"]; $this->__noCache = $parsed_annotations["noCache"]; } # See if the object we received was an object or array # TODO: override the __import class for this if (is_array($object) || is_object($object)) { # Separate the object's values into primary key and other values $id_values = array(); $values = array(); foreach ($object as $key => $value) { if (array_key_exists($key, $this->__alias)) { if (in_array($this->__alias[$key], $this->__resourceIds)) { # This is a primary key value $id_values[$key] = $value; } else { # This is another value $values[$key] = $value; } } else { # Store the value as-is $values[$key] = $value; } } # Load the resource id values $this->__enforceReadOnly = false; $this->__import($id_values, $view, $strict); # Get the record $this->getRecord(); $this->__enforceReadOnly = true; # Save the initial values foreach ($this->__properties as $property) { $this->__defaultValues[$property] = $this->{$property}; } # Set the other properties $this->__import($values, $view, $strict); } else { $this->__createMode = true; $this->__enforceReadOnly = false; # Save the initial values foreach ($this->__properties as $property) { $this->__defaultValues[$property] = $this->{$property}; } } }
/** * 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"]; } }
/** * Stores the Security object and initializes the REST request * * @history * 2013.09.30: * (AT) Initial release * * @version 2013.09.30 * @author (AT) Alberto Trevino, Brigham Young Univ. <*****@*****.**> * * @todo Make sure the code works in CGI and Windows environments * * @param iSecurity $security Reference to Security context */ public function __construct(iSecurity $security) { # Call the parent constructor parent::__construct(); # Store the security object $this->security = $security; # Create a new local cache $this->localCache = CacheFactory::getLocalCache(); }