@@ -46,6 +46,12 @@ class DocBlock implements \Reflector
4646 /** @var Location Information about the location of this DocBlock. */
4747 protected $ location = null ;
4848
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+
4955 /**
5056 * Parses the given docblock and populates the member fields.
5157 *
@@ -81,7 +87,9 @@ public function __construct(
8187
8288 $ docblock = $ this ->cleanInput ($ docblock );
8389
84- list ($ short , $ long , $ tags ) = $ this ->splitDocBlock ($ docblock );
90+ list ($ templateMarker , $ short , $ long , $ tags ) = $ this ->splitDocBlock ($ docblock );
91+ $ this ->isTemplateStart = $ templateMarker === '#@+ ' ;
92+ $ this ->isTemplateEnd = $ templateMarker === '#@- ' ;
8593 $ this ->short_description = $ short ;
8694 $ this ->long_description = new DocBlock \Description ($ long , $ this );
8795 $ this ->parseTags ($ tags );
@@ -119,74 +127,86 @@ protected function cleanInput($comment)
119127 }
120128
121129 /**
122- * Splits the DocBlock into a short description, long description and
123- * block of tags.
130+ * Splits the DocBlock into a template marker, summary, description and block of tags.
124131 *
125132 * @param string $comment Comment to split into the sub-parts.
126133 *
127- * @author RichardJ Special thanks to RichardJ for the regex responsible
128- * for the split .
134+ * @author Richard van Velzen (@_richardJ) Special thanks to Richard for the regex responsible for the split.
135+ * @author Mike van Riel <[email protected] > for extending the regex with template marker support . 129136 *
130- * @return string[] containing the short-, long description and an element
131- * containing the tags.
137+ * @return string[] containing the template marker (if any), summary, description and a string containing the tags.
132138 */
133139 protected function splitDocBlock ($ comment )
134140 {
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
135144 if (strpos ($ comment , '@ ' ) === 0 ) {
136- $ matches = array ('' , '' , $ comment );
137- } else {
138- // clears all extra horizontal whitespace from the line endings
139- // to prevent parsing issues
140- $ comment = preg_replace ('/\h*$/Sum ' , '' , $ comment );
141-
142- /*
143- * Splits the docblock into a short description, long description and
144- * tags section
145- * - The short description is started from the first character until
146- * a dot is encountered followed by a newline OR
147- * two consecutive newlines (horizontal whitespace is taken into
148- * account to consider spacing errors)
149- * - The long description, any character until a new line is
150- * encountered followed by an @ and word characters (a tag).
151- * This is optional.
152- * - Tags; the remaining characters
153- *
154- * Big thanks to RichardJ for contributing this Regular Expression
155- */
156- preg_match (
157- '/
158- \A (
159- [^\n.]+
160- (?:
161- (?! \. \n | \n{2} ) # disallow the first seperator here
162- [\n.] (?! [ \t]* @\pL ) # disallow second seperator
163- [^\n.]+
164- )*
165- \.?
166- )
167- (?:
168- \s* # first seperator (actually newlines but it \'s all whitespace)
169- (?! @\pL ) # disallow the rest, to make sure this one doesn \'t match,
170- #if it doesn \'t exist
171- (
172- [^\n]+
173- (?: \n+
174- (?! [ \t]* @\pL ) # disallow second seperator (@param)
175- [^\n]+
176- )*
177- )
178- )?
179- (\s+ [\s\S]*)? # everything that follows
180- /ux ' ,
181- $ comment ,
182- $ matches
183- );
184- array_shift ($ matches );
145+ return array ('' , '' , '' , $ comment );
185146 }
186147
187- while (count ($ matches ) < 3 ) {
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 ) {
188207 $ matches [] = '' ;
189208 }
209+
190210 return $ matches ;
191211 }
192212
@@ -257,7 +277,7 @@ public function getText()
257277 */
258278 public function setText ($ comment )
259279 {
260- list ($ short , $ long ) = $ this ->splitDocBlock ($ comment );
280+ list (, $ short , $ long ) = $ this ->splitDocBlock ($ comment );
261281 $ this ->short_description = $ short ;
262282 $ this ->long_description = new DocBlock \Description ($ long , $ this );
263283 return $ this ;
@@ -282,6 +302,44 @@ public function getLongDescription()
282302 return $ this ->long_description ;
283303 }
284304
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+
285343 /**
286344 * Returns the current context.
287345 *
0 commit comments