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 Laminas\Code\Reflection\DocBlock\Tag\TagInterface as DocBlockTagInterface; 12use Laminas\Code\Reflection\DocBlock\TagManager as DocBlockTagManager; 13use Laminas\Code\Scanner\DocBlockScanner; 14use Reflector; 15 16use function count; 17use function get_class; 18use function is_string; 19use function ltrim; 20use function method_exists; 21use function preg_replace; 22use function sprintf; 23use function substr_count; 24 25class DocBlockReflection implements ReflectionInterface 26{ 27 /** 28 * @var Reflector 29 */ 30 protected $reflector; 31 32 /** 33 * @var string 34 */ 35 protected $docComment; 36 37 /** 38 * @var DocBlockTagManager 39 */ 40 protected $tagManager; 41 42 /** 43 * @var int 44 */ 45 protected $startLine; 46 47 /** 48 * @var int 49 */ 50 protected $endLine; 51 52 /** 53 * @var string 54 */ 55 protected $cleanDocComment; 56 57 /** 58 * @var string 59 */ 60 protected $longDescription; 61 62 /** 63 * @var string 64 */ 65 protected $shortDescription; 66 67 /** 68 * @var array 69 */ 70 protected $tags = []; 71 72 /** 73 * @var bool 74 */ 75 protected $isReflected = false; 76 77 /** 78 * Export reflection 79 * 80 * Required by the Reflector interface. 81 * 82 * @todo What should this do? 83 * @return void 84 */ 85 public static function export() 86 { 87 } 88 89 /** 90 * @param Reflector|string $commentOrReflector 91 * @param null|DocBlockTagManager $tagManager 92 * @throws Exception\InvalidArgumentException 93 */ 94 public function __construct($commentOrReflector, DocBlockTagManager $tagManager = null) 95 { 96 if (! $tagManager) { 97 $tagManager = new DocBlockTagManager(); 98 $tagManager->initializeDefaultTags(); 99 } 100 $this->tagManager = $tagManager; 101 102 if ($commentOrReflector instanceof Reflector) { 103 $this->reflector = $commentOrReflector; 104 if (! method_exists($commentOrReflector, 'getDocComment')) { 105 throw new Exception\InvalidArgumentException('Reflector must contain method "getDocComment"'); 106 } 107 /* @var MethodReflection $commentOrReflector */ 108 $this->docComment = $commentOrReflector->getDocComment(); 109 110 // determine line numbers 111 $lineCount = substr_count($this->docComment, "\n"); 112 $this->startLine = $this->reflector->getStartLine() - $lineCount - 1; 113 $this->endLine = $this->reflector->getStartLine() - 1; 114 } elseif (is_string($commentOrReflector)) { 115 $this->docComment = $commentOrReflector; 116 } else { 117 throw new Exception\InvalidArgumentException(sprintf( 118 '%s must have a (string) DocComment or a Reflector in the constructor', 119 get_class($this) 120 )); 121 } 122 123 if ($this->docComment == '') { 124 throw new Exception\InvalidArgumentException('DocComment cannot be empty'); 125 } 126 127 $this->reflect(); 128 } 129 130 /** 131 * Retrieve contents of DocBlock 132 * 133 * @return string 134 */ 135 public function getContents() 136 { 137 $this->reflect(); 138 139 return $this->cleanDocComment; 140 } 141 142 /** 143 * Get start line (position) of DocBlock 144 * 145 * @return int 146 */ 147 public function getStartLine() 148 { 149 $this->reflect(); 150 151 return $this->startLine; 152 } 153 154 /** 155 * Get last line (position) of DocBlock 156 * 157 * @return int 158 */ 159 public function getEndLine() 160 { 161 $this->reflect(); 162 163 return $this->endLine; 164 } 165 166 /** 167 * Get DocBlock short description 168 * 169 * @return string 170 */ 171 public function getShortDescription() 172 { 173 $this->reflect(); 174 175 return $this->shortDescription; 176 } 177 178 /** 179 * Get DocBlock long description 180 * 181 * @return string 182 */ 183 public function getLongDescription() 184 { 185 $this->reflect(); 186 187 return $this->longDescription; 188 } 189 190 /** 191 * Does the DocBlock contain the given annotation tag? 192 * 193 * @param string $name 194 * @return bool 195 */ 196 public function hasTag($name) 197 { 198 $this->reflect(); 199 foreach ($this->tags as $tag) { 200 if ($tag->getName() == $name) { 201 return true; 202 } 203 } 204 205 return false; 206 } 207 208 /** 209 * Retrieve the given DocBlock tag 210 * 211 * @param string $name 212 * @return DocBlockTagInterface|false 213 */ 214 public function getTag($name) 215 { 216 $this->reflect(); 217 foreach ($this->tags as $tag) { 218 if ($tag->getName() == $name) { 219 return $tag; 220 } 221 } 222 223 return false; 224 } 225 226 /** 227 * Get all DocBlock annotation tags 228 * 229 * @param string $filter 230 * @return DocBlockTagInterface[] 231 */ 232 public function getTags($filter = null) 233 { 234 $this->reflect(); 235 if ($filter === null || ! is_string($filter)) { 236 return $this->tags; 237 } 238 239 $returnTags = []; 240 foreach ($this->tags as $tag) { 241 if ($tag->getName() == $filter) { 242 $returnTags[] = $tag; 243 } 244 } 245 246 return $returnTags; 247 } 248 249 /** 250 * Parse the DocBlock 251 * 252 * @return void 253 */ 254 protected function reflect() 255 { 256 if ($this->isReflected) { 257 return; 258 } 259 260 $docComment = preg_replace('#[ ]{0,1}\*/$#', '', $this->docComment); 261 262 // create a clean docComment 263 $this->cleanDocComment = preg_replace("#[ \t]*(?:/\*\*|\*/|\*)[ ]{0,1}(.*)?#", '$1', $docComment); 264 265 // @todo should be changed to remove first and last empty line 266 $this->cleanDocComment = ltrim($this->cleanDocComment, "\r\n"); 267 268 $scanner = new DocBlockScanner($docComment); 269 $this->shortDescription = ltrim($scanner->getShortDescription()); 270 $this->longDescription = ltrim($scanner->getLongDescription()); 271 272 foreach ($scanner->getTags() as $tag) { 273 $this->tags[] = $this->tagManager->createTag(ltrim($tag['name'], '@'), ltrim($tag['value'])); 274 } 275 276 $this->isReflected = true; 277 } 278 279 /** 280 * @return string 281 */ 282 public function toString() 283 { 284 $str = 'DocBlock [ /* DocBlock */ ] {' . "\n\n"; 285 $str .= ' - Tags [' . count($this->tags) . '] {' . "\n"; 286 287 foreach ($this->tags as $tag) { 288 $str .= ' ' . $tag; 289 } 290 291 $str .= ' }' . "\n"; 292 $str .= '}' . "\n"; 293 294 return $str; 295 } 296 297 /** 298 * Serialize to string 299 * 300 * Required by the Reflector interface 301 * 302 * @return string 303 */ 304 public function __toString() 305 { 306 return $this->toString(); 307 } 308} 309