/** * Generates cacheable url to method mapping * * @param string $className * @param string $resourcePath */ protected function generateMap($className, $resourcePath = '') { /* * Mapping Rules - Optional parameters should not be mapped to URL - if * a required parameter is of primitive type - Map them to URL - Do not * create routes with out it - if a required parameter is not primitive * type - Do not include it in URL */ $reflection = new ReflectionClass($className); $classMetadata = CommentParser::parse($reflection->getDocComment()); $methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC + ReflectionMethod::IS_PROTECTED); foreach ($methods as $method) { $methodUrl = strtolower($method->getName()); //method name should not begin with _ if ($methodUrl[0] == '_') { continue; } $doc = $method->getDocComment(); $metadata = CommentParser::parse($doc) + $classMetadata; //@access should not be private if (isset($metadata['access']) && $metadata['access'] == 'private') { continue; } $arguments = array(); $defaults = array(); $params = $method->getParameters(); $position = 0; $ignorePathTill = false; $allowAmbiguity = isset($metadata['smart-auto-routing']) && $metadata['smart-auto-routing'] != 'true' || !Defaults::$smartAutoRouting; $metadata['resourcePath'] = $resourcePath; if (isset($classMetadata['description'])) { $metadata['classDescription'] = $classMetadata['description']; } if (isset($classMetadata['classLongDescription'])) { $metadata['classLongDescription'] = $classMetadata['longDescription']; } if (!isset($metadata['param'])) { $metadata['param'] = array(); } foreach ($params as $param) { $type = $param->isArray() ? 'array' : $param->getClass(); if ($type instanceof ReflectionClass) { $type = $type->getName(); } $arguments[$param->getName()] = $position; $defaults[$position] = $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null; if (!isset($metadata['param'][$position])) { $metadata['param'][$position] = array(); } $m =& $metadata['param'][$position]; if (isset($type)) { $m['type'] = $type; } $m['name'] = trim($param->getName(), '$ '); $m['default'] = $defaults[$position]; $m['required'] = !$param->isOptional(); if (isset($m[CommentParser::$embeddedDataName]['from'])) { $from = $m[CommentParser::$embeddedDataName]['from']; } else { if (isset($type) && Util::isObjectOrArray($type) || $param->getName() == Defaults::$fullRequestDataName) { $from = 'body'; } elseif ($m['required']) { $from = 'path'; } else { $from = 'query'; } } $m['from'] = $from; if (!$allowAmbiguity && $from == 'path') { $ignorePathTill = $position + 1; } $position++; } $accessLevel = 0; if ($method->isProtected()) { $accessLevel = 3; } elseif (isset($metadata['access'])) { if ($metadata['access'] == 'protected') { $accessLevel = 2; } elseif ($metadata['access'] == 'hybrid') { $accessLevel = 1; } } elseif (isset($metadata['protected'])) { $accessLevel = 2; } /* echo " access level $accessLevel for $className::" .$method->getName().$method->isProtected().PHP_EOL; */ // take note of the order $call = array('className' => $className, 'path' => rtrim($resourcePath, '/'), 'methodName' => $method->getName(), 'arguments' => $arguments, 'defaults' => $defaults, 'metadata' => $metadata, 'accessLevel' => $accessLevel); // if manual route if (preg_match_all('/@url\\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)' . '[ \\t]*\\/?(\\S*)/s', $doc, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { $httpMethod = $match[1]; $url = rtrim($resourcePath . $match[2], '/'); $this->routes[$httpMethod][$url] = $call; } //if auto route enabled, do so } elseif (Defaults::$autoRoutingEnabled) { // no configuration found so use convention if (preg_match_all('/^(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)/i', $methodUrl, $matches)) { $httpMethod = strtoupper($matches[0][0]); $methodUrl = substr($methodUrl, strlen($httpMethod)); } else { $httpMethod = 'GET'; } if ($methodUrl == 'index') { $methodUrl = ''; } $url = empty($methodUrl) ? rtrim($resourcePath, '/') : $resourcePath . $methodUrl; if (!$ignorePathTill) { $this->routes[$httpMethod][$url] = $call; } $position = 1; foreach ($params as $param) { $from = $metadata['param'][$position - 1]['from']; if ($from == 'body' && ($httpMethod == 'GET' || $httpMethod == 'DELETE')) { $from = $metadata['param'][$position - 1]['from'] = 'query'; } if (!$allowAmbiguity && $from != 'path') { break; } if (!empty($url)) { $url .= '/'; } $url .= '{' . $param->getName() . '}'; if ($allowAmbiguity || $position == $ignorePathTill) { $this->routes[$httpMethod][$url] = $call; } $position++; } } } }
/** * Get the type and associated model * * @param ReflectionClass $class * @param array $scope * * @throws RestException * @throws \Exception * @return array * * @access protected */ protected static function getTypeAndModel(ReflectionClass $class, array $scope, $prefix = '', array $rules = array()) { $className = $class->getName(); $dataName = CommentParser::$embeddedDataName; if (isset(static::$models[$prefix . $className])) { return static::$models[$prefix . $className]; } $children = array(); try { if ($magic_properties = static::parseMagic($class, empty($prefix))) { foreach ($magic_properties as $prop) { if (!isset($prop['name'])) { throw new Exception('@property comment is not properly defined in ' . $className . ' class'); } if (!isset($prop[$dataName]['label'])) { $prop[$dataName]['label'] = Text::title($prop['name']); } if (isset(static::$fieldTypesByName[$prop['name']]) && $prop['type'] == 'string' && !isset($prop[$dataName]['type'])) { $prop[$dataName]['type'] = static::$fieldTypesByName[$prop['name']]; } $children[$prop['name']] = $prop; } } else { $props = $class->getProperties(ReflectionProperty::IS_PUBLIC); foreach ($props as $prop) { $name = $prop->getName(); $child = array('name' => $name); if ($c = $prop->getDocComment()) { $child += Util::nestedValue(CommentParser::parse($c), 'var') ?: []; } else { $o = $class->newInstance(); $p = $prop->getValue($o); if (is_object($p)) { $child['type'] = get_class($p); } elseif (is_array($p)) { $child['type'] = 'array'; if (count($p)) { $pc = reset($p); if (is_object($pc)) { $child['contentType'] = get_class($pc); } } } } $child += array('type' => isset(static::$fieldTypesByName[$child['name']]) ? static::$fieldTypesByName[$child['name']] : 'string', 'label' => Text::title($child['name'])); isset($child[$dataName]) ? $child[$dataName] += array('required' => true) : ($child[$dataName]['required'] = true); if ($prop->class != $className && ($qualified = Scope::resolve($child['type'], $scope))) { list($child['type'], $child['children']) = static::getTypeAndModel(new ReflectionClass($qualified), $scope); } elseif (($contentType = Util::nestedValue($child, $dataName, 'type')) && ($qualified = Scope::resolve($contentType, $scope))) { list($child['contentType'], $child['children']) = static::getTypeAndModel(new ReflectionClass($qualified), $scope); } $children[$name] = $child; } } } catch (Exception $e) { if (Text::endsWith($e->getFile(), 'CommentParser.php')) { throw new RestException(500, "Error while parsing comments of `{$className}` class. " . $e->getMessage()); } throw $e; } if ($properties = Util::nestedValue($rules, 'properties')) { if (is_string($properties)) { $properties = array($properties); } $c = array(); foreach ($properties as $property) { if (isset($children[$property])) { $c[$property] = $children[$property]; } } $children = $c; } if ($required = Util::nestedValue($rules, 'required')) { //override required on children if (is_bool($required)) { // true means all are required false means none are required $required = $required ? array_keys($children) : array(); } elseif (is_string($required)) { $required = array($required); } $required = array_fill_keys($required, true); foreach ($children as $name => $child) { $children[$name][$dataName]['required'] = isset($required[$name]); } } static::$models[$prefix . $className] = array($className, $children, $prefix . $className); return static::$models[$prefix . $className]; }
<?php /** * Restler 2 compatibility mode enabler */ use Luracast\Restler\Defaults; use Luracast\Restler\AutoLoader; use Luracast\Restler\CommentParser; //changes in auto loading $classMap = array(); //find lowercase php files representing a class/interface foreach (explode(PATH_SEPARATOR, get_include_path()) as $path) { foreach (new DirectoryIterator($path) as $fileInfo) { if ($fileInfo->isFile() && 'php' === $fileInfo->getExtension() && ctype_lower($fileInfo->getBasename('.php')) && preg_match('/^ *(class|interface|abstract +class)' . ' +([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)/m', file_get_contents($fileInfo->getPathname()), $matches)) { $classMap[$matches[2]] = $fileInfo->getPathname(); } } } AutoLoader::seen($classMap); //changes in iAuthenticate Defaults::$authenticationMethod = '__isAuthenticated'; include __DIR__ . '/iAuthenticate.php'; //changes in auto routing Defaults::$smartAutoRouting = false; Defaults::$smartParameterParsing = false; Defaults::$autoValidationEnabled = false; //changes in parsing embedded data in comments CommentParser::$embeddedDataPattern = '/\\((\\S+)\\)/ms'; CommentParser::$embeddedDataIndex = 1;
protected function _model($className, $instance = null) { $id = $this->_noNamespace($className); if (isset($this->_models->{$id})) { return; } $properties = array(); if (!$instance) { if (!class_exists($className)) { return; } $instance = new $className(); } $data = get_object_vars($instance); $reflectionClass = new \ReflectionClass($className); foreach ($data as $key => $value) { $propertyMetaData = null; try { $property = $reflectionClass->getProperty($key); if ($c = $property->getDocComment()) { $propertyMetaData = Util::nestedValue(CommentParser::parse($c), 'var'); } } catch (\ReflectionException $e) { } if (is_null($propertyMetaData)) { $type = $this->getType($value, true); $description = ''; } else { $type = Util::nestedValue($propertyMetaData, 'type') ?: $this->getType($value, true); $description = Util::nestedValue($propertyMetaData, 'description') ?: ''; if (class_exists($type)) { $this->_model($type); $type = $this->_noNamespace($type); } } if (isset(static::$dataTypeAlias[$type])) { $type = static::$dataTypeAlias[$type]; } $properties[$key] = array('type' => $type, 'description' => $description); if (Util::nestedValue($propertyMetaData, CommentParser::$embeddedDataName, 'required')) { $properties[$key]['required'] = true; } if ($type == 'Array') { $itemType = Util::nestedValue($propertyMetaData, CommentParser::$embeddedDataName, 'type') ?: (count($value) ? $this->getType(end($value), true) : 'string'); if (class_exists($itemType)) { $this->_model($itemType); $itemType = $this->_noNamespace($itemType); } $properties[$key]['item'] = array('type' => $itemType); } else { if (preg_match('/^Array\\[(.+)\\]$/', $type, $matches)) { $itemType = $matches[1]; $properties[$key]['type'] = 'Array'; $properties[$key]['item']['type'] = $itemType; if (class_exists($itemType)) { $this->_model($itemType); } } } } if (!empty($properties)) { $model = new stdClass(); $model->id = $id; $model->properties = $properties; $this->_models->{$id} = $model; } }
/** * Get the type and associated model * * @param ReflectionClass $class * @param array $scope * * @throws RestException * @throws \Exception * @return array * * @access protected */ protected static function getTypeAndModel(ReflectionClass $class, array $scope) { $className = $class->getName(); if (isset(static::$models[$className])) { return static::$models[$className]; } $children = array(); try { $props = $class->getProperties(ReflectionProperty::IS_PUBLIC); foreach ($props as $prop) { $name = $prop->getName(); $child = array('name' => $name); if ($c = $prop->getDocComment()) { $child += Util::nestedValue(CommentParser::parse($c), 'var') ?: []; } else { $o = $class->newInstance(); $p = $prop->getValue($o); if (is_object($p)) { $child['type'] = get_class($p); } elseif (is_array($p)) { $child['type'] = 'array'; if (count($p)) { $pc = reset($p); if (is_object($pc)) { $child['contentType'] = get_class($pc); } } } } $child += array('type' => $child['name'] == 'email' ? 'email' : 'string', 'label' => static::label($child['name'])); isset($child[CommentParser::$embeddedDataName]) ? $child[CommentParser::$embeddedDataName] += array('required' => true) : ($child[CommentParser::$embeddedDataName]['required'] = true); if ($qualified = Scope::resolve($child['type'], $scope)) { list($child['type'], $child['children']) = static::getTypeAndModel(new ReflectionClass($qualified), $scope); } elseif (($contentType = Util::nestedValue($child, CommentParser::$embeddedDataName, 'type')) && ($qualified = Scope::resolve($contentType, $scope))) { list($child['contentType'], $child['children']) = static::getTypeAndModel(new ReflectionClass($qualified), $scope); } $children[$name] = $child; } } catch (Exception $e) { if (String::endsWith($e->getFile(), 'CommentParser.php')) { throw new RestException(500, "Error while parsing comments of `{$className}` class. " . $e->getMessage()); } throw $e; } static::$models[$className] = array($className, $children); return static::$models[$className]; }
/** * @param ReflectionClass $class * * @return array * * @access protected */ protected static function getTypeAndModel(ReflectionClass $class) { $children = array(); $props = $class->getProperties(ReflectionProperty::IS_PUBLIC); foreach ($props as $prop) { if ($c = $prop->getDocComment()) { $children[$prop->getName()] = array('name' => $prop->getName()) + Util::nestedValue(CommentParser::parse($c), 'var') + array('type' => 'string'); } } return array($class->getName(), $children); }
private function _model($className, $instance = null) { $properties = array(); $reflectionClass = new \ReflectionClass($className); if (!$instance) { $instance = new $className(); } $data = get_object_vars($instance); foreach ($data as $key => $value) { $propertyMetaData = null; try { $property = $reflectionClass->getProperty($key); if ($c = $property->getDocComment()) { $propertyMetaData = CommentParser::parse($c); } } catch (\ReflectionException $e) { } if ($propertyMetaData !== null) { $type = isset($propertyMetaData['var']) ? $propertyMetaData['var'] : 'string'; $description = @$propertyMetaData['description'] ?: ''; if (class_exists($type)) { $this->_model($type); } } else { $type = $this->getType($value, true); $description = ''; } if (isset(static::$dataTypeAlias[$type])) { $type = static::$dataTypeAlias[$type]; } $properties[$key] = array('type' => $type, 'description' => $description); if ($type == 'Array') { $itemType = count($value) ? $this->getType($value[0], true) : 'string'; $properties[$key]['item'] = array('type' => $itemType); } else { if (preg_match('/^Array\\[(.+)\\]$/', $type, $matches)) { $itemType = $matches[1]; $properties[$key]['type'] = 'Array'; $properties[$key]['item']['type'] = $itemType; if (class_exists($itemType)) { $this->_model($itemType); } } } } if (!empty($properties)) { $id = $this->_noNamespace($className); $model = new stdClass(); $model->id = $id; $model->properties = $properties; $this->_models->{$id} = $model; } }