* @version $Id: WsdlGenerator.php 3314 2013-08-20 10:00:47Z ctrlaltca $ * @package System.Web.Services.SOAP */ require_once(dirname(__FILE__).'/Wsdl.php'); require_once(dirname(__FILE__).'/WsdlMessage.php'); require_once(dirname(__FILE__).'/WsdlOperation.php'); /** * Generator for the wsdl. * Special thanks to Cristian Losada for implementing the Complex Types section of the WSDL. * @author Marcus Nyeholt * @author Cristian Losada * @version $Revision$ */ class WsdlGenerator { /** * The instance. * var WsdlGenerator */ private static $instance; /** * The name of this service (the classname) * @var string */ private $serviceName = ''; /** * The complex types to use in the wsdl * @var Array */ private $types = array(); /** * The operations available in this wsdl * @var ArrayObject */ private $operations; /** * The wsdl object. * @var object */ private $wsdlDocument; /** * The actual wsdl string * @var string */ private $wsdl = ''; /** * The singleton instance for the generator */ public static function getInstance() { if (is_null(self::$instance)) { self::$instance = new WsdlGenerator(); } return self::$instance; } /** * Get the Wsdl generated * @return string The Wsdl for this wsdl */ public function getWsdl() { return $this->wsdl; } /** * Generates WSDL for a passed in class, and saves it in the current object. The * WSDL can then be retrieved by calling * @param string $className The name of the class to generate for * @param string $serviceUri The URI of the service that handles this WSDL * @param string $encoding character encoding. * @return void */ public function generateWsdl($className, $serviceUri='',$encoding='') { $this->wsdlDocument = new Wsdl($className, $serviceUri, $encoding); $classReflect = new ReflectionClass($className); $methods = $classReflect->getMethods(); foreach ($methods as $method) { // Only process public methods if ($method->isPublic()) { $this->processMethod($method); } } foreach($this->types as $type => $elements) { $this->wsdlDocument->addComplexType($type, $elements); } $this->wsdl = $this->wsdlDocument->getWsdl(); } /** * Static method that generates and outputs the generated wsdl * @param string $className The name of the class to export * @param string $serviceUri The URI of the service that handles this WSDL * @param string $encoding character encoding. */ public static function generate($className, $serviceUri='', $encoding='') { $generator = WsdlGenerator::getInstance(); $generator->generateWsdl($className, $serviceUri,$encoding); //header('Content-type: text/xml'); return $generator->getWsdl(); //exit(); } /** * Process a method found in the passed in class. * @param ReflectionMethod $method The method to process */ protected function processMethod(ReflectionMethod $method) { $comment = $method->getDocComment(); if (strpos($comment, '@soapmethod') === false) { return; } $comment = preg_replace("/(^[\\s]*\\/\\*\\*) |(^[\\s]\\*\\/) |(^[\\s]*\\*?\\s) |(^[\\s]*) |(^[\\t]*)/ixm", "", $comment); $comment = str_replace("\r", "", $comment); $comment = preg_replace("/([\\t])+/", "\t", $comment); $commentLines = explode("\n", $comment); $methodDoc = ''; $params = array(); $return = array(); $gotDesc = false; $gotParams = false; foreach ($commentLines as $line) { if ($line == '') continue; if ($line{0} == '@') { $gotDesc = true; if (preg_match('/^@param\s+([\w\[\]()]+)\s+\$([\w()]+)\s*(.*)/i', $line, $match)) { $param = array(); $param['type'] = $this->convertType($match[1]); $param['name'] = $match[2]; $param['desc'] = $match[3]; $params[] = $param; } else if (preg_match('/^@return\s+([\w\[\]()]+)\s*(.*)/i', $line, $match)) { $gotParams = true; $return['type'] = $this->convertType($match[1]); $return['desc'] = $match[2]; $return['name'] = 'return'; } } else { if (!$gotDesc) { $methodDoc .= trim($line); } else if (!$gotParams) { $params[count($params)-1]['desc'] .= trim($line); } else { if ($line == '*/') continue; $return['desc'] .= trim($line); } } } $methodName = $method->getName(); $operation = new WsdlOperation($methodName, $methodDoc); $operation->setInputMessage(new WsdlMessage($methodName.'Request', $params)); $operation->setOutputMessage(new WsdlMessage($methodName.'Response', array($return))); $this->wsdlDocument->addOperation($operation); } /** * Converts from a PHP type into a WSDL type. This is borrowed from * Cerebral Cortex (let me know and I'll remove asap). * * TODO: date and dateTime * @param string $type The php type to convert * @return string The XSD type. */ private function convertType($type) { switch ($type) { case 'string': case 'str': return 'xsd:string'; break; case 'int': case 'integer': return 'xsd:int'; break; case 'float': case 'double': return 'xsd:float'; break; case 'boolean': case 'bool': return 'xsd:boolean'; break; case 'date': return 'xsd:date'; break; case 'time': return 'xsd:time'; break; case 'dateTime': return 'xsd:dateTime'; break; case 'array': return 'soap-enc:Array'; break; case 'object': return 'xsd:struct'; break; case 'mixed': return 'xsd:anyType'; break; case 'void': return ''; default: if(strpos($type, '[]')) // if it is an array { $className = substr($type, 0, strlen($type) - 2); $type = $className . 'Array'; $this->types[$type] = ''; $this->convertType($className); } else { if(!isset($this->types[$type])) $this->extractClassProperties($type); } return 'tns:' . $type; } } /** * Extract the type and the name of all properties of the $className class and saves it in the $types array * This method extract properties from PHPDoc formatted comments for variables. Unfortunately the reflectionproperty * class doesn't have a getDocComment method to extract comments about it, so we have to extract the information * about the variables manually. Thanks heaps to Cristian Losada for implementing this. * @param string $className The name of the class */ private function extractClassProperties($className) { /** * modified by Qiang Xue, Jan. 2, 2007 * Using Reflection's DocComment to obtain property definitions * DocComment is available since PHP 5.1 */ $reflection = new ReflectionClass($className); $properties = $reflection->getProperties(); foreach($properties as $property) { $comment = $property->getDocComment(); if(strpos($comment, '@soapproperty') !== false) { if(preg_match('/@var\s+([\w\.]+(\[\s*\])?)\s*?\$(.*)$/mi',$comment,$matches)) { // support nillable, minOccurs, maxOccurs attributes $nillable=$minOccurs=$maxOccurs=false; if(preg_match('/{(.+)}/',$matches[3],$attr)) { $matches[3]=str_replace($attr[0],'',$matches[3]); if(preg_match_all('/((\w+)\s*=\s*(\w+))/mi',$attr[1],$attr)) { foreach($attr[2] as $id=>$prop) { if(strcasecmp($prop,'nillable')===0) $nillable=$attr[3][$id] ? 'true' : 'false'; elseif(strcasecmp($prop,'minOccurs')===0) $minOccurs=(int)$attr[3][$id]; elseif(strcasecmp($prop,'maxOccurs')===0) $maxOccurs=(int)$attr[3][$id]; } } } $param = array(); $param['type'] = $this->convertType($matches[1]); $param['name'] = trim($matches[3]); $param['nil'] = $nillable; $param['minOc'] = $minOccurs; $param['maxOc'] = $maxOccurs; $this->types[$className][] = $param; } } } } }