Skip to content

Commit

Permalink
Added more exceptions
Browse files Browse the repository at this point in the history
More accurate docblock parsing (support for long & short descriptions, multiline comments, malformated tags...)
BC Break: Change in constructor (dropped the $description parameter, now taken from the class's docblock)


git-svn-id: http://svn.php.net/repository/pear/packages/Services_Webservice/trunk@193471 c90b9560-bf6c-de11-be94-00142212c4b1
  • Loading branch information
Philippe Jausions committed Aug 15, 2005
1 parent a281a2e commit 83c8a7f
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 58 deletions.
14 changes: 2 additions & 12 deletions Webservice.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,6 @@ abstract class Services_Webservice
*/
public $namespace;

/**
* Description of the web service
*
* @var string
* @access public
*/
public $description;

/**
* Protocol of the web service
*
Expand Down Expand Up @@ -81,17 +73,15 @@ abstract class Services_Webservice
* Constructor
*
* @var string $namespace
* @var string $description
* @var array $options
* @access public
*/
public function __construct($namespace, $description, $options = null)
public function __construct($namespace, $options = null)
{
if (trim($namespace) == '') {
$namespace = 'http://example.org/';
}
$this->namespace = $namespace;
$this->description = $description;
$this->soapServerOptions['uri'] = isset($options['uri']) ? $options['uri'] : $this->namespace;
$this->soapServerOptions['encoding'] = isset($options['encoding']) ? $options['encoding'] : SOAP_ENCODED;
$this->protocol = 'http';
Expand Down Expand Up @@ -130,7 +120,7 @@ public function handle()
}
if ($action) {
require_once 'Services/Webservice/Definition.php';
$this->_wsdlWriter = new Services_Webservice_Definition($this->_classname, $this->namespace, $this->description);
$this->_wsdlWriter = new Services_Webservice_Definition($this->_classname, $this->namespace);
$this->_wsdlWriter->protocol = $this->protocol;
echo $this->_wsdlWriter->{'to' . $action}();
} else {
Expand Down
173 changes: 133 additions & 40 deletions Webservice/Definition.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ class Services_Webservice_Definition
* Description of the web service
*
* @var string
* @access public
* @access protected
*/
public $description;
protected $description;

/**
* Simple WSDL types
Expand Down Expand Up @@ -108,12 +108,11 @@ class Services_Webservice_Definition
*
* @var object|string $class
* @var string $namespace
* @var string $description
* @var array $options not currently used
* @access public
* @throws Services_Webservice_Definition_NotClassException
*/
public function __construct($class, $namespace, $description, $options = null)
public function __construct($class, $namespace, $options = null)
{
if (is_object($class)) {
$this->_classname = $class->get_class();
Expand All @@ -129,8 +128,6 @@ public function __construct($class, $namespace, $description, $options = null)
$this->namespace = 'http://example.org/';
}

$this->description = $description;

$this->_wsdlStruct = array();
$this->_hiddenMethods = array(
'__construct',
Expand Down Expand Up @@ -246,6 +243,7 @@ public function getURI($name = null)
* @return string
* @access public
* @throws Services_Webservice_Definition_UnknownFormatException
* @throws Services_Webservice_Definition_Exception
*/
public function __call($name, $arg)
{
Expand All @@ -266,6 +264,7 @@ public function __call($name, $arg)
* Dispatches types
*
* @access protected
* @throws Services_Webservice_Definition_Exception
*/
protected function classStructDispatch()
{
Expand Down Expand Up @@ -295,6 +294,7 @@ protected function classStructDispatch()
* @var string
* @access private
* @throws Services_Webservice_Definition_NoDocCommentException
* @throws Services_Webservice_Definition_IncompleteDocCommentException
*/
protected function classPropertiesIntoStruct($className)
{
Expand All @@ -304,37 +304,40 @@ protected function classPropertiesIntoStruct($className)
$this->_wsdlStruct['class'][$className]['property'] = array();
for ($i = 0; $i < count($properties); ++$i) {
if ($properties[$i]->isPublic()) {
$docComments = trim($properties[$i]->getDocComment());

$propertyName = $properties[$i]->getName();

try {
$docComments = $this->_parseDocBlock($properties[$i]->getDocComment());
} catch (Services_Webservice_Definition_Exception $e) {
throw new Services_Webservice_Definition_Exception('Error in ' . $className . '::' . $propertyName . ' property docblock', $e);
}

if (!$docComments) {
throw new Services_Webservice_Definition_NoDocCommentException(
'Property ' . $class . '::' . $propertyName
. ' is missing docblock comment.');
'Empty or missing docblock comment for ' . $className . '::' . $propertyName . ' property.');
}

// Skip property?
if (strpos($docComments, '* @webservice.hidden') !== false) {
if (isset($docComments['webservice.hidden'])) {
continue;
}

$_properties =& $this->_wsdlStruct['class'][$className]['property'][$propertyName];

// Deprecated?
if (strpos($docComments, '* @deprecated') !== false) {
if (isset($docComments['deprecated'])) {
$_properties['deprecated'] = true;
}

// Description
if (($endPos = strpos($docComments, '* @')) === false) {
$endPos = strlen($docComments);
}
$_properties['description'] = trim(substr($docComments, 0, $endPos), "\r\n\t *\0\x0B/");

preg_match_all('~\* @var\s(\S+)~', $docComments, $var);
$_properties['description'] = @$docComments['shortDescription'];

$_cleanType = str_replace('[]', '', $var[1][0], $_length);
if (!isset($docComments['var'])) {
throw new Services_Webservice_Definition_IncompleteDocCommentException('@var missing in docblock comment for ' . $className . '::' . $propertyName . ' property.');
}
$var = $docComments['var'];
$_cleanType = str_replace('[]', '', $var[0][0], $_length);
$_typens = str_repeat('ArrayOf', $_length);

$_properties['type'] = $_cleanType;
Expand Down Expand Up @@ -370,19 +373,19 @@ protected function classPropertiesIntoStruct($className)
* @access protected
* @throws Services_Webservice_Definition_NoDocCommentException
* @throws Services_Webservice_Definition_DocCommentMismatchException
* @throws Services_Webservice_Definition_Exception
*/
protected function classMethodsIntoStruct()
{
$class = new ReflectionClass($this->_classname);
$docComments = trim($class->getDocComment(), "\r\n\t *\0\x0B/");

$docComments = $this->_parseDocBlock($class->getDocComment());

if (!$docComments) {
throw new Services_Webservice_Definition_NoDocCommentException('Class ' . $this->_classname . ' is missing docblock comment.');
throw new Services_Webservice_Definition_NoDocCommentException('Empty or missing docblock for ' . $this->_classname . ' class');
}

if (($endPos = strpos($docComments, '* @')) === false) {
$endPos = strlen($docComments);
}
$this->description = trim(substr($docComments, 0, $endPos), "\r\n\t *\0\x0B/");
$this->_wsdlStruct['service']['description'] = $docComments['shortDescription'];

$methods = $class->getMethods();

Expand All @@ -391,39 +394,47 @@ protected function classMethodsIntoStruct()
if ($method->isPublic()
&& !in_array($methodName, $this->_hiddenMethods)) {

$docComments = trim($method->getDocComment());
try {
$docComments = $this->_parseDocBlock($method->getDocComment());
} catch (Services_Webservice_Definition_Exception $e) {
throw new Services_Webservice_Definition_Exception('Error in ' . $this->_classname . '::' . $methodName . '() docblock', $e);
}

if (!$docComments) {
throw new Services_Webservice_Definition_NoDocCommentException('Method ' . $this->_classname . '::' . $methodName . '() is missing docblock comment.');
throw new Services_Webservice_Definition_NoDocCommentException('empty or missing docblock for ' . $this->_classname . '::' . $methodName . '() method');
}

// Skip method?
if (strpos($docComments, '* @webservice.hidden') !== false) {
if (isset($docComments['webservice.hidden'])) {
continue;
}

// Deprecated?
if (strpos($docComments, '* @deprecated') !== false) {
if (isset($docComments['deprecated'])) {
$this->_wsdlStruct[$this->_classname]['method'][$methodName]['deprecated'] = true;
}

// Description
if (($endPos = strpos($docComments, '* @')) === false) {
$endPos = strlen($docComments);
}
$this->_wsdlStruct[$this->_classname]['method'][$methodName]['description'] = trim(substr($docComments, 0, $endPos), "\r\n\t *\0\x0B/");
$this->_wsdlStruct[$this->_classname]['method'][$methodName]['description'] = @$docComments['shortDescription'];

// Params
preg_match_all('~@param\s(\S+)~', $docComments, $param);
$param = (array) @$docComments['param'];
$params = $method->getParameters();
if (count($params) != count($param[1])) {
if (count($params) != count($param)) {
throw new Services_Webservice_Definition_DocCommentMismatchException(
'Docblock comment doesn\'t match ' . $this->_classname
. '::' . $methodName . '() signature.');
. '::' . $methodName . '() signature');
}
for ($i = 0; $i < count($params); ++$i) {
$_class = $params[$i]->getClass();
$_type = ($_class) ? $_class->getName() : $param[1][$i];
// Type hint
if ($_class = $params[$i]->getClass()) {
$_type = $_class->getName();
if ($_type != $param[$i][0]) {
throw new Services_Webservice_Definition_DocCommentMismatchException('Docblock comment doesn\'t match ' . $this->_classname . '::' . $methodName . '() type hints');
}
} else {
$_type = $param[$i][0];
}

$_cleanType = str_replace('[]', '', $_type, $_length);
$_typens = str_repeat('ArrayOf', $_length);
Expand All @@ -444,9 +455,8 @@ protected function classMethodsIntoStruct()
}

// return
preg_match_all('~@return\s(\S+)~', $docComments, $return);
if (isset($return[1][0])) {
$_cleanType = str_replace('[]', '', $return[1][0], $_length);
if ($return = @$docComments['return']) {
$_cleanType = str_replace('[]', '', $return[0][0], $_length);
} else {
$_cleanType = 'void';
$_length = 0;
Expand All @@ -467,6 +477,89 @@ protected function classMethodsIntoStruct()
}
}
}

/**
* Parses a docblock comment
*
* @param string $comments
* @return array
* @access protected
* @throws Services_Webservice_Definition_InvalidDocCommentException
*/
protected function _parseDocBlock($comments)
{
$comments = explode("\n", $comments);

$info = array();
$tag = '';
$lastTag = 0;
$shortDesc = array();
$longDesc = '';
$inDesc = 'short';

foreach ($comments as $line) {
$line = ltrim(trim($line), "/* \t");
if ($line && $line{0} == '@') {
$inDesc = false;
$line = explode(' ', strtr($line, "\t", ' '), 2);
$tag = substr(array_shift($line), 1);
$attr = trim(@$line[0]);
switch ($tag) {
case 'return':
case 'var':
if (isset($info[$tag])) {
throw new Services_Webservice_Definition_InvalidDocCommentException('Only 1 @' . $tag . ' doctag allowed in docblock');
}
// No "break" here. This is intentional!
case 'param':
if (!$attr) {
throw new Services_Webservice_Definition_InvalidDocCommentException('Incomplete @' . $tag . ' docblock tag');
}
$attr = explode(' ', $attr, 2);
break;
}
$info[$tag][] = $attr;
$lastTag = count($info[$tag]) - 1;

} elseif ($inDesc) {
switch ($inDesc) {
case 'short':
if ($line == '' || $line == '.') {
if ($shortDesc) {
$inDesc = 'long';
}
continue 2;
} elseif (count($shortDesc) < 3) {
$shortDesc[] = $line;
if (substr($line, -1) == '.') {
$inDesc = 'long';
}
break;
} else {
$line = array_pop($shortDesc) . "\n" . $line;
$line = array_pop($shortDesc) . "\n" . $line;
$inDesc = 'long';
}
// No "break" here. This is intentional!
case 'long':
$longDesc .= $line . "\n";
break;
}

} elseif ($line != '') {
// Multiline tag
$info[$tag][$lastTag] .= "\n" . $line;
}
}
if ($shortDesc) {
$info['shortDescription'] = implode("\n", $shortDesc);
}
if (trim($longDesc)) {
$info['longDescription'] = $longDesc;
}

return $info;
}
}

?>
14 changes: 14 additions & 0 deletions Webservice/Definition/Exception.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,18 @@ class Services_Webservice_Definition_DocCommentMismatchException extends Service
{
}

/**
* Exception class for invalid docblock comment
*/
class Services_Webservice_Definition_InvalidDocCommentException extends Services_Webservice_Definition_Exception
{
}

/**
* Exception class for incomplete docblock comment
*/
class Services_Webservice_Definition_IncompleteDocCommentException extends Services_Webservice_Definition_Exception
{
}

?>
4 changes: 2 additions & 2 deletions Webservice/Definition/HTML.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ public function toString()
$namespace = $this->_parser->namespace;
$classname = $this->_parser->getClassName();

if (trim($this->_parser->description) == '') {
if (trim($wsdlStruct['service']['description']) == '') {
$description = 'My example service description';
} else {
$description = $this->_parser->description;
$description = $wsdlStruct['service']['description'];
}

if (!($urlWSDL = $this->_parser->getURI('WSDL'))) {
Expand Down
2 changes: 1 addition & 1 deletion Webservice/Definition/WSDL.php
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ protected function createWSDLService()
$service->setAttribute('name', $this->classname);

$documentation = $this->_wsdl->createElement('documentation');
$documentation->appendChild($this->_wsdl->createTextNode($this->_parser->description));
$documentation->appendChild($this->_wsdl->createTextNode($this->_wsdlStruct['service']['description']));
$service->appendChild($documentation);

$port = $this->_wsdl->createElement('port');
Expand Down
Loading

0 comments on commit 83c8a7f

Please sign in to comment.