| // +------------------------------------------------------------------------+ // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | // +------------------------------------------------------------------------+ // | This software is licensed under the LGPL license. For more information | // | see http://wsdl2php.sf.net | // +------------------------------------------------------------------------+ ini_set('soap.wsdl_cache_enabled', 0); // disable WSDL cache if( $_SERVER['argc'] != 2 ) { die("usage: wsdl2php \n"); } $wsdl = $_SERVER['argv'][1]; print "Analyzing WSDL"; try { $client = new SoapClient($wsdl); } catch(SoapFault $e) { die($e); } print "."; $dom = DOMDocument::load($wsdl); print "."; // get documentation $nodes = $dom->getElementsByTagName('documentation'); $doc = array('service' => '', 'operations' => array()); foreach($nodes as $node) { if( $node->parentNode->localName == 'service' ) { $doc['service'] = trim($node->parentNode->nodeValue); } else if( $node->parentNode->localName == 'operation' ) { $operation = $node->parentNode->getAttribute('name'); //$parameterOrder = $node->parentNode->getAttribute('parameterOrder'); $doc['operations'][$operation] = trim($node->nodeValue); } } print "."; // get targetNamespace $targetNamespace = ''; $nodes = $dom->getElementsByTagName('definitions'); foreach($nodes as $node) { $targetNamespace = $node->getAttribute('targetNamespace'); } print "."; // declare service $service = array('class' => $dom->getElementsByTagNameNS('*', 'service')->item(0)->getAttribute('name'), 'wsdl' => $wsdl, 'doc' => $doc['service'], 'functions' => array()); print "."; // PHP keywords - can not be used as constants, class names or function names! $reserved_keywords = array('and', 'or', 'xor', 'as', 'break', 'case', 'cfunction', 'class', 'continue', 'declare', 'const', 'default', 'do', 'else', 'elseif', 'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', 'extends', 'for', 'foreach', 'function', 'global', 'if', 'new', 'old_function', 'static', 'switch', 'use', 'var', 'while', 'array', 'die', 'echo', 'empty', 'exit', 'include', 'include_once', 'isset', 'list', 'print', 'require', 'require_once', 'return', 'unset', '__file__', '__line__', '__function__', '__class__', 'abstract', 'private', 'public', 'protected', 'throw', 'try'); // ensure legal class name (I don't think using . and whitespaces is allowed in terms of the SOAP standard, should check this out and may throw and exception instead...) $service['class'] = str_replace(' ', '_', $service['class']); $service['class'] = str_replace('.', '_', $service['class']); $service['class'] = str_replace('-', '_', $service['class']); if(in_array(strtolower($service['class']), $reserved_keywords)) { $service['class'] .= 'Service'; } // verify that the name of the service is named as a defined class if(class_exists($service['class'])) { throw new Exception("Class '".$service['class']."' already exists"); } /*if(function_exists($service['class'])) { throw new Exception("Class '".$service['class']."' can't be used, a function with that name already exists"); }*/ // get operations $operations = $client->__getFunctions(); foreach($operations as $operation) { /* This is broken, need to handle GetAllByBGName_Response_t GetAllByBGName(string $Name) list(int $pcode, string $city, string $area, string $adm_center) GetByBGName(string $Name) finding the last '(' should be ok */ //list($call, $params) = explode('(', $operation); // broken //if($call == 'list') { // a list is returned //} /*$call = array(); preg_match('/^(list\(.*\)) (.*)\((.*)\)$/', $operation, $call); if(sizeof($call) == 3) { // found list() } else { preg_match('/^(.*) (.*)\((.*)\)$/', $operation, $call); if(sizeof($call) == 3) { } }*/ $matches = array(); if(preg_match('/^(\w[\w\d_]*) (\w[\w\d_]*)\(([\w\$\d,_ ]*)\)$/', $operation, $matches)) { $returns = $matches[1]; $call = $matches[2]; $params = $matches[3]; } else if(preg_match('/^(list\([\w\$\d,_ ]*\)) (\w[\w\d_]*)\(([\w\$\d,_ ]*)\)$/', $operation, $matches)) { $returns = $matches[1]; $call = $matches[2]; $params = $matches[3]; } else { // invalid function call throw new Exception('Invalid function call: '.$function); } $params = explode(', ', $params); $paramsArr = array(); foreach($params as $param) { $paramsArr[] = explode(' ', $param); } // $call = explode(' ', $call); $function = array('name' => $call, 'method' => $call, 'return' => $returns, 'doc' => isset($doc['operations'][$call])?$doc['operations'][$call]:'', 'params' => $paramsArr); // ensure legal function name if(in_array(strtolower($function['method']), $reserved_keywords)) { $function['name'] = '_'.$function['method']; } // ensure that the method we are adding has not the same name as the constructor if(strtolower($service['class']) == strtolower($function['method'])) { $function['name'] = '_'.$function['method']; } // ensure that there's no method that already exists with this name // this is most likely a Soap vs HttpGet vs HttpPost problem in WSDL // I assume for now that Soap is the one listed first and just skip the rest // this should be improved by actually verifying that it's a Soap operation that's in the WSDL file // QUICK FIX: just skip function if it already exists $add = true; foreach($service['functions'] as $func) { if($func['name'] == $function['name']) { $add = false; } } if($add) { $service['functions'][] = $function; } print "."; } $types = $client->__getTypes(); $primitive_types = array('string', 'int', 'long', 'float', 'boolean', 'dateTime', 'double', 'short', 'UNKNOWN', 'base64Binary', 'decimal', 'ArrayOfInt', 'ArrayOfFloat', 'ArrayOfString', 'decimal', 'hexBinary'); // TODO: dateTime is special, maybe use PEAR::Date or similar $service['types'] = array(); foreach($types as $type) { $parts = explode("\n", $type); $class = explode(" ", $parts[0]); $class = $class[1]; if( substr($class, -2, 2) == '[]' ) { // array skipping continue; } if( substr($class, 0, 7) == 'ArrayOf' ) { // skip 'ArrayOf*' types (from MS.NET, Axis etc.) continue; } $members = array(); for($i=1; $i $member, 'type' => $type); } } // gather enumeration values $values = array(); if(count($members) == 0) { $values = checkForEnum($dom, $class); } $service['types'][] = array('class' => $class, 'members' => $members, 'values' => $values); print "."; } print "done\n"; print "Generating code..."; $code = ""; // add types foreach($service['types'] as $type) { // $code .= "/**\n"; // $code .= " * ".(isset($type['doc'])?$type['doc']:'')."\n"; // $code .= " * \n"; // $code .= " * @package\n"; // $code .= " * @copyright\n"; // $code .= " */\n"; // add enumeration values $code .= "class ".$type['class']." {\n"; foreach($type['values'] as $value) { $code .= " const ".generatePHPSymbol($value)." = '$value';\n"; } // add member variables foreach($type['members'] as $member) { //$code .= " /* ".$member['type']." */\n"; $code .= " public \$".$member['member']."; // ".$member['type']."\n"; } $code .= "}\n\n"; /* print "Writing ".$type['class'].".php..."; $filename = $type['class'].".php"; $fp = fopen($filename, 'w'); fwrite($fp, "\n"); fclose($fp); print "ok\n";*/ } // add service // page level docblock //$code .= "/**\n"; //$code .= " * ".$service['class']." class file\n"; //$code .= " * \n"; //$code .= " * @author {author}\n"; //$code .= " * @copyright {copyright}\n"; //$code .= " * @package {package}\n"; //$code .= " */\n\n"; // require types //foreach($service['types'] as $type) { // $code .= "/**\n"; // $code .= " * ".$type['class']." class\n"; // $code .= " */\n"; // $code .= "require_once '".$type['class'].".php';\n"; //} $code .= "\n"; // class level docblock $code .= "/**\n"; $code .= " * ".$service['class']." class\n"; $code .= " * \n"; $code .= parse_doc(" * ", $service['doc']); $code .= " * \n"; $code .= " * @author {author}\n"; $code .= " * @copyright {copyright}\n"; $code .= " * @package {package}\n"; $code .= " */\n"; $code .= "class ".$service['class']." extends SoapClient {\n\n"; // add classmap $code .= " private static \$classmap = array(\n"; foreach($service['types'] as $type) { $code .= " '".$type['class']."' => '".$type['class']."',\n"; } $code .= " );\n\n"; $code .= " public function ".$service['class']."(\$wsdl = \"".$service['wsdl']."\", \$options = array()) {\n"; // initialize classmap (merge) $code .= " foreach(self::\$classmap as \$key => \$value) {\n"; $code .= " if(!isset(\$options['classmap'][\$key])) {\n"; $code .= " \$options['classmap'][\$key] = \$value;\n"; $code .= " }\n"; $code .= " }\n"; $code .= " parent::__construct(\$wsdl, \$options);\n"; $code .= " }\n\n"; foreach($service['functions'] as $function) { $code .= " /**\n"; $code .= parse_doc(" * ", $function['doc']); $code .= " *\n"; $signature = array(); // used for function signature $para = array(); // just variable names if(count($function['params']) > 0) { foreach($function['params'] as $param) { $code .= " * @param ".(isset($param[0])?$param[0]:'')." ".(isset($param[1])?$param[1]:'')."\n"; /*$typehint = false; foreach($service['types'] as $type) { if($type['class'] == $param[0]) { $typehint = true; } } $signature[] = ($typehint) ? implode(' ', $param) : $param[1];*/ $signature[] = (in_array($param[0], $primitive_types) or substr($param[0], 0, 7) == 'ArrayOf') ? $param[1] : implode(' ', $param); $para[] = $param[1]; } } $code .= " * @return ".$function['return']."\n"; $code .= " */\n"; $code .= " public function ".$function['name']."(".implode(', ', $signature).") {\n"; // $code .= " return \$this->client->".$function['name']."(".implode(', ', $para).");\n"; $code .= " return \$this->__soapCall('".$function['method']."', array("; $params = array(); if(count($signature) > 0) { // add arguments foreach($signature as $param) { if(strpos($param, ' ')) { // slice $param = array_pop(explode(' ', $param)); } $params[] = $param; } //$code .= "\n "; $code .= implode(", ", $params); //$code .= "\n ),\n"; } $code .= "), "; //$code .= implode(', ', $signature)."),\n"; $code .= " array(\n"; $code .= " 'uri' => '".$targetNamespace."',\n"; $code .= " 'soapaction' => ''\n"; $code .= " )\n"; $code .= " );\n"; $code .= " }\n\n"; } $code .= "}\n\n"; print "done\n"; print "Writing ".$service['class'].".php..."; $fp = fopen($service['class'].".php", 'w'); fwrite($fp, "\n"); fclose($fp); print "done\n"; function parse_doc($prefix, $doc) { $code = ""; $words = split(' ', $doc); $line = $prefix; foreach($words as $word) { $line .= $word.' '; if( strlen($line) > 90 ) { // new line $code .= $line."\n"; $line = $prefix; } } $code .= $line."\n"; return $code; } /** * Look for enumeration * * @param DOM $dom * @param string $class * @return array */ function checkForEnum(&$dom, $class) { $values = array(); $node = findType($dom, $class); if(!$node) { return $values; } $value_list = $node->getElementsByTagName('enumeration'); if($value_list->length == 0) { return $values; } for($i=0; $i<$value_list->length; $i++) { $values[] = $value_list->item($i)->attributes->getNamedItem('value')->nodeValue; } return $values; } /** * Look for a type * * @param DOM $dom * @param string $class * @return DOMNode */ function findType(&$dom, $class) { $types_node = $dom->getElementsByTagName('types')->item(0); $schema_list = $types_node->getElementsByTagName('schema'); for ($i=0; $i<$schema_list->length; $i++) { $children = $schema_list->item($i)->childNodes; for ($j=0; $j<$children->length; $j++) { $node = $children->item($j); if ($node instanceof DOMElement && $node->hasAttributes() && $node->attributes->getNamedItem('name')->nodeValue == $class) { return $node; } } } return null; } function generatePHPSymbol($s) { global $reserved_keywords; if(!preg_match('/^[A-Za-z_]/', $s)) { $s = 'value_'.$s; } if(in_array(strtolower($s), $reserved_keywords)) { $s = '_'.$s; } return preg_replace('/[-.\s]/', '_', $s); } ?>