| * +-----------------------------------------------------------------------------+ * * @category XML * @package XML_RPC2 * @author Sergio Carvalho * @copyright 2004-2006 Sergio Carvalho * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @version CVS: $Id$ * @link http://pear.php.net/package/XML_RPC2 */ // }}} // dependencies {{{ require_once 'XML/RPC2/Exception.php'; // }}} /** * Class representing an XML-RPC exported method. * * This class is used internally by XML_RPC2_Server. External users of the * package should not need to ever instantiate XML_RPC2_Server_Method * * @category XML * @package XML_RPC2 * @author Sergio Carvalho * @copyright 2004-2006 Sergio Carvalho * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 * @link http://pear.php.net/package/XML_RPC2 */ class XML_RPC2_Server_Method { // {{{ properties /** * Method signature parameters * * @var array */ private $_parameters; /** * Method signature return type * * @var string */ private $_returns ; /** * Method help, for introspection * * @var string */ private $_help; /** * internalMethod field : method name in PHP-land * * @var string */ private $_internalMethod; /** * hidden field : true if the method is hidden * * @var boolean */ private $_hidden; /** * name Field : external method name * * @var string */ private $_name; /** * Number of required parameters * * @var int */ private $_numberOfRequiredParameters; // }}} // {{{ getInternalMethod() /** * internalMethod getter * * @return string internalMethod */ public function getInternalMethod() { return $this->_internalMethod; } // }}} // {{{ isHidden() /** * hidden getter * * @return boolean hidden value */ public function isHidden() { return $this->_hidden; } // }}} // {{{ getName() /** * name getter * * @return string name */ public function getName() { return $this->_name; } // }}} // {{{ constructor /** * Create a new XML-RPC method by introspecting a PHP method * * @param ReflectionMethod The PHP method to introspect * @param string default prefix */ public function __construct(ReflectionMethod $method, $defaultPrefix) { $hidden = false; $docs = $method->getDocComment(); if (!$docs) { $hidden = true; } $docs = explode("\n", $docs); $parameters = array(); $methodname = null; $returns = 'mixed'; $shortdesc = ''; $paramcount = -1; $prefix = $defaultPrefix; // Extract info from Docblock $paramDocs = array(); foreach ($docs as $i => $doc) { $doc = trim($doc, " \r\t/*"); if (strlen($doc) && strpos($doc, '@') !== 0) { if ($shortdesc) { $shortdesc .= "\n"; } $shortdesc .= $doc; continue; } if (strpos($doc, '@xmlrpc.hidden') === 0) { $hidden = true; } if ((strpos($doc, '@xmlrpc.prefix') === 0) && preg_match('/@xmlrpc.prefix( )*(.*)/', $doc, $matches)) { $prefix = $matches[2]; } if ((strpos($doc, '@xmlrpc.methodname') === 0) && preg_match('/@xmlrpc.methodname( )*(.*)/', $doc, $matches)) { $methodname = $matches[2]; } if (strpos($doc, '@param') === 0) { // Save doctag for usage later when filling parameters $paramDocs[] = $doc; } if (strpos($doc, '@return') === 0) { $param = preg_split("/\s+/", $doc); if (isset($param[1])) { $param = $param[1]; $returns = $param; } } } $this->_numberOfRequiredParameters = $method->getNumberOfRequiredParameters(); // we don't use isOptional() because of bugs in the reflection API // Fill in info for each method parameter foreach ($method->getParameters() as $parameterIndex => $parameter) { // Parameter defaults $newParameter = array('type' => 'mixed'); // Attempt to extract type and doc from docblock if (array_key_exists($parameterIndex, $paramDocs) && preg_match('/@param\s+(\S+)(\s+(.+))/', $paramDocs[$parameterIndex], $matches)) { if (strpos($matches[1], '|')) { $newParameter['type'] = XML_RPC2_Server_Method::_limitPHPType(explode('|', $matches[1])); } else { $newParameter['type'] = XML_RPC2_Server_Method::_limitPHPType($matches[1]); } $tmp = '$' . $parameter->getName() . ' '; if (strpos($matches[3], '$' . $tmp) === 0) { $newParameter['doc'] = $matches[3]; } else { // The phpdoc comment is something like "@param string $param description of param" // Let's keep only "description of param" as documentation (remove $param) $newParameter['doc'] = substr($matches[3], strlen($tmp)); } $newParameter['doc'] = preg_replace('_^\s*_', '', $newParameter['doc']); } $parameters[$parameter->getName()] = $newParameter; } if (is_null($methodname)) { $methodname = $prefix . $method->getName(); } $this->_internalMethod = $method->getName(); $this->_parameters = $parameters; $this->_returns = $returns; $this->_help = $shortdesc; $this->_name = $methodname; $this->_hidden = $hidden; } // }}} // {{{ matchesSignature() /** * Check if method matches provided call signature * * Compare the provided call signature with this methods' signature and * return true iff they match. * * @param string Signature to compare method name * @param array Array of parameter values for method call. * @return boolean True if call matches signature, false otherwise */ public function matchesSignature($methodName, $callParams) { if ($methodName != $this->_name) return false; if (count($callParams) < $this->_numberOfRequiredParameters) return false; if (count($callParams) > $this->_parameters) return false; $paramIndex = 0; foreach($this->_parameters as $param) { $paramIndex++; if ($paramIndex <= $this->_numberOfRequiredParameters) { // the parameter is not optional $callParamType = XML_RPC2_Server_Method::_limitPHPType(gettype($callParams[$paramIndex-1])); if ((!($param['type'] == 'mixed')) and ($param['type'] != $callParamType)) { return false; } } } return true; } // }}} // {{{ getHTMLSignature() /** * Return a HTML signature of the method * * @return string HTML signature */ public function getHTMLSignature() { $name = $this->_name; $returnType = $this->_returns; $result = "($returnType) "; $result .= "$name"; $result .= "("; $first = true; $nbr = 0; while (list($name, $parameter) = each($this->_parameters)) { $nbr++; if ($nbr == $this->_numberOfRequiredParameters + 1) { $result .= " [ "; } if ($first) { $first = false; } else { $result .= ', '; } $type = $parameter['type']; $result .= "($type) "; $result .= "$name"; } reset($this->_parameters); if ($nbr > $this->_numberOfRequiredParameters) { $result .= " ] "; } $result .= ")"; return $result; } // }}} // {{{ autoDocument() /** * Print a complete HTML description of the method */ public function autoDocument() { $name = $this->getName(); $signature = $this->getHTMLSignature(); $id = md5($name); $help = nl2br(htmlentities($this->_help)); print "

