├── README.md ├── LICENSE.TXT ├── test.php └── docblock-parser.php /README.md: -------------------------------------------------------------------------------- 1 | # PHP DocBlock Parser 2 | 3 | A utility for parsing the documentation blocks of PHP features. 4 | 5 | ## License 6 | 7 | This is my first public project on GitHub, so I've chosen [WTF Public License](http://sam.zoy.org/wtfpl/) but attribution and patch submissions are of course appreciated. 8 | 9 | ## Author 10 | 11 | Paul Scott 12 | -------------------------------------------------------------------------------- /LICENSE.TXT: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /test.php: -------------------------------------------------------------------------------- 1 | 51 | */ 52 | function d() {} 53 | } 54 | -------------------------------------------------------------------------------- /docblock-parser.php: -------------------------------------------------------------------------------- 1 | 6 | * {@link http://www.github.com/icio/PHP-DocBlock-Parser} 7 | */ 8 | class DocBlock 9 | { 10 | /** 11 | * The docblock of a class. 12 | * @param String $class The class name 13 | * @return DocBlock 14 | */ 15 | public static function ofClass($class) 16 | { 17 | return DocBlock::of(new ReflectionClass($class)); 18 | } 19 | 20 | /** 21 | * The docblock of a class property. 22 | * @param String $class The class on which the property is defined 23 | * @param String $property The name of the property 24 | * @return DocBlock 25 | */ 26 | public static function ofProperty($class, $property) 27 | { 28 | return DocBlock::of(new ReflectionProperty($class, $property)); 29 | } 30 | 31 | /** 32 | * The docblock of a function. 33 | * @param String $function The name of the function 34 | * @return DocBlock 35 | */ 36 | public static function ofFunction($function) 37 | { 38 | return DocBlock::of(new ReflectionFunction($function)); 39 | } 40 | 41 | /** 42 | * The docblock of a class method. 43 | * @param String $class The class on which the method is defined 44 | * @param String $method The name of the method 45 | * @return DocBlock 46 | */ 47 | public static function ofMethod($class, $method) 48 | { 49 | return DocBlock::of(new ReflectionMethod($class, $method)); 50 | } 51 | 52 | /** 53 | * The docblock of a reflection. 54 | * @param Reflector $ref A reflector object defining `getDocComment`. 55 | * @return DocBlock 56 | */ 57 | public static function of($ref) 58 | { 59 | if (method_exists($ref, 'getDocComment')) 60 | return new DocBlock($ref->getDocComment()); 61 | return null; 62 | } 63 | 64 | /* 65 | * ================================== 66 | */ 67 | 68 | /** 69 | * Tags in the docblock that have a whitepace-delimited number of parameters 70 | * (such as `@param type var desc` and `@return type desc`) and the names of 71 | * those parameters. 72 | * 73 | * @type Array 74 | */ 75 | public static $vectors = array( 76 | 'param' => array('type', 'var', 'desc'), 77 | 'return' => array('type', 'desc'), 78 | ); 79 | 80 | /** 81 | * The description of the symbol 82 | * @type String 83 | */ 84 | public $desc; 85 | 86 | /** 87 | * The tags defined in the docblock. 88 | * 89 | * The array has keys which are the tag names (excluding the @) and values 90 | * that are arrays, each of which is an entry for the tag. 91 | * 92 | * In the case where the tag name is defined in {@see DocBlock::$vectors} the 93 | * value within the tag-value array is an array in itself with keys as 94 | * described by {@see DocBlock::$vectors}. 95 | * 96 | * @type Array 97 | */ 98 | public $tags; 99 | 100 | /** 101 | * The entire DocBlock comment that was parsed. 102 | * @type String 103 | */ 104 | public $comment; 105 | 106 | /** 107 | * CONSTRUCTOR. 108 | * @param String $comment The text of the docblock 109 | */ 110 | public function __construct($comment = null) 111 | { 112 | if ($comment) 113 | $this->setComment($comment); 114 | } 115 | 116 | /** 117 | * Set and parse the docblock comment. 118 | * @param String $comment The docblock 119 | */ 120 | public function setComment($comment) 121 | { 122 | $this->desc = ''; 123 | $this->tags = array(); 124 | $this->comment = $comment; 125 | 126 | $this->parseComment($comment); 127 | } 128 | 129 | /** 130 | * Parse the comment into the component parts and set the state of the object. 131 | * @param String $comment The docblock 132 | */ 133 | protected function parseComment($comment) 134 | { 135 | // Strip the opening and closing tags of the docblock 136 | $comment = substr($comment, 3, -2); 137 | 138 | // Split into arrays of lines 139 | $comment = preg_split('/\r?\n\r?/', $comment); 140 | 141 | // Trim asterisks and whitespace from the beginning and whitespace from the end of lines 142 | $comment = array_map(function($line) { 143 | return ltrim(rtrim($line), "* \t\n\r\0\x0B"); 144 | }, $comment); 145 | 146 | // Group the lines together by @tags 147 | $blocks = array(); 148 | $b = -1; 149 | foreach ($comment as $line) 150 | { 151 | if (self::isTagged($line)) { 152 | $b++; 153 | $blocks[] = array(); 154 | } else if($b == -1) { 155 | $b = 0; 156 | $blocks[] = array(); 157 | } 158 | $blocks[$b][] = $line; 159 | } 160 | 161 | // Parse the blocks 162 | foreach ($blocks as $block => $body) 163 | { 164 | $body = trim(implode("\n", $body)); 165 | 166 | if ($block == 0 && !self::isTagged($body)) 167 | { 168 | // This is the description block 169 | $this->desc = $body; 170 | continue; 171 | } 172 | else 173 | { 174 | // This block is tagged 175 | $tag = substr(self::strTag($body), 1); 176 | $body = ltrim(substr($body, strlen($tag)+2)); 177 | 178 | if (isset(self::$vectors[$tag])) { 179 | // The tagged block is a vector 180 | $count = count(self::$vectors[$tag]); 181 | if ($body) { 182 | $parts = preg_split('/\s+/', $body, $count); 183 | } else { 184 | $parts = array(); 185 | } 186 | // Default the trailing values 187 | $parts = array_pad($parts, $count, null); 188 | // Store as a mapped array 189 | $this->tags[$tag][] = array_combine( 190 | self::$vectors[$tag], 191 | $parts 192 | ); 193 | } 194 | else { 195 | // The tagged block is only text 196 | $this->tags[$tag][] = $body; 197 | } 198 | } 199 | } 200 | } 201 | 202 | /** 203 | * Whether or not a docblock contains a given @tag. 204 | * @param String $tag The name of the @tag to check for 205 | * @return bool 206 | */ 207 | public function hasTag($tag) 208 | { 209 | return is_array($this->tags) && array_key_exists($tag, $this->tags); 210 | } 211 | 212 | /** 213 | * The value of a tag 214 | * @param String $tag 215 | * @return Array 216 | */ 217 | public function tag($tag) 218 | { 219 | return $this->hasTag($tag) ? $this->tags[$tag] : null; 220 | } 221 | 222 | /** 223 | * The value of a tag (concatenated for multiple values) 224 | * @param String $tag 225 | * @param string $sep The seperator for concatenating 226 | * @return String 227 | */ 228 | public function tagImplode($tag, $sep = ' ') 229 | { 230 | return $this->hasTag($tag) ? implode($sep, $this->tags[$tag]) : null; 231 | } 232 | 233 | /** 234 | * The value of a tag (merged recursively) 235 | * @param String $tag 236 | * @return Array 237 | */ 238 | public function tagMerge($tag) 239 | { 240 | return $this->hasTag($tag) ? array_merge_recursive($this->tags[$tag]) : null; 241 | } 242 | 243 | /* 244 | * ================================== 245 | */ 246 | 247 | /** 248 | * Whether or not a string begins with a @tag 249 | * @param String $str 250 | * @return bool 251 | */ 252 | public static function isTagged($str) 253 | { 254 | return isset($str[1]) && $str[0] == '@' && ctype_alpha($str[1]); 255 | } 256 | 257 | /** 258 | * The tag at the beginning of a string 259 | * @param String $str 260 | * @return String|null 261 | */ 262 | public static function strTag($str) 263 | { 264 | if (preg_match('/^@[a-z0-9_]+/', $str, $matches)) 265 | return $matches[0]; 266 | return null; 267 | } 268 | } 269 | --------------------------------------------------------------------------------