From 687607f9210ff3af17649e83374886781863004e Mon Sep 17 00:00:00 2001 From: merijnvdk Date: Tue, 19 Dec 2017 12:49:43 +0100 Subject: [PATCH] Fixes and extra features added over the years Allow creation of webservices with mapped class names, so we do not expose internal class names. - WsdlDefinition: add new method setTypeMapping() and getTypeMapping(). The typeMapping is an assoc array with as key a php class name and as value the new type name. The mapping is stored in this wsdl definition in a new private property. - WsdlWriter: now in several places pass the new classmapping from the wsdl definition to sub-modules. - WsdlType: -- add new private property orgClassName, it will be set from a new, optional, constructor param. -- made the private static method stripArrayNotation public (static). No idea why it was private, which is weird for a static method anyway. -- findComplexTypes: because it tries to reflect the types (which already have been possibly mapped) we need to revert the mapping just for the reflection. So the method has a new optional param with the class mapping. -- getComplexTypes: it calls the above method and aslo creates WsdlType objects so it needs to use mapped classnames (in fact to find the original php classname). -- getProperties: because the method tries to reflect the type, it needs the original php classname. - WsdlMethod: parameter types and return types will be mapped from php classnames to chosen type names when the (new) setTypeMappings() method has been called with a mapping. We use some WsdlType static methods to detect array types because arrays are postfixed with [] array notation, which ofcource cannot be found in the mapping array. So we need to strip array notation, look up in mapping and ad it again if needed. Remove function split() for improved PHP compatibility. Added parsing of docblocks for class properties, now also detects @internal tag so properties can be optional in the wsdl. Added support for optional properties. When the docblock of a class property contains "@internal optional" then in the wsdl the following property is added: minOccurs="0" Change the soapaction URI format to be conform rfc Added binary, float,date/time primitive types to WsdlType class. Allow '@internal ignore' docblock var. any method containing this will not be exposed, even if its public. The wsdlDefinition can now set the className for situations where classname and classfile are different. --- classes/BaseWsdlWriter.php | 20 ++++++--- classes/DocBlockParser.php | 43 ++++++++++++------- classes/WsdlDefinition.php | 50 +++++++++++++++++++++- classes/WsdlMethod.php | 36 +++++++++++++++- classes/WsdlProperty.php | 6 +++ classes/WsdlType.php | 85 ++++++++++++++++++++++++-------------- classes/WsdlWriter.php | 31 ++++++++------ 7 files changed, 205 insertions(+), 66 deletions(-) diff --git a/classes/BaseWsdlWriter.php b/classes/BaseWsdlWriter.php index 3d13cc9..3a6d055 100644 --- a/classes/BaseWsdlWriter.php +++ b/classes/BaseWsdlWriter.php @@ -154,8 +154,10 @@ private function addType(WsdlType &$wsdlType) $wsdlSequence = $this->createElement("sequence"); // Create WSDL Elements + /* @var $elements WsdlProperty[] */ $elements = $wsdlType->getElements(); foreach ($elements as &$element) { + /* @var $element WsdlProperty */ $wsdlElement = $this->createElement("element"); $wsdlElement->setAttribute("name", $element->name); if (WsdlType::isPrimitiveType($element->type)) { @@ -163,7 +165,10 @@ private function addType(WsdlType &$wsdlType) } else { $wsdlElement->setAttribute("type", "tns:" . $element->type); } - + if($element->usage && $element->usage=='optional'){ + $wsdlElement->setAttribute("minOccurs", "0"); + } + // Add WSDL Element to the Sequence $wsdlSequence->appendChild($wsdlElement); } @@ -268,8 +273,8 @@ private function addBindingOperation($operation) $binding = $this->getBinding(); $definition = $this->getDefinitionName(); - $soapAction = "urn:{$definition}#{$definition}Server#{$operation->name}"; - + $soapAction = $this->getWsdlDefinition()->getNameSpace()."{$operation->name}"; + // Create a new WSDL Operation $wsdlOperation = $this->createElement("operation"); $wsdlOperation->setAttribute("name", $operation->name); @@ -583,9 +588,12 @@ public function getDefinitionName() */ public function getClassName() { - $className = $this->getWsdlDefinition()->getClassFileName(); - $className = basename($className, ".inc"); - $className = basename($className, ".php"); + $className = $this->getWsdlDefinition()->getClassName(); + if(!$className){ + $className = $this->getWsdlDefinition()->getClassFileName(); + $className = basename($className, ".inc"); + $className = basename($className, ".php"); + } return $className; } diff --git a/classes/DocBlockParser.php b/classes/DocBlockParser.php index 3a5ce6b..1cb37f5 100644 --- a/classes/DocBlockParser.php +++ b/classes/DocBlockParser.php @@ -22,11 +22,11 @@ class DocBlockParser private static $docBlockTags = array( "@param" => array( - "regex" => "|@param\s+([a-zA-Z0-9\[\]]+)\s+\\\$([a-zA-Z0-9]+)\s+(.*)|", - "matches" => array("type", "name", "desc") + "regex" => "#@param\s+([_a-zA-Z0-9\[\]]+)\s+\\\$([_a-zA-Z0-9]+)(\=null|)\s+(.*)#", + "matches" => array("type", "name", "optional", "desc") ), "@return" => array( - "regex" => "|@return\s+([a-zA-Z0-9\[\]]+)\s+(.*)|", + "regex" => "|@return\s+([_a-zA-Z0-9\[\]]+)\s+(.*)|", "matches" => array("type", "desc") ), "@author" => array( @@ -37,14 +37,16 @@ class DocBlockParser "regex" => "|@internal\s+(.*)|", "matches" => array("usage") ), + "@var" => array( + "regex" => "|@var\s+([_a-zA-Z0-9\[\]]+)\s+(.*)|", + "matches" => array("type", "desc") + ) ); /** * Get the Type and Description Information for a Property * - * @param string $name The name of the Property - * @param string $className the name file which contains - * the Class that the property is defined + * @param ReflectionProperty $property The reflected property */ public static function getPropertyInfo(ReflectionProperty $property) { @@ -52,12 +54,23 @@ public static function getPropertyInfo(ReflectionProperty $property) $commentLine = $property->getDocComment(); if (strlen($commentLine)) { - if (preg_match("|/\*\*\s+@var\s+([a-zA-Z0-9\[\]]+)([^(\*/)]*)\*/|", $commentLine, $matches)) { - $info['type'] = $matches[1]; - if (isset($matches[2])) { - $info['desc'] = $matches[2]; + // parse docblock + $taginfoarr=self::getTagInfo($commentLine); + $info=array(); + // flatten the parsed data + foreach($taginfoarr as $taginfo){ + foreach($taginfo as $tag => $tagdata){ + foreach($tagdata as $key => $value){ + if($value){ + // trim value because it can contain newlines etc + $info[$key]=trim($value); + } } - return $info; + } + } + // if we found the type, return + if(isset($info['type'])){ + return $info; } } return null; @@ -72,7 +85,7 @@ public static function getPropertyInfo(ReflectionProperty $property) public static function getDescription($doc) { $tagRegex = self::getTagRegex(); - $lines = split("\n", self::stripCommentChars($doc)); + $lines = explode("\n", self::stripCommentChars($doc)); $desc = ""; foreach ($lines as $line) { @@ -101,8 +114,8 @@ public static function getTagInfo($doc) $currentTag = null; // Do a Line at a time - $lines = split("\n", self::stripCommentChars($doc)); - + $lines = explode("\n", self::stripCommentChars($doc)); + for ($i = 0, $c = count($lines); $i < $c; $i++) { // Loop through the Doc Tag list and Find Matches @@ -156,7 +169,7 @@ public static function getTagInfo($doc) private static function stripCommentChars($doc) { $out = ""; - $lines = split("\n", $doc); + $lines = explode("\n", $doc); foreach ($lines as $line) { $line = preg_replace("|^\s*/\**|", "", $line); $line = preg_replace("|^\s*\**|", "", $line); diff --git a/classes/WsdlDefinition.php b/classes/WsdlDefinition.php index 01623c5..7dee006 100644 --- a/classes/WsdlDefinition.php +++ b/classes/WsdlDefinition.php @@ -26,7 +26,10 @@ class WsdlDefinition /** @var string */ private $classFileName; + /** @var string */ + private $className; + /** @var string */ private $wsdlFileName; @@ -46,8 +49,10 @@ class WsdlDefinition /** @var string */ private $baseUrl; - - + /** @var string[] */ + private $typeMapping = array(); + + /** * Get the value of classFileName * @@ -58,6 +63,26 @@ public function getClassFileName() return $this->classFileName; } + /** + * Set the value of className + * + * @param string $className The value of className + */ + public function setClassName($className) + { + $this->className = $className; + } + + /** + * Get the value of className + * + * @return string The value of className + */ + public function getClassName() + { + return $this->className; + } + /** * Set the value of classFileName * @@ -174,4 +199,25 @@ public function setBaseUrl($baseUrl) $this->baseUrl = $baseUrl; } + + /** + * Get the value of typeMapping + * + * @return string[] The typeMapping as assoc array + */ + public function getTypeMapping() + { + return $this->typeMapping; + } + + /** + * Set the value of typeMapping + * + * @param string[] $mapping The value of typeMapping + */ + public function setTypeMapping($mapping) + { + $this->typeMapping = $mapping; + } + } diff --git a/classes/WsdlMethod.php b/classes/WsdlMethod.php index b8503ba..f6251db 100644 --- a/classes/WsdlMethod.php +++ b/classes/WsdlMethod.php @@ -10,6 +10,7 @@ */ require_once("WsdlPart.php"); +require_once("WsdlType.php"); /** * WSDL Generator for PHP5 @@ -25,6 +26,8 @@ class WsdlMethod private $desc = null; private $header = false; private $reqHeaders = array(); + + private $typeMapping = array(); private $params = array(); private $returnType = null; @@ -60,6 +63,16 @@ public function setDesc($desc) $this->desc = $desc; } + /** + * Set the Description of the Method + * + * @param string[] $mapping Assoc array with type mapping old => new + */ + public function setTypeMappings($mapping) + { + $this->typeMapping = $mapping; + } + /** * Set whether the Method represents a SOAP Body or Header element * @@ -122,7 +135,17 @@ public function resolveHeaders($wsdlMethods) public function addParameter($varType, $varName, $varDesc) { $param = new StdClass(); - + if (WsdlType::isArrayTypeClass($varType)) { + $varTypeCheck = WsdlType::stripArrayNotation($varType); + if(array_key_exists($varTypeCheck,$this->typeMapping)){ + $varType = $this->typeMapping[$varTypeCheck].'[]'; + } + }else{ + if(array_key_exists($varType,$this->typeMapping)){ + $varType = $this->typeMapping[$varType]; + } + } + $param->type = $varType; $param->name = $varName; $param->desc = $varDesc; @@ -138,6 +161,17 @@ public function addParameter($varType, $varName, $varDesc) */ public function setReturn($varType, $varDesc) { + if (WsdlType::isArrayTypeClass($varType)) { + $varTypeCheck = WsdlType::stripArrayNotation($varType); + if(array_key_exists($varTypeCheck,$this->typeMapping)){ + $varType = $this->typeMapping[$varTypeCheck].'[]'; + } + }else{ + if(array_key_exists($varType,$this->typeMapping)){ + $varType = $this->typeMapping[$varType]; + } + } + $this->returnType = $varType; $this->returnDesc = $varDesc; } diff --git a/classes/WsdlProperty.php b/classes/WsdlProperty.php index bfe76ed..e96f99f 100644 --- a/classes/WsdlProperty.php +++ b/classes/WsdlProperty.php @@ -23,6 +23,7 @@ class WsdlProperty public $type = null; public $name = null; public $description = null; + public $usage = null; public function setName($name) @@ -45,4 +46,9 @@ public function setDesc($desc) $this->description = $desc; } + public function setUsage($usage) + { + $this->usage = $usage; + } + } diff --git a/classes/WsdlType.php b/classes/WsdlType.php index a744c7b..6624a5e 100644 --- a/classes/WsdlType.php +++ b/classes/WsdlType.php @@ -20,12 +20,13 @@ */ class WsdlType { - + // see http://www.w3schools.com/Schema/schema_dtypes_string.asp for starting point on data types // The List of Primitive WSDL Types - private static $primitives = array('string', 'int', 'array', 'boolean', 'base64binary' ); - private static $badTypes = array('mixed', ''); - + private static $primitives = array("string", "int", "array", "boolean", "base64binary", "binary", "float", "date", "time", "datetime" ); + private static $badTypes = array("mixed", ""); + private $className = null; + private $orgClassName = null; private $properties = array(); private $isArray = false; @@ -34,13 +35,22 @@ class WsdlType * The Name of the Class * * + * @param string $className The Name of the Type + * @param string $unmappedClassName If the name of the type was mapped, set this to the original name */ - public function __construct($className) + public function __construct($className, $unmappedClassName=null) { if (self::isArrayTypeClass($className)) { $className = self::stripArrayNotation($className); $this->isArray = true; } + if(!is_null($unmappedClassName)){ + if($this->isArray){ + $this->orgClassName = self::stripArrayNotation($unmappedClassName); + }else{ + $this->orgClassName = $unmappedClassName; + } + } $this->className = $className; } @@ -74,6 +84,19 @@ public static function getArrayComplexTypeName($typeName) return "ArrayOf{$typeName}"; } + + /** + * Strip the Array syntax from the Class Name + * + * @param string $className The Array Type Class Name + * @return string The class name without the array notation + */ + public static function stripArrayNotation($className) + { + return substr($className, 0, (strlen($className) - 2)); + } + + /** * Get the Name of the Type @@ -116,7 +139,7 @@ public function getArrayTypeName() /** * Get The Elements for this Type * - * @return array The list of element objects + * @return WsdlProperty[] The list of element objects */ public function getElements() { @@ -179,12 +202,14 @@ public static function isPrimitiveType($type) * array of WsdlTypes for them... * * @param array The array of WsdlMethods + * @param array Optional assoc array of classmappings * @return array The array of WsdlTypes */ - public static function getComplexTypes(&$wsdlMethods) + public static function getComplexTypes(&$wsdlMethods, $classMappings=array()) { $wsdlTypes = array(); $argumentTypes = array(); + $invertedClassMappings = array_flip($classMappings); // Find the types used as arguments and return parameters to each SOAP method foreach ($wsdlMethods as &$method) { @@ -207,11 +232,15 @@ public static function getComplexTypes(&$wsdlMethods) // Recurse through each argument and return parameter for each method // to build a complete list of complexTypes required by all methods - $complexTypes = self::findComplexTypes($argumentTypes, array()); - + $complexTypes = self::findComplexTypes($argumentTypes, array(), $invertedClassMappings); + // Create WSDL type list foreach ($complexTypes as $complexType) { - $wsdlTypes[] = new WsdlType($complexType); + $orgClassName = null; + if(array_key_exists($complexType,$invertedClassMappings)){ + $orgClassName = $invertedClassMappings[$complexType]; + } + $wsdlTypes[] = new WsdlType($complexType, $orgClassName); } return $wsdlTypes; } @@ -225,11 +254,12 @@ public static function getComplexTypes(&$wsdlMethods) /** * Get the WSDL Properties from the Class File * - * @return array An array of properties + * @return WsdlProperty[] An array of properties */ private function getProperties() { - $reflect = new ReflectionClass($this->className); + $classname = (is_null($this->orgClassName)? $this->className : $this->orgClassName); + $reflect = new ReflectionClass($classname); $properties = $reflect->getProperties(); $wsdlProperties = array(); @@ -247,7 +277,7 @@ private function getProperties() * Get information for a Property * * @param ReflectionProperty $property The property to get Information for - * @return WsdlService The Service Information object + * @return WsdlProperty The Service Information object */ private static function getWsdlProperty(ReflectionProperty $property) { @@ -262,23 +292,16 @@ private static function getWsdlProperty(ReflectionProperty $property) $wsdlProperty->setName($property->getName()); $wsdlProperty->setType($propertyInfo['type']); $wsdlProperty->setDesc($propertyInfo['desc']); + if (isset($propertyInfo['optional'])){ + $wsdlProperty->setUsage("optional"); + } + if (isset($propertyInfo['usage'])){ + $wsdlProperty->setUsage($propertyInfo['usage']); + } return $wsdlProperty; } - - /** - * Strip the Array syntax from the Class Name - * - * @param string $className The Array Type Class Name - * @return string The class name without the array notation - */ - private static function stripArrayNotation($className) - { - return substr($className, 0, (strlen($className) - 2)); - } - - /** * Find all types needed to fully define the specified types and their properties * @@ -290,9 +313,10 @@ private static function stripArrayNotation($className) * * @param array $types List of types to iterate over * @param array $foundTypes List of types discovered so far + * @param array $invertedClassMappings Assoc array of class mappings $type => $org_classname * @return array List of types required to fully define all the types in $types */ - private static function findComplexTypes(&$types, $foundTypes) + private static function findComplexTypes(&$types, $foundTypes, $invertedClassMappings=array()) { // Iterate over each type foreach ($types as $type) { @@ -309,7 +333,8 @@ private static function findComplexTypes(&$types, $foundTypes) // Process objects only - not arrays if (!self::isArrayTypeClass($type)) { - $reflect = new ReflectionClass($type); + $classname = (array_key_exists($type,$invertedClassMappings)? $invertedClassMappings[$type] : $type); + $reflect = new ReflectionClass($classname); $properties = $reflect->getProperties(); $searchTypes = array(); @@ -322,7 +347,7 @@ private static function findComplexTypes(&$types, $foundTypes) // Iterate over all the found types to search their properties etc. if (count($searchTypes)) { - $foundTypes = self::findComplexTypes($searchTypes, $foundTypes); + $foundTypes = self::findComplexTypes($searchTypes, $foundTypes, $invertedClassMappings); } } @@ -331,7 +356,7 @@ private static function findComplexTypes(&$types, $foundTypes) // referenced anywhere else else { $searchTypes = array(self::stripArrayNotation($type)); - $foundTypes = self::findComplexTypes($searchTypes, $foundTypes); + $foundTypes = self::findComplexTypes($searchTypes, $foundTypes, $invertedClassMappings); } } } diff --git a/classes/WsdlWriter.php b/classes/WsdlWriter.php index bf1a020..20c8d34 100644 --- a/classes/WsdlWriter.php +++ b/classes/WsdlWriter.php @@ -47,7 +47,7 @@ public function classToWsdl() $wsdlMethods = $this->getMethods($className); // Find the Complex Types - $complexTypes = WsdlType::getComplexTypes($wsdlMethods); + $complexTypes = WsdlType::getComplexTypes($wsdlMethods, $this->getWsdlDefinition()->getTypeMapping()); foreach ($complexTypes as &$complexType) { $this->addComplexType($complexType); @@ -79,7 +79,10 @@ private function getMethods($className) || substr($method->getName(), 0, 2) == '__') { continue; } - $wsdlMethods[] = $this->getWsdlMethod($method); + // print "Found Method: " . $method->getName() . "\n"; + $wsdlmethod=$this->getWsdlMethod($method); + if(is_null($wsdlmethod)) continue; + $wsdlMethods[] = $wsdlmethod; } foreach ($wsdlMethods as $wsdlMethod) @@ -101,7 +104,8 @@ private function getWsdlMethod(ReflectionMethod $method) $wsdlMethod = new WsdlMethod(); $wsdlMethod->setName($method->getName()); $wsdlMethod->setDesc(DocBlockParser::getDescription($doc)); - + $wsdlMethod->setTypeMappings($this->getWsdlDefinition()->getTypeMapping()); + $params = DocBlockParser::getTagInfo($doc); for ($i = 0, $c = count($params); $i < $c; $i++) { @@ -114,15 +118,18 @@ private function getWsdlMethod(ReflectionMethod $method) break; case "@return": $wsdlMethod->setReturn($param['type'], $param['desc']); - break; - case "@internal": - if (trim(strtolower($param['usage'])) == 'soapheader') - $wsdlMethod->setIsHeader(true); - - if (substr(trim(strtolower($param['usage'])), 0, 13) == 'soaprequires ') - $wsdlMethod->setRequiredHeaders(explode(' ', substr(trim($param['usage']), 13))); - - break; + break; + case "@internal": + if (trim(strtolower($param['usage'])) == 'ignore') + return null; + + if (trim(strtolower($param['usage'])) == 'soapheader') + $wsdlMethod->setIsHeader(true); + + if (substr(trim(strtolower($param['usage'])), 0, 13) == 'soaprequires ') + $wsdlMethod->setRequiredHeaders(explode(' ', substr(trim($param['usage']), 13))); + + break; default: break; }