1<?php 2 3/** 4 * @see https://github.com/laminas/laminas-code for the canonical source repository 5 * @copyright https://github.com/laminas/laminas-code/blob/master/COPYRIGHT.md 6 * @license https://github.com/laminas/laminas-code/blob/master/LICENSE.md New BSD License 7 */ 8 9namespace Laminas\Code\Reflection; 10 11use ReflectionFunction; 12 13use function array_shift; 14use function array_slice; 15use function count; 16use function file; 17use function implode; 18use function preg_match; 19use function preg_quote; 20use function preg_replace; 21use function sprintf; 22use function strlen; 23use function strrpos; 24use function substr; 25use function var_export; 26 27class FunctionReflection extends ReflectionFunction implements ReflectionInterface 28{ 29 /** 30 * Constant use in @MethodReflection to display prototype as an array 31 */ 32 const PROTOTYPE_AS_ARRAY = 'prototype_as_array'; 33 34 /** 35 * Constant use in @MethodReflection to display prototype as a string 36 */ 37 const PROTOTYPE_AS_STRING = 'prototype_as_string'; 38 39 /** 40 * Get function DocBlock 41 * 42 * @throws Exception\InvalidArgumentException 43 * @return DocBlockReflection 44 */ 45 public function getDocBlock() 46 { 47 if ('' == ($comment = $this->getDocComment())) { 48 throw new Exception\InvalidArgumentException(sprintf( 49 '%s does not have a DocBlock', 50 $this->getName() 51 )); 52 } 53 54 $instance = new DocBlockReflection($comment); 55 56 return $instance; 57 } 58 59 /** 60 * Get start line (position) of function 61 * 62 * @param bool $includeDocComment 63 * @return int 64 */ 65 public function getStartLine($includeDocComment = false) 66 { 67 if ($includeDocComment) { 68 if ($this->getDocComment() != '') { 69 return $this->getDocBlock()->getStartLine(); 70 } 71 } 72 73 return parent::getStartLine(); 74 } 75 76 /** 77 * Get contents of function 78 * 79 * @param bool $includeDocBlock 80 * @return string 81 */ 82 public function getContents($includeDocBlock = true) 83 { 84 $fileName = $this->getFileName(); 85 if (false === $fileName) { 86 return ''; 87 } 88 89 $startLine = $this->getStartLine(); 90 $endLine = $this->getEndLine(); 91 92 // eval'd protect 93 if (preg_match('#\((\d+)\) : eval\(\)\'d code$#', $fileName, $matches)) { 94 $fileName = preg_replace('#\(\d+\) : eval\(\)\'d code$#', '', $fileName); 95 $startLine = $endLine = $matches[1]; 96 } 97 98 $lines = array_slice( 99 file($fileName, FILE_IGNORE_NEW_LINES), 100 $startLine - 1, 101 $endLine - ($startLine - 1), 102 true 103 ); 104 105 $functionLine = implode("\n", $lines); 106 107 $content = ''; 108 if ($this->isClosure()) { 109 preg_match('#function\s*\([^\)]*\)\s*(use\s*\([^\)]+\))?\s*\{(.*\;)?\s*\}#s', $functionLine, $matches); 110 if (isset($matches[0])) { 111 $content = $matches[0]; 112 } 113 } else { 114 $name = substr($this->getName(), strrpos($this->getName(), '\\') + 1); 115 preg_match( 116 '#function\s+' . preg_quote($name) . '\s*\([^\)]*\)\s*{([^{}]+({[^}]+})*[^}]+)?}#', 117 $functionLine, 118 $matches 119 ); 120 if (isset($matches[0])) { 121 $content = $matches[0]; 122 } 123 } 124 125 $docComment = $this->getDocComment(); 126 127 return $includeDocBlock && $docComment ? $docComment . "\n" . $content : $content; 128 } 129 130 /** 131 * Get method prototype 132 * 133 * @param string $format 134 * @return array|string 135 */ 136 public function getPrototype($format = FunctionReflection::PROTOTYPE_AS_ARRAY) 137 { 138 $returnType = 'mixed'; 139 $docBlock = $this->getDocBlock(); 140 if ($docBlock) { 141 $return = $docBlock->getTag('return'); 142 $returnTypes = $return->getTypes(); 143 $returnType = count($returnTypes) > 1 ? implode('|', $returnTypes) : $returnTypes[0]; 144 } 145 146 $prototype = [ 147 'namespace' => $this->getNamespaceName(), 148 'name' => substr($this->getName(), strlen($this->getNamespaceName()) + 1), 149 'return' => $returnType, 150 'arguments' => [], 151 ]; 152 153 $parameters = $this->getParameters(); 154 foreach ($parameters as $parameter) { 155 $prototype['arguments'][$parameter->getName()] = [ 156 'type' => $parameter->detectType(), 157 'required' => ! $parameter->isOptional(), 158 'by_ref' => $parameter->isPassedByReference(), 159 'default' => $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null, 160 ]; 161 } 162 163 if ($format == FunctionReflection::PROTOTYPE_AS_STRING) { 164 $line = $prototype['return'] . ' ' . $prototype['name'] . '('; 165 $args = []; 166 foreach ($prototype['arguments'] as $name => $argument) { 167 $argsLine = ($argument['type'] 168 ? $argument['type'] . ' ' 169 : '') . ($argument['by_ref'] ? '&' : '') . '$' . $name; 170 if (! $argument['required']) { 171 $argsLine .= ' = ' . var_export($argument['default'], true); 172 } 173 $args[] = $argsLine; 174 } 175 $line .= implode(', ', $args); 176 $line .= ')'; 177 178 return $line; 179 } 180 181 return $prototype; 182 } 183 184 /** 185 * Get function parameters 186 * 187 * @return ParameterReflection[] 188 */ 189 public function getParameters() 190 { 191 $phpReflections = parent::getParameters(); 192 $laminasReflections = []; 193 while ($phpReflections && ($phpReflection = array_shift($phpReflections))) { 194 $instance = new ParameterReflection($this->getName(), $phpReflection->getName()); 195 $laminasReflections[] = $instance; 196 unset($phpReflection); 197 } 198 unset($phpReflections); 199 200 return $laminasReflections; 201 } 202 203 /** 204 * Get return type tag 205 * 206 * @throws Exception\InvalidArgumentException 207 * @return DocBlockReflection 208 */ 209 public function getReturn() 210 { 211 $docBlock = $this->getDocBlock(); 212 if (! $docBlock->hasTag('return')) { 213 throw new Exception\InvalidArgumentException( 214 'Function does not specify an @return annotation tag; cannot determine return type' 215 ); 216 } 217 218 $tag = $docBlock->getTag('return'); 219 220 return new DocBlockReflection('@return ' . $tag->getDescription()); 221 } 222 223 /** 224 * Get method body 225 * 226 * @return string|false 227 */ 228 public function getBody() 229 { 230 $fileName = $this->getFileName(); 231 if (false === $fileName) { 232 throw new Exception\InvalidArgumentException( 233 'Cannot determine internals functions body' 234 ); 235 } 236 237 $startLine = $this->getStartLine(); 238 $endLine = $this->getEndLine(); 239 240 // eval'd protect 241 if (preg_match('#\((\d+)\) : eval\(\)\'d code$#', $fileName, $matches)) { 242 $fileName = preg_replace('#\(\d+\) : eval\(\)\'d code$#', '', $fileName); 243 $startLine = $endLine = $matches[1]; 244 } 245 246 $lines = array_slice( 247 file($fileName, FILE_IGNORE_NEW_LINES), 248 $startLine - 1, 249 $endLine - ($startLine - 1), 250 true 251 ); 252 253 $functionLine = implode("\n", $lines); 254 255 $body = false; 256 if ($this->isClosure()) { 257 preg_match('#function\s*\([^\)]*\)\s*(use\s*\([^\)]+\))?\s*\{(.*\;)\s*\}#s', $functionLine, $matches); 258 if (isset($matches[2])) { 259 $body = $matches[2]; 260 } 261 } else { 262 $name = substr($this->getName(), strrpos($this->getName(), '\\') + 1); 263 preg_match('#function\s+' . $name . '\s*\([^\)]*\)\s*{([^{}]+({[^}]+})*[^}]+)}#', $functionLine, $matches); 264 if (isset($matches[1])) { 265 $body = $matches[1]; 266 } 267 } 268 269 return $body; 270 } 271 272 /** 273 * @return string 274 */ 275 public function toString() 276 { 277 return $this->__toString(); 278 } 279 280 /** 281 * Required due to bug in php 282 * 283 * @return string 284 */ 285 public function __toString() 286 { 287 return parent::__toString(); 288 } 289} 290