comparison vendor/phpdocumentor/reflection-docblock/src/phpDocumentor/Reflection/DocBlock.php @ 0:4c8ae668cc8c

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