$signature

\n"; print "

Description :

\n"; print "
\n"; print " $help\n"; print "
\n"; if (count($this->_parameters)>0) { print "

Parameters :

\n"; if (count($this->_parameters)>0) { print " \n"; print " \n"; while (list($name, $parameter) = each($this->_parameters)) { $type = $parameter['type']; $doc = isset($parameter['doc']) ? htmlentities($parameter['doc']) : 'Method is not documented. No PHPDoc block was found associated with the method in the source code.'; print " \n"; } reset($this->_parameters); print "
TypeNameDocumentation
$type$name$doc
\n"; } } } // }}} // {{{ _limitPHPType() /** * standardise type names between gettype php function and phpdoc comments (and limit to xmlrpc available types) * * @var string $type * @return string standardised type */ private static function _limitPHPType($type) { $tmp = strtolower($type); $convertArray = array( 'int' => 'integer', 'i4' => 'integer', 'integer' => 'integer', 'string' => 'string', 'str' => 'string', 'char' => 'string', 'bool' => 'boolean', 'boolean' => 'boolean', 'array' => 'array', 'float' => 'double', 'double' => 'double', 'array' => 'array', 'struct' => 'array', 'assoc' => 'array', 'structure' => 'array', 'datetime' => 'mixed', 'datetime.iso8601' => 'mixed', 'iso8601' => 'mixed', 'base64' => 'string' ); if (isset($convertArray[$tmp])) { return $convertArray[$tmp]; } return 'mixed'; } } ?>