Chris@0
|
1 <?php
|
Chris@0
|
2 /**
|
Chris@0
|
3 * phpDocumentor
|
Chris@0
|
4 *
|
Chris@0
|
5 * PHP Version 5.3
|
Chris@0
|
6 *
|
Chris@0
|
7 * @author Mike van Riel <mike.vanriel@naenius.com>
|
Chris@0
|
8 * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com)
|
Chris@0
|
9 * @license http://www.opensource.org/licenses/mit-license.php MIT
|
Chris@0
|
10 * @link http://phpdoc.org
|
Chris@0
|
11 */
|
Chris@0
|
12
|
Chris@0
|
13 namespace phpDocumentor\Reflection;
|
Chris@0
|
14
|
Chris@0
|
15 use phpDocumentor\Reflection\DocBlock\Tag;
|
Chris@0
|
16 use phpDocumentor\Reflection\DocBlock\Context;
|
Chris@0
|
17 use phpDocumentor\Reflection\DocBlock\Location;
|
Chris@0
|
18
|
Chris@0
|
19 /**
|
Chris@0
|
20 * Parses the DocBlock for any structure.
|
Chris@0
|
21 *
|
Chris@0
|
22 * @author Mike van Riel <mike.vanriel@naenius.com>
|
Chris@0
|
23 * @license http://www.opensource.org/licenses/mit-license.php MIT
|
Chris@0
|
24 * @link http://phpdoc.org
|
Chris@0
|
25 */
|
Chris@0
|
26 class DocBlock implements \Reflector
|
Chris@0
|
27 {
|
Chris@0
|
28 /** @var string The opening line for this docblock. */
|
Chris@0
|
29 protected $short_description = '';
|
Chris@0
|
30
|
Chris@0
|
31 /**
|
Chris@0
|
32 * @var DocBlock\Description The actual
|
Chris@0
|
33 * description for this docblock.
|
Chris@0
|
34 */
|
Chris@0
|
35 protected $long_description = null;
|
Chris@0
|
36
|
Chris@0
|
37 /**
|
Chris@0
|
38 * @var Tag[] An array containing all
|
Chris@0
|
39 * the tags in this docblock; except inline.
|
Chris@0
|
40 */
|
Chris@0
|
41 protected $tags = array();
|
Chris@0
|
42
|
Chris@0
|
43 /** @var Context Information about the context of this DocBlock. */
|
Chris@0
|
44 protected $context = null;
|
Chris@0
|
45
|
Chris@0
|
46 /** @var Location Information about the location of this DocBlock. */
|
Chris@0
|
47 protected $location = null;
|
Chris@0
|
48
|
Chris@0
|
49 /** @var bool Is this DocBlock (the start of) a template? */
|
Chris@0
|
50 protected $isTemplateStart = false;
|
Chris@0
|
51
|
Chris@0
|
52 /** @var bool Does this DocBlock signify the end of a DocBlock template? */
|
Chris@0
|
53 protected $isTemplateEnd = false;
|
Chris@0
|
54
|
Chris@0
|
55 /**
|
Chris@0
|
56 * Parses the given docblock and populates the member fields.
|
Chris@0
|
57 *
|
Chris@0
|
58 * The constructor may also receive namespace information such as the
|
Chris@0
|
59 * current namespace and aliases. This information is used by some tags
|
Chris@0
|
60 * (e.g. @return, @param, etc.) to turn a relative Type into a FQCN.
|
Chris@0
|
61 *
|
Chris@0
|
62 * @param \Reflector|string $docblock A docblock comment (including
|
Chris@0
|
63 * asterisks) or reflector supporting the getDocComment method.
|
Chris@0
|
64 * @param Context $context The context in which the DocBlock
|
Chris@0
|
65 * occurs.
|
Chris@0
|
66 * @param Location $location The location within the file that this
|
Chris@0
|
67 * DocBlock occurs in.
|
Chris@0
|
68 *
|
Chris@0
|
69 * @throws \InvalidArgumentException if the given argument does not have the
|
Chris@0
|
70 * getDocComment method.
|
Chris@0
|
71 */
|
Chris@0
|
72 public function __construct(
|
Chris@0
|
73 $docblock,
|
Chris@0
|
74 Context $context = null,
|
Chris@0
|
75 Location $location = null
|
Chris@0
|
76 ) {
|
Chris@0
|
77 if (is_object($docblock)) {
|
Chris@0
|
78 if (!method_exists($docblock, 'getDocComment')) {
|
Chris@0
|
79 throw new \InvalidArgumentException(
|
Chris@0
|
80 'Invalid object passed; the given reflector must support '
|
Chris@0
|
81 . 'the getDocComment method'
|
Chris@0
|
82 );
|
Chris@0
|
83 }
|
Chris@0
|
84
|
Chris@0
|
85 $docblock = $docblock->getDocComment();
|
Chris@0
|
86 }
|
Chris@0
|
87
|
Chris@0
|
88 $docblock = $this->cleanInput($docblock);
|
Chris@0
|
89
|
Chris@0
|
90 list($templateMarker, $short, $long, $tags) = $this->splitDocBlock($docblock);
|
Chris@0
|
91 $this->isTemplateStart = $templateMarker === '#@+';
|
Chris@0
|
92 $this->isTemplateEnd = $templateMarker === '#@-';
|
Chris@0
|
93 $this->short_description = $short;
|
Chris@0
|
94 $this->long_description = new DocBlock\Description($long, $this);
|
Chris@0
|
95 $this->parseTags($tags);
|
Chris@0
|
96
|
Chris@0
|
97 $this->context = $context;
|
Chris@0
|
98 $this->location = $location;
|
Chris@0
|
99 }
|
Chris@0
|
100
|
Chris@0
|
101 /**
|
Chris@0
|
102 * Strips the asterisks from the DocBlock comment.
|
Chris@0
|
103 *
|
Chris@0
|
104 * @param string $comment String containing the comment text.
|
Chris@0
|
105 *
|
Chris@0
|
106 * @return string
|
Chris@0
|
107 */
|
Chris@0
|
108 protected function cleanInput($comment)
|
Chris@0
|
109 {
|
Chris@0
|
110 $comment = trim(
|
Chris@0
|
111 preg_replace(
|
Chris@0
|
112 '#[ \t]*(?:\/\*\*|\*\/|\*)?[ \t]{0,1}(.*)?#u',
|
Chris@0
|
113 '$1',
|
Chris@0
|
114 $comment
|
Chris@0
|
115 )
|
Chris@0
|
116 );
|
Chris@0
|
117
|
Chris@0
|
118 // reg ex above is not able to remove */ from a single line docblock
|
Chris@0
|
119 if (substr($comment, -2) == '*/') {
|
Chris@0
|
120 $comment = trim(substr($comment, 0, -2));
|
Chris@0
|
121 }
|
Chris@0
|
122
|
Chris@0
|
123 // normalize strings
|
Chris@0
|
124 $comment = str_replace(array("\r\n", "\r"), "\n", $comment);
|
Chris@0
|
125
|
Chris@0
|
126 return $comment;
|
Chris@0
|
127 }
|
Chris@0
|
128
|
Chris@0
|
129 /**
|
Chris@0
|
130 * Splits the DocBlock into a template marker, summary, description and block of tags.
|
Chris@0
|
131 *
|
Chris@0
|
132 * @param string $comment Comment to split into the sub-parts.
|
Chris@0
|
133 *
|
Chris@0
|
134 * @author Richard van Velzen (@_richardJ) Special thanks to Richard for the regex responsible for the split.
|
Chris@0
|
135 * @author Mike van Riel <me@mikevanriel.com> for extending the regex with template marker support.
|
Chris@0
|
136 *
|
Chris@0
|
137 * @return string[] containing the template marker (if any), summary, description and a string containing the tags.
|
Chris@0
|
138 */
|
Chris@0
|
139 protected function splitDocBlock($comment)
|
Chris@0
|
140 {
|
Chris@0
|
141 // Performance improvement cheat: if the first character is an @ then only tags are in this DocBlock. This
|
Chris@0
|
142 // method does not split tags so we return this verbatim as the fourth result (tags). This saves us the
|
Chris@0
|
143 // performance impact of running a regular expression
|
Chris@0
|
144 if (strpos($comment, '@') === 0) {
|
Chris@0
|
145 return array('', '', '', $comment);
|
Chris@0
|
146 }
|
Chris@0
|
147
|
Chris@0
|
148 // clears all extra horizontal whitespace from the line endings to prevent parsing issues
|
Chris@0
|
149 $comment = preg_replace('/\h*$/Sum', '', $comment);
|
Chris@0
|
150
|
Chris@0
|
151 /*
|
Chris@0
|
152 * Splits the docblock into a template marker, short description, long description and tags section
|
Chris@0
|
153 *
|
Chris@0
|
154 * - The template marker is empty, #@+ or #@- if the DocBlock starts with either of those (a newline may
|
Chris@0
|
155 * occur after it and will be stripped).
|
Chris@0
|
156 * - The short description is started from the first character until a dot is encountered followed by a
|
Chris@0
|
157 * newline OR two consecutive newlines (horizontal whitespace is taken into account to consider spacing
|
Chris@0
|
158 * errors). This is optional.
|
Chris@0
|
159 * - The long description, any character until a new line is encountered followed by an @ and word
|
Chris@0
|
160 * characters (a tag). This is optional.
|
Chris@0
|
161 * - Tags; the remaining characters
|
Chris@0
|
162 *
|
Chris@0
|
163 * Big thanks to RichardJ for contributing this Regular Expression
|
Chris@0
|
164 */
|
Chris@0
|
165 preg_match(
|
Chris@0
|
166 '/
|
Chris@0
|
167 \A
|
Chris@0
|
168 # 1. Extract the template marker
|
Chris@0
|
169 (?:(\#\@\+|\#\@\-)\n?)?
|
Chris@0
|
170
|
Chris@0
|
171 # 2. Extract the summary
|
Chris@0
|
172 (?:
|
Chris@0
|
173 (?! @\pL ) # The summary may not start with an @
|
Chris@0
|
174 (
|
Chris@0
|
175 [^\n.]+
|
Chris@0
|
176 (?:
|
Chris@0
|
177 (?! \. \n | \n{2} ) # End summary upon a dot followed by newline or two newlines
|
Chris@0
|
178 [\n.] (?! [ \t]* @\pL ) # End summary when an @ is found as first character on a new line
|
Chris@0
|
179 [^\n.]+ # Include anything else
|
Chris@0
|
180 )*
|
Chris@0
|
181 \.?
|
Chris@0
|
182 )?
|
Chris@0
|
183 )
|
Chris@0
|
184
|
Chris@0
|
185 # 3. Extract the description
|
Chris@0
|
186 (?:
|
Chris@0
|
187 \s* # Some form of whitespace _must_ precede a description because a summary must be there
|
Chris@0
|
188 (?! @\pL ) # The description may not start with an @
|
Chris@0
|
189 (
|
Chris@0
|
190 [^\n]+
|
Chris@0
|
191 (?: \n+
|
Chris@0
|
192 (?! [ \t]* @\pL ) # End description when an @ is found as first character on a new line
|
Chris@0
|
193 [^\n]+ # Include anything else
|
Chris@0
|
194 )*
|
Chris@0
|
195 )
|
Chris@0
|
196 )?
|
Chris@0
|
197
|
Chris@0
|
198 # 4. Extract the tags (anything that follows)
|
Chris@0
|
199 (\s+ [\s\S]*)? # everything that follows
|
Chris@0
|
200 /ux',
|
Chris@0
|
201 $comment,
|
Chris@0
|
202 $matches
|
Chris@0
|
203 );
|
Chris@0
|
204 array_shift($matches);
|
Chris@0
|
205
|
Chris@0
|
206 while (count($matches) < 4) {
|
Chris@0
|
207 $matches[] = '';
|
Chris@0
|
208 }
|
Chris@0
|
209
|
Chris@0
|
210 return $matches;
|
Chris@0
|
211 }
|
Chris@0
|
212
|
Chris@0
|
213 /**
|
Chris@0
|
214 * Creates the tag objects.
|
Chris@0
|
215 *
|
Chris@0
|
216 * @param string $tags Tag block to parse.
|
Chris@0
|
217 *
|
Chris@0
|
218 * @return void
|
Chris@0
|
219 */
|
Chris@0
|
220 protected function parseTags($tags)
|
Chris@0
|
221 {
|
Chris@0
|
222 $result = array();
|
Chris@0
|
223 $tags = trim($tags);
|
Chris@0
|
224 if ('' !== $tags) {
|
Chris@0
|
225 if ('@' !== $tags[0]) {
|
Chris@0
|
226 throw new \LogicException(
|
Chris@0
|
227 'A tag block started with text instead of an actual tag,'
|
Chris@0
|
228 . ' this makes the tag block invalid: ' . $tags
|
Chris@0
|
229 );
|
Chris@0
|
230 }
|
Chris@0
|
231 foreach (explode("\n", $tags) as $tag_line) {
|
Chris@0
|
232 if (isset($tag_line[0]) && ($tag_line[0] === '@')) {
|
Chris@0
|
233 $result[] = $tag_line;
|
Chris@0
|
234 } else {
|
Chris@0
|
235 $result[count($result) - 1] .= "\n" . $tag_line;
|
Chris@0
|
236 }
|
Chris@0
|
237 }
|
Chris@0
|
238
|
Chris@0
|
239 // create proper Tag objects
|
Chris@0
|
240 foreach ($result as $key => $tag_line) {
|
Chris@0
|
241 $result[$key] = Tag::createInstance(trim($tag_line), $this);
|
Chris@0
|
242 }
|
Chris@0
|
243 }
|
Chris@0
|
244
|
Chris@0
|
245 $this->tags = $result;
|
Chris@0
|
246 }
|
Chris@0
|
247
|
Chris@0
|
248 /**
|
Chris@0
|
249 * Gets the text portion of the doc block.
|
Chris@0
|
250 *
|
Chris@0
|
251 * Gets the text portion (short and long description combined) of the doc
|
Chris@0
|
252 * block.
|
Chris@0
|
253 *
|
Chris@0
|
254 * @return string The text portion of the doc block.
|
Chris@0
|
255 */
|
Chris@0
|
256 public function getText()
|
Chris@0
|
257 {
|
Chris@0
|
258 $short = $this->getShortDescription();
|
Chris@0
|
259 $long = $this->getLongDescription()->getContents();
|
Chris@0
|
260
|
Chris@0
|
261 if ($long) {
|
Chris@0
|
262 return "{$short}\n\n{$long}";
|
Chris@0
|
263 } else {
|
Chris@0
|
264 return $short;
|
Chris@0
|
265 }
|
Chris@0
|
266 }
|
Chris@0
|
267
|
Chris@0
|
268 /**
|
Chris@0
|
269 * Set the text portion of the doc block.
|
Chris@0
|
270 *
|
Chris@0
|
271 * Sets the text portion (short and long description combined) of the doc
|
Chris@0
|
272 * block.
|
Chris@0
|
273 *
|
Chris@0
|
274 * @param string $docblock The new text portion of the doc block.
|
Chris@0
|
275 *
|
Chris@0
|
276 * @return $this This doc block.
|
Chris@0
|
277 */
|
Chris@0
|
278 public function setText($comment)
|
Chris@0
|
279 {
|
Chris@0
|
280 list(,$short, $long) = $this->splitDocBlock($comment);
|
Chris@0
|
281 $this->short_description = $short;
|
Chris@0
|
282 $this->long_description = new DocBlock\Description($long, $this);
|
Chris@0
|
283 return $this;
|
Chris@0
|
284 }
|
Chris@0
|
285 /**
|
Chris@0
|
286 * Returns the opening line or also known as short description.
|
Chris@0
|
287 *
|
Chris@0
|
288 * @return string
|
Chris@0
|
289 */
|
Chris@0
|
290 public function getShortDescription()
|
Chris@0
|
291 {
|
Chris@0
|
292 return $this->short_description;
|
Chris@0
|
293 }
|
Chris@0
|
294
|
Chris@0
|
295 /**
|
Chris@0
|
296 * Returns the full description or also known as long description.
|
Chris@0
|
297 *
|
Chris@0
|
298 * @return DocBlock\Description
|
Chris@0
|
299 */
|
Chris@0
|
300 public function getLongDescription()
|
Chris@0
|
301 {
|
Chris@0
|
302 return $this->long_description;
|
Chris@0
|
303 }
|
Chris@0
|
304
|
Chris@0
|
305 /**
|
Chris@0
|
306 * Returns whether this DocBlock is the start of a Template section.
|
Chris@0
|
307 *
|
Chris@0
|
308 * A Docblock may serve as template for a series of subsequent DocBlocks. This is indicated by a special marker
|
Chris@0
|
309 * (`#@+`) that is appended directly after the opening `/**` of a DocBlock.
|
Chris@0
|
310 *
|
Chris@0
|
311 * An example of such an opening is:
|
Chris@0
|
312 *
|
Chris@0
|
313 * ```
|
Chris@0
|
314 * /**#@+
|
Chris@0
|
315 * * My DocBlock
|
Chris@0
|
316 * * /
|
Chris@0
|
317 * ```
|
Chris@0
|
318 *
|
Chris@0
|
319 * The description and tags (not the summary!) are copied onto all subsequent DocBlocks and also applied to all
|
Chris@0
|
320 * elements that follow until another DocBlock is found that contains the closing marker (`#@-`).
|
Chris@0
|
321 *
|
Chris@0
|
322 * @see self::isTemplateEnd() for the check whether a closing marker was provided.
|
Chris@0
|
323 *
|
Chris@0
|
324 * @return boolean
|
Chris@0
|
325 */
|
Chris@0
|
326 public function isTemplateStart()
|
Chris@0
|
327 {
|
Chris@0
|
328 return $this->isTemplateStart;
|
Chris@0
|
329 }
|
Chris@0
|
330
|
Chris@0
|
331 /**
|
Chris@0
|
332 * Returns whether this DocBlock is the end of a Template section.
|
Chris@0
|
333 *
|
Chris@0
|
334 * @see self::isTemplateStart() for a more complete description of the Docblock Template functionality.
|
Chris@0
|
335 *
|
Chris@0
|
336 * @return boolean
|
Chris@0
|
337 */
|
Chris@0
|
338 public function isTemplateEnd()
|
Chris@0
|
339 {
|
Chris@0
|
340 return $this->isTemplateEnd;
|
Chris@0
|
341 }
|
Chris@0
|
342
|
Chris@0
|
343 /**
|
Chris@0
|
344 * Returns the current context.
|
Chris@0
|
345 *
|
Chris@0
|
346 * @return Context
|
Chris@0
|
347 */
|
Chris@0
|
348 public function getContext()
|
Chris@0
|
349 {
|
Chris@0
|
350 return $this->context;
|
Chris@0
|
351 }
|
Chris@0
|
352
|
Chris@0
|
353 /**
|
Chris@0
|
354 * Returns the current location.
|
Chris@0
|
355 *
|
Chris@0
|
356 * @return Location
|
Chris@0
|
357 */
|
Chris@0
|
358 public function getLocation()
|
Chris@0
|
359 {
|
Chris@0
|
360 return $this->location;
|
Chris@0
|
361 }
|
Chris@0
|
362
|
Chris@0
|
363 /**
|
Chris@0
|
364 * Returns the tags for this DocBlock.
|
Chris@0
|
365 *
|
Chris@0
|
366 * @return Tag[]
|
Chris@0
|
367 */
|
Chris@0
|
368 public function getTags()
|
Chris@0
|
369 {
|
Chris@0
|
370 return $this->tags;
|
Chris@0
|
371 }
|
Chris@0
|
372
|
Chris@0
|
373 /**
|
Chris@0
|
374 * Returns an array of tags matching the given name. If no tags are found
|
Chris@0
|
375 * an empty array is returned.
|
Chris@0
|
376 *
|
Chris@0
|
377 * @param string $name String to search by.
|
Chris@0
|
378 *
|
Chris@0
|
379 * @return Tag[]
|
Chris@0
|
380 */
|
Chris@0
|
381 public function getTagsByName($name)
|
Chris@0
|
382 {
|
Chris@0
|
383 $result = array();
|
Chris@0
|
384
|
Chris@0
|
385 /** @var Tag $tag */
|
Chris@0
|
386 foreach ($this->getTags() as $tag) {
|
Chris@0
|
387 if ($tag->getName() != $name) {
|
Chris@0
|
388 continue;
|
Chris@0
|
389 }
|
Chris@0
|
390
|
Chris@0
|
391 $result[] = $tag;
|
Chris@0
|
392 }
|
Chris@0
|
393
|
Chris@0
|
394 return $result;
|
Chris@0
|
395 }
|
Chris@0
|
396
|
Chris@0
|
397 /**
|
Chris@0
|
398 * Checks if a tag of a certain type is present in this DocBlock.
|
Chris@0
|
399 *
|
Chris@0
|
400 * @param string $name Tag name to check for.
|
Chris@0
|
401 *
|
Chris@0
|
402 * @return bool
|
Chris@0
|
403 */
|
Chris@0
|
404 public function hasTag($name)
|
Chris@0
|
405 {
|
Chris@0
|
406 /** @var Tag $tag */
|
Chris@0
|
407 foreach ($this->getTags() as $tag) {
|
Chris@0
|
408 if ($tag->getName() == $name) {
|
Chris@0
|
409 return true;
|
Chris@0
|
410 }
|
Chris@0
|
411 }
|
Chris@0
|
412
|
Chris@0
|
413 return false;
|
Chris@0
|
414 }
|
Chris@0
|
415
|
Chris@0
|
416 /**
|
Chris@0
|
417 * Appends a tag at the end of the list of tags.
|
Chris@0
|
418 *
|
Chris@0
|
419 * @param Tag $tag The tag to add.
|
Chris@0
|
420 *
|
Chris@0
|
421 * @return Tag The newly added tag.
|
Chris@0
|
422 *
|
Chris@0
|
423 * @throws \LogicException When the tag belongs to a different DocBlock.
|
Chris@0
|
424 */
|
Chris@0
|
425 public function appendTag(Tag $tag)
|
Chris@0
|
426 {
|
Chris@0
|
427 if (null === $tag->getDocBlock()) {
|
Chris@0
|
428 $tag->setDocBlock($this);
|
Chris@0
|
429 }
|
Chris@0
|
430
|
Chris@0
|
431 if ($tag->getDocBlock() === $this) {
|
Chris@0
|
432 $this->tags[] = $tag;
|
Chris@0
|
433 } else {
|
Chris@0
|
434 throw new \LogicException(
|
Chris@0
|
435 'This tag belongs to a different DocBlock object.'
|
Chris@0
|
436 );
|
Chris@0
|
437 }
|
Chris@0
|
438
|
Chris@0
|
439 return $tag;
|
Chris@0
|
440 }
|
Chris@0
|
441
|
Chris@0
|
442
|
Chris@0
|
443 /**
|
Chris@0
|
444 * Builds a string representation of this object.
|
Chris@0
|
445 *
|
Chris@0
|
446 * @todo determine the exact format as used by PHP Reflection and
|
Chris@0
|
447 * implement it.
|
Chris@0
|
448 *
|
Chris@0
|
449 * @return string
|
Chris@0
|
450 * @codeCoverageIgnore Not yet implemented
|
Chris@0
|
451 */
|
Chris@0
|
452 public static function export()
|
Chris@0
|
453 {
|
Chris@0
|
454 throw new \Exception('Not yet implemented');
|
Chris@0
|
455 }
|
Chris@0
|
456
|
Chris@0
|
457 /**
|
Chris@0
|
458 * Returns the exported information (we should use the export static method
|
Chris@0
|
459 * BUT this throws an exception at this point).
|
Chris@0
|
460 *
|
Chris@0
|
461 * @return string
|
Chris@0
|
462 * @codeCoverageIgnore Not yet implemented
|
Chris@0
|
463 */
|
Chris@0
|
464 public function __toString()
|
Chris@0
|
465 {
|
Chris@0
|
466 return 'Not yet implemented';
|
Chris@0
|
467 }
|
Chris@0
|
468 }
|