1<?php 2/** 3 * This file is part of phpDocumentor. 4 * 5 * For the full copyright and license information, please view the LICENSE 6 * file that was distributed with this source code. 7 * 8 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org> 9 * @license http://www.opensource.org/licenses/mit-license.php MIT 10 * @link http://phpdoc.org 11 */ 12 13namespace phpDocumentor\Reflection\DocBlock\Tags; 14 15use phpDocumentor\Reflection\DocBlock\Description; 16use phpDocumentor\Reflection\DocBlock\DescriptionFactory; 17use phpDocumentor\Reflection\Type; 18use phpDocumentor\Reflection\TypeResolver; 19use phpDocumentor\Reflection\Types\Context as TypeContext; 20use phpDocumentor\Reflection\Types\Void_; 21use Webmozart\Assert\Assert; 22 23/** 24 * Reflection class for an {@}method in a Docblock. 25 */ 26final class Method extends BaseTag implements Factory\StaticMethod 27{ 28 protected $name = 'method'; 29 30 /** @var string */ 31 private $methodName = ''; 32 33 /** @var string[] */ 34 private $arguments = []; 35 36 /** @var bool */ 37 private $isStatic = false; 38 39 /** @var Type */ 40 private $returnType; 41 42 public function __construct( 43 $methodName, 44 array $arguments = [], 45 Type $returnType = null, 46 $static = false, 47 Description $description = null 48 ) { 49 Assert::stringNotEmpty($methodName); 50 Assert::boolean($static); 51 52 if ($returnType === null) { 53 $returnType = new Void_(); 54 } 55 56 $this->methodName = $methodName; 57 $this->arguments = $this->filterArguments($arguments); 58 $this->returnType = $returnType; 59 $this->isStatic = $static; 60 $this->description = $description; 61 } 62 63 /** 64 * {@inheritdoc} 65 */ 66 public static function create( 67 $body, 68 TypeResolver $typeResolver = null, 69 DescriptionFactory $descriptionFactory = null, 70 TypeContext $context = null 71 ) { 72 Assert::stringNotEmpty($body); 73 Assert::allNotNull([ $typeResolver, $descriptionFactory ]); 74 75 // 1. none or more whitespace 76 // 2. optionally the keyword "static" followed by whitespace 77 // 3. optionally a word with underscores followed by whitespace : as 78 // type for the return value 79 // 4. then optionally a word with underscores followed by () and 80 // whitespace : as method name as used by phpDocumentor 81 // 5. then a word with underscores, followed by ( and any character 82 // until a ) and whitespace : as method name with signature 83 // 6. any remaining text : as description 84 if (!preg_match( 85 '/^ 86 # Static keyword 87 # Declares a static method ONLY if type is also present 88 (?: 89 (static) 90 \s+ 91 )? 92 # Return type 93 (?: 94 ( 95 (?:[\w\|_\\\\]*\$this[\w\|_\\\\]*) 96 | 97 (?: 98 (?:[\w\|_\\\\]+) 99 # array notation 100 (?:\[\])* 101 )* 102 ) 103 \s+ 104 )? 105 # Legacy method name (not captured) 106 (?: 107 [\w_]+\(\)\s+ 108 )? 109 # Method name 110 ([\w\|_\\\\]+) 111 # Arguments 112 (?: 113 \(([^\)]*)\) 114 )? 115 \s* 116 # Description 117 (.*) 118 $/sux', 119 $body, 120 $matches 121 )) { 122 return null; 123 } 124 125 list(, $static, $returnType, $methodName, $arguments, $description) = $matches; 126 127 $static = $static === 'static'; 128 129 if ($returnType === '') { 130 $returnType = 'void'; 131 } 132 133 $returnType = $typeResolver->resolve($returnType, $context); 134 $description = $descriptionFactory->create($description, $context); 135 136 if (is_string($arguments) && strlen($arguments) > 0) { 137 $arguments = explode(',', $arguments); 138 foreach ($arguments as &$argument) { 139 $argument = explode(' ', self::stripRestArg(trim($argument)), 2); 140 if ($argument[0][0] === '$') { 141 $argumentName = substr($argument[0], 1); 142 $argumentType = new Void_(); 143 } else { 144 $argumentType = $typeResolver->resolve($argument[0], $context); 145 $argumentName = ''; 146 if (isset($argument[1])) { 147 $argument[1] = self::stripRestArg($argument[1]); 148 $argumentName = substr($argument[1], 1); 149 } 150 } 151 152 $argument = [ 'name' => $argumentName, 'type' => $argumentType]; 153 } 154 } else { 155 $arguments = []; 156 } 157 158 return new static($methodName, $arguments, $returnType, $static, $description); 159 } 160 161 /** 162 * Retrieves the method name. 163 * 164 * @return string 165 */ 166 public function getMethodName() 167 { 168 return $this->methodName; 169 } 170 171 /** 172 * @return string[] 173 */ 174 public function getArguments() 175 { 176 return $this->arguments; 177 } 178 179 /** 180 * Checks whether the method tag describes a static method or not. 181 * 182 * @return bool TRUE if the method declaration is for a static method, FALSE otherwise. 183 */ 184 public function isStatic() 185 { 186 return $this->isStatic; 187 } 188 189 /** 190 * @return Type 191 */ 192 public function getReturnType() 193 { 194 return $this->returnType; 195 } 196 197 public function __toString() 198 { 199 $arguments = []; 200 foreach ($this->arguments as $argument) { 201 $arguments[] = $argument['type'] . ' $' . $argument['name']; 202 } 203 204 return trim(($this->isStatic() ? 'static ' : '') 205 . (string)$this->returnType . ' ' 206 . $this->methodName 207 . '(' . implode(', ', $arguments) . ')' 208 . ($this->description ? ' ' . $this->description->render() : '')); 209 } 210 211 private function filterArguments($arguments) 212 { 213 foreach ($arguments as &$argument) { 214 if (is_string($argument)) { 215 $argument = [ 'name' => $argument ]; 216 } 217 218 if (! isset($argument['type'])) { 219 $argument['type'] = new Void_(); 220 } 221 222 $keys = array_keys($argument); 223 sort($keys); 224 if ($keys !== [ 'name', 'type' ]) { 225 throw new \InvalidArgumentException( 226 'Arguments can only have the "name" and "type" fields, found: ' . var_export($keys, true) 227 ); 228 } 229 } 230 231 return $arguments; 232 } 233 234 private static function stripRestArg($argument) 235 { 236 if (strpos($argument, '...') === 0) { 237 $argument = trim(substr($argument, 3)); 238 } 239 240 return $argument; 241 } 242} 243