3 * @see https://github.com/zendframework/zend-server for the canonical source repository
4 * @copyright Copyright (c) 2005-2018 Zend Technologies USA Inc. (https://www.zend.com)
5 * @license https://github.com/zendframework/zend-server/blob/master/LICENSE.md New BSD License
8 namespace Zend\Server\Reflection
;
10 use ReflectionClass
as PhpReflectionClass
;
11 use ReflectionFunction
as PhpReflectionFunction
;
12 use ReflectionFunctionAbstract
;
13 use ReflectionMethod
as PhpReflectionMethod
;
14 use Zend\Code\Reflection\DocBlockReflection
;
17 * Function/Method Reflection
19 * Decorates a ReflectionFunction. Allows setting and retrieving an alternate
20 * 'service' name (i.e., the name to be used when calling via a service),
21 * setting and retrieving the description (originally set using the docblock
22 * contents), retrieving the callback and callback type, retrieving additional
23 * method invocation arguments, and retrieving the
24 * method {@link \Zend\Server\Reflection\Prototype prototypes}.
26 abstract class AbstractFunction
29 * @var ReflectionFunctionAbstract
31 protected $reflection;
34 * Additional arguments to pass to method on invocation
40 * Used to store extra configuration for the method (typically done by the
41 * server class, e.g., to indicate whether or not to instantiate a class).
42 * Associative array; access is as properties via {@link __get()} and
46 protected $config = [];
49 * Declaring class (needed for when serialization occurs)
55 * Function/method description
58 protected $description = '';
61 * Namespace with which to prefix function/method name
70 protected $prototypes = [];
76 protected $docComment = '';
82 private $sigParamsDepth;
87 * @param ReflectionFunctionAbstract $r
88 * @param null|string $namespace
89 * @param null|array $argv
90 * @throws Exception\InvalidArgumentException
91 * @throws Exception\RuntimeException
93 public function __construct(ReflectionFunctionAbstract
$r, $namespace = null, $argv = [])
95 $this->reflection
= $r;
97 // Determine namespace
98 if (null !== $namespace) {
99 $this->setNamespace($namespace);
102 // Determine arguments
103 if (is_array($argv)) {
107 // If method call, need to store some info on the class
108 if ($r instanceof PhpReflectionMethod
) {
109 $this->class = $r->getDeclaringClass()->getName();
112 // Perform some introspection
117 * Create signature node tree
119 * Recursive method to build the signature node tree. Increments through
120 * each array in {@link $sigParams}, adding every value of the next level
121 * to the current value (unless the current value is null).
123 * @param \Zend\Server\Reflection\Node $parent
127 protected function addTree(Node
$parent, $level = 0)
129 if ($level >= $this->sigParamsDepth
) {
133 foreach ($this->sigParams
[$level] as $value) {
134 $node = new Node($value, $parent);
135 if ((null !== $value) && ($this->sigParamsDepth
> $level +
1)) {
136 $this->addTree($node, $level +
1);
142 * Build the signature tree
144 * Builds a signature tree starting at the return values and descending
145 * through each method argument. Returns an array of
146 * {@link \Zend\Server\Reflection\Node}s.
150 protected function buildTree()
153 foreach ($this->return as $value) {
154 $node = new Node($value);
155 $this->addTree($node);
156 $returnTree[] = $node;
163 * Build method signatures
165 * Builds method signatures using the array of return types and the array of
168 * @param array $return Array of return types
169 * @param string $returnDesc Return value description
170 * @param array $paramTypes Array of arguments (each an array of types)
171 * @param array $paramDesc Array of parameter descriptions
174 protected function buildSignatures($return, $returnDesc, $paramTypes, $paramDesc)
176 $this->return = $return;
177 $this->returnDesc
= $returnDesc;
178 $this->paramDesc
= $paramDesc;
179 $this->sigParams
= $paramTypes;
180 $this->sigParamsDepth
= count($paramTypes);
181 $signatureTrees = $this->buildTree();
185 foreach ($signatureTrees as $root) {
186 $tmp = $root->getEndPoints();
188 $endPoints = array_merge($endPoints, [$root]);
190 $endPoints = array_merge($endPoints, $tmp);
194 foreach ($endPoints as $node) {
195 if (! $node instanceof Node
) {
201 array_unshift($signature, $node->getValue());
202 $node = $node->getParent();
203 } while ($node instanceof Node
);
205 $signatures[] = $signature;
209 $params = $this->reflection
->getParameters();
210 foreach ($signatures as $signature) {
211 $return = new ReflectionReturnValue(array_shift($signature), $this->returnDesc
);
213 foreach ($signature as $key => $type) {
214 $param = new ReflectionParameter(
217 (isset($this->paramDesc
[$key]) ?
$this->paramDesc
[$key] : null)
219 $param->setPosition($key);
223 $this->prototypes
[] = new Prototype($return, $tmp);
228 * Use code reflection to create method signatures
230 * Determines the method help/description text from the function DocBlock
231 * comment. Determines method signatures using a combination of
232 * ReflectionFunction and parsing of DocBlock @param and @return values.
234 * @throws Exception\RuntimeException
237 protected function reflect()
239 $function = $this->reflection
;
240 $paramCount = $function->getNumberOfParameters();
241 $parameters = $function->getParameters();
243 if (! $this->docComment
) {
244 $this->docComment
= $function->getDocComment();
247 $scanner = new DocBlockReflection(($this->docComment
) ?
: '/***/');
248 $helpText = $scanner->getLongDescription();
249 /* @var \Zend\Code\Reflection\DocBlock\Tag\ParamTag[] $paramTags */
250 $paramTags = $scanner->getTags('param');
251 /* @var \Zend\Code\Reflection\DocBlock\Tag\ReturnTag $returnTag */
252 $returnTag = $scanner->getTag('return');
254 if (empty($helpText)) {
255 $helpText = $scanner->getShortDescription();
256 if (empty($helpText)) {
257 $helpText = $function->getName();
260 $this->setDescription($helpText);
264 $returnDesc = $returnTag->getDescription();
265 foreach ($returnTag->getTypes() as $type) {
275 if (empty($paramTags)) {
276 foreach ($parameters as $param) {
277 $paramTypesTmp[] = [($param->isArray()) ?
'array' : 'mixed'];
282 foreach ($paramTags as $paramTag) {
283 $paramTypesTmp[] = $paramTag->getTypes();
284 $paramDesc[] = ($paramTag->getDescription()) ?
: '';
288 // Get all param types as arrays
289 $nParamTypesTmp = count($paramTypesTmp);
290 if ($nParamTypesTmp < $paramCount) {
291 $start = $paramCount - $nParamTypesTmp;
292 for ($i = $start; $i < $paramCount; ++
$i) {
293 $paramTypesTmp[$i] = ['mixed'];
296 } elseif ($nParamTypesTmp != $paramCount) {
297 throw new Exception\
RuntimeException(
298 'Variable number of arguments is not supported for services (except optional parameters). '
299 . 'Number of function arguments must correspond to actual number of arguments described in a docblock.'
304 foreach ($paramTypesTmp as $i => $param) {
305 if ($parameters[$i]->isOptional()) {
306 array_unshift($param, null);
308 $paramTypes[] = $param;
311 $this->buildSignatures($return, $returnDesc, $paramTypes, $paramDesc);
315 * Proxy reflection calls
317 * @param string $method
319 * @throws Exception\BadMethodCallException
322 public function __call($method, $args)
324 if (method_exists($this->reflection
, $method)) {
325 return call_user_func_array([$this->reflection
, $method], $args);
328 throw new Exception\
BadMethodCallException('Invalid reflection method ("' . $method . '")');
332 * Retrieve configuration parameters
334 * Values are retrieved by key from {@link $config}. Returns null if no
340 public function __get($key)
342 if (isset($this->config
[$key])) {
343 return $this->config
[$key];
350 * Set configuration parameters
352 * Values are stored by $key in {@link $config}.
355 * @param mixed $value
358 public function __set($key, $value)
360 $this->config
[$key] = $value;
364 * Set method's namespace
366 * @param string $namespace
367 * @throws Exception\InvalidArgumentException
370 public function setNamespace($namespace)
372 if (empty($namespace)) {
373 $this->namespace = '';
377 if (! is_string($namespace) ||
! preg_match('/[a-z0-9_\.]+/i', $namespace)) {
378 throw new Exception\
InvalidArgumentException('Invalid namespace');
381 $this->namespace = $namespace;
385 * Return method's namespace
389 public function getNamespace()
391 return $this->namespace;
395 * Set the description
397 * @param string $string
398 * @throws Exception\InvalidArgumentException
401 public function setDescription($string)
403 if (! is_string($string)) {
404 throw new Exception\
InvalidArgumentException('Invalid description');
407 $this->description
= $string;
411 * Retrieve the description
415 public function getDescription()
417 return $this->description
;
421 * Retrieve all prototypes as array of
422 * {@link \Zend\Server\Reflection\Prototype}s
424 * @return Prototype[]
426 public function getPrototypes()
428 return $this->prototypes
;
432 * Retrieve additional invocation arguments
436 public function getInvokeArguments()
442 * Wakeup from serialization
444 * Reflection needs explicit instantiation to work correctly. Re-instantiate
445 * reflection object on wakeup.
449 public function __wakeup()
451 if ($this->reflection
instanceof PhpReflectionMethod
) {
452 $class = new PhpReflectionClass($this->class);
453 $this->reflection
= new PhpReflectionMethod($class->newInstance(), $this->getName());
455 $this->reflection
= new PhpReflectionFunction($this->getName());