1: <?php
2: /**
3: * The Horde_Reflection class provides reflection methods, e.g. to generate
4: * method documentation.
5: *
6: * Based on the PEAR XML_RPC2_Server_Method class by Sergio Carvalho
7: *
8: * Copyright 2004-2006 Sergio Gonalves Carvalho
9: * (<sergio.carvalho@portugalmail.com>)
10: * Copyright 2008-2012 Horde LLC (http://www.horde.org/)
11: *
12: * See the enclosed file COPYING for license information (LGPL). If you
13: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
14: *
15: * @author Sergio Carvalho <sergio.carvalho@portugalmail.com>
16: * @author Duck <duck@obala.net>
17: * @author Jan Schneider <jan@horde.org>
18: * @package Reflection
19: */
20: abstract class Horde_Reflection {
21:
22: /**
23: * Method signature parameters.
24: *
25: * @var array
26: */
27: protected $_parameters;
28:
29: /**
30: * Method signature return type.
31: *
32: * @var string
33: */
34: protected $_returns;
35:
36: /**
37: * Method help, for introspection.
38: *
39: * @var string
40: */
41: protected $_help;
42:
43: /**
44: * Number of required parameters.
45: *
46: * @var integer
47: */
48: protected $_numberOfRequiredParameters;
49:
50: /**
51: * External method name.
52: *
53: * @var string
54: */
55: protected $_name;
56:
57: /**
58: * Constructor.
59: *
60: * @param ReflectionMethod $method The PHP method to introspect.
61: */
62: public function __construct(ReflectionFunction $method)
63: {
64: $docs = $method->getDocComment();
65: $docs = explode("\n", $docs);
66:
67: $parameters = array();
68: $returns = 'mixed';
69: $shortdesc = '';
70: $paramcount = -1;
71: $this->_name = $method->getName();
72:
73: // Extract info from docblock.
74: $paramDocs = array();
75: foreach ($docs as $i => $doc) {
76: $doc = trim($doc, " \r\t/*");
77: if (strlen($doc) && strpos($doc, '@') !== 0) {
78: if ($shortdesc) {
79: $shortdesc .= "\n";
80: }
81: $shortdesc .= $doc;
82: continue;
83: }
84: if (strpos($doc, '@param') === 0) {
85: // Save doctag for usage later when filling parameters.
86: $paramDocs[] = $doc;
87: }
88:
89: if (strpos($doc, '@return') === 0) {
90: $param = preg_split("/\s+/", $doc);
91: if (isset($param[1])) {
92: $param = $param[1];
93: $returns = $param;
94: }
95: }
96: }
97:
98: // We don't use isOptional() because of bugs in the reflection API.
99: $this->_numberOfRequiredParameters = $method->getNumberOfRequiredParameters();
100: // Fill in info for each method parameter.
101: foreach ($method->getParameters() as $parameterIndex => $parameter) {
102: // Parameter defaults.
103: $newParameter = array('type' => 'mixed');
104:
105: // Attempt to extract type and doc from docblock.
106: if (array_key_exists($parameterIndex, $paramDocs) &&
107: preg_match('/@param\s+(\S+)(\s+(.+))/',
108: $paramDocs[$parameterIndex],
109: $matches)) {
110: if (strpos($matches[1], '|')) {
111: $newParameter['type'] = self::_limitPHPType(explode('|', $matches[1]));
112: } else {
113: $newParameter['type'] = self::_limitPHPType($matches[1]);
114: }
115: $tmp = '$' . $parameter->getName() . ' ';
116: if (strpos($matches[2], '$' . $tmp) === 0) {
117: $newParameter['doc'] = $matches[2];
118: } else {
119: // The phpdoc comment is something like "@param string
120: // $param description of param". Let's keep only
121: // "description of param" as documentation (remove
122: // $param).
123: $newParameter['doc'] = substr($matches[2], strlen($tmp));
124: }
125: }
126:
127: $parameters[$parameter->getName()] = $newParameter;
128: }
129:
130: $this->_parameters = $parameters;
131: $this->_returns = $returns;
132: $this->_help = $shortdesc;
133: }
134:
135: /**
136: * Returns a complete description of the method.
137: *
138: * @return string The method documentation.
139: */
140: abstract public function autoDocument();
141:
142: /**
143: * Converts types from phpdoc comments (and limit to xmlrpc available
144: * types) to php type names.
145: *
146: * @var string|array $type One or multiple phpdoc comment type(s).
147: *
148: * @return string|array The standardized php type(s).
149: */
150: protected static function _limitPHPType($type)
151: {
152: $convertArray = array(
153: 'int' => 'integer',
154: 'i4' => 'integer',
155: 'integer' => 'integer',
156: 'string' => 'string',
157: 'str' => 'string',
158: 'char' => 'string',
159: 'bool' => 'boolean',
160: 'boolean' => 'boolean',
161: 'array' => 'array',
162: 'float' => 'double',
163: 'double' => 'double',
164: 'array' => 'array',
165: 'struct' => 'array',
166: 'assoc' => 'array',
167: 'structure' => 'array',
168: 'datetime' => 'mixed',
169: 'datetime.iso8601' => 'mixed',
170: 'iso8601' => 'mixed',
171: 'base64' => 'string'
172: );
173:
174:
175: if (is_array($type)) {
176: $types = array();
177: foreach ($type as $tmp) {
178: $tmp = Horde_String::lower($tmp);
179: if (isset($convertArray[$tmp])) {
180: $types[] = $convertArray[$tmp];
181: } else {
182: $types[] = 'mixes';
183: }
184: }
185: return $types;
186: } else {
187: $tmp = Horde_String::lower($type);
188: if (isset($convertArray[$tmp])) {
189: return $convertArray[$tmp];
190: }
191: }
192:
193: return 'mixed';
194: }
195:
196: /**
197: * Attempts to return a concrete Horde_Document instance based on $driver.
198: *
199: * @param string $function The method to document.
200: * @param string $driver The type of the concrete Horde_Document
201: * subclass to return. The class name is based on
202: * the driver. The code is dynamically included.
203: *
204: * @return Horde_Document The newly created concrete Horde_Document
205: * instance, or false on an error.
206: */
207: public static function factory($function, $driver = 'Html')
208: {
209: $class = 'Horde_Reflection_' . $driver;
210: if (!class_exists($class)) {
211: include dirname(__FILE__) . '/Reflection/' . $driver . '.php';
212: }
213: if (class_exists($class)) {
214: return new $class($function);
215: } else {
216: return false;
217: }
218: }
219:
220: }
221: