Make WordPress Core

Ticket #35065: docblocks.diff

File docblocks.diff, 98.4 KB (added by johnbillion, 9 years ago)
  • tests/phpunit/tests/docs/docblocks.php

     
     1<?php
     2
     3class Tests_DocBlocks extends WP_UnitTestCase {
     4
     5        public function setUp() {
     6
     7                if ( ! is_callable( 'spl_autoload_register' ) ) {
     8                        $this->markTestSkipped( 'Autoload is not available' );
     9                }
     10
     11                parent::setUp();
     12
     13                spl_autoload_register( array( $this, 'autoloader' ) );
     14
     15        }
     16
     17        public function autoloader( $class ) {
     18
     19                if ( 0 === strpos( $class, 'phpDocumentor' ) ) {
     20                        $file = dirname( DIR_TESTDATA ) . '/includes/' . str_replace( '\\', '/', $class ) . '.php';
     21                        require_once $file;
     22                }
     23
     24        }
     25
     26        /**
     27         * Test a function or method for a given class
     28         *
     29         * @dataProvider dataReflectionTestFunctions
     30         *
     31         * @param string|array $function The function name, or array of class name and method name.
     32         */
     33        public function testFunction( $function ) {
     34
     35                // We can't pass Reflector objects in here because they get printed out in their
     36                // entirety when a test fails
     37
     38                if ( is_array( $function ) ) {
     39                        $ref  = new ReflectionMethod( $function[0], $function[1] );
     40                        $name = $function[0] . '::' . $function[1] . '()';
     41                } else {
     42                        $ref  = new ReflectionFunction( $function );
     43                        $name = $function . '()';
     44                }
     45
     46                $docblock      = new \phpDocumentor\Reflection\DocBlock( $ref );
     47                $doc_comment   = $ref->getDocComment();
     48                $method_params = $ref->getParameters();
     49                $doc_params    = $docblock->getTagsByName( 'param' );
     50
     51                $this->assertNotFalse( $doc_comment, sprintf(
     52                        'The docblock for `%s` should not be missing.',
     53                        $name
     54                ) );
     55
     56                $this->assertNotEmpty( $docblock->getShortDescription(), sprintf(
     57                        'The docblock description for `%s` should not be empty.',
     58                        $name
     59                ) );
     60
     61                $this->assertSame( count( $method_params ), count( $doc_params ), sprintf(
     62                        'The number of @param docs for `%s` should match its number of parameters.',
     63                        $name
     64                ) );
     65
     66                foreach ( $method_params as $i => $param ) {
     67
     68                        $param_doc   = $doc_params[ $i ];
     69                        $description = $param_doc->getDescription();
     70                        $content     = $param_doc->getContent();
     71
     72                        $is_hash = ( ( 0 === strpos( $description, '{' ) ) && ( ( strlen( $description ) - 1 ) === strrpos( $description, '}' ) ) );
     73
     74                        if ( $is_hash ) {
     75                                $lines = explode( "\n", $description );
     76                                $description = $lines[1];
     77                        }
     78
     79                        $this->assertNotEmpty( $description, sprintf(
     80                                'The @param description for the `%s` parameter of `%s` should not be empty.',
     81                                $param_doc->getVariableName(),
     82                                $name
     83                        ) );
     84
     85                        list( $param_doc_type, $param_doc_name ) = preg_split( '#\s+#', $param_doc->getContent() );
     86
     87                        $this->assertSame( '$' . $param->getName(), $param_doc_name, sprintf(
     88                                'The @param name for the `%s` parameter of `%s` is incorrect.',
     89                                '$' . $param->getName(),
     90                                $name
     91                        ) );
     92
     93                        if ( $param->isArray() ) {
     94                                $this->assertNotFalse( strpos( $param_doc_type, 'array' ), sprintf(
     95                                        'The @param type hint for the `%s` parameter of `%s` should state that it accepts an array.',
     96                                        $param_doc->getVariableName(),
     97                                        $name
     98                                ) );
     99                        }
     100
     101                        if ( ( $param_class = $param->getClass() ) && ( 'stdClass' !== $param_class->getName() ) ) {
     102                                $this->assertNotFalse( strpos( $param_doc_type, $param_class->getName() ), sprintf(
     103                                        'The @param type hint for the `%s` parameter of `%s` should state that it accepts an object of type `%s`.',
     104                                        $param_doc->getVariableName(),
     105                                        $name,
     106                                        $param_class->getName()
     107                                ) );
     108                        }
     109
     110                        $this->assertFalse( strpos( $param_doc_type, 'callback' ), sprintf(
     111                                '`callback` is not a valid type. `callable` should be used in the @param type hint for the `%s` parameter of `%s` instead.',
     112                                $param_doc->getVariableName(),
     113                                $name
     114                        ) );
     115
     116                        if ( $param->isCallable() ) {
     117                                $this->assertNotFalse( strpos( $param_doc_type, 'callable' ), sprintf(
     118                                        'The @param type hint for the `%s` parameter of `%s` should state that it accepts a callable.',
     119                                        $param_doc->getVariableName(),
     120                                        $name
     121                                ) );
     122                        }
     123
     124                        if ( $param->isOptional() ) {
     125                                $this->assertNotFalse( strpos( $description, 'Optional' ), sprintf(
     126                                        'The @param description for the optional `%s` parameter of `%s` should state that it is optional.',
     127                                        $param_doc->getVariableName(),
     128                                        $name
     129                                ) );
     130                        } else {
     131                                $this->assertFalse( strpos( $description, 'Optional.' ), sprintf(
     132                                        'The @param description for the required `%s` parameter of `%s` should not state that it is optional.',
     133                                        $param_doc->getVariableName(),
     134                                        $name
     135                                ) );
     136                        }
     137
     138                        if ( $param->isDefaultValueAvailable() ) {
     139                                $this->assertNotFalse( strpos( $description, 'Default ' ), sprintf(
     140                                        'The @param description for the `%s` parameter of `%s` should state its default value.',
     141                                        $param_doc->getVariableName(),
     142                                        $name
     143                                ) );
     144                        } else {
     145                                // print_r($param->getDefaultValue());
     146                                $this->assertFalse( strpos( $description, 'Default ' ), sprintf(
     147                                        'The @param description for the `%s` parameter of `%s` should not state a default value.',
     148                                        $param_doc->getVariableName(),
     149                                        $name
     150                                ) );
     151                        }
     152
     153                }
     154
     155        }
     156
     157        protected static function rsearch( $folder, $ext ) {
     158                $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $folder ) );
     159
     160                $files = array();
     161
     162                foreach ( $iterator as $file ) {
     163
     164                        if ( $file->isDir() ) {
     165                                continue;
     166                        }
     167                        if ( $ext !== $file->getExtension() ) {
     168                                continue;
     169                        }
     170
     171                        $files[] = $file->getPathname();
     172
     173                }
     174                return $files;
     175        }
     176
     177        public function dataReflectionTestFunctions() {
     178
     179                $to_search = ABSPATH . 'wp-includes/rest-api';
     180
     181                // Recursive:
     182                // $files = self::rsearch( $to_search, 'php' );
     183
     184                // Non-recursive
     185                $files = glob( $to_search . '/*.php' );
     186
     187                $data = array();
     188
     189                $test_functions = false;
     190                $test_classes   = true;
     191
     192                foreach ( $files as $file ) {
     193
     194                        if ( $test_functions ) {
     195
     196                                preg_match_all( '#^function (\w+)#m', file_get_contents( $file ), $matches );
     197
     198                                if ( ! empty( $matches ) && ! empty( $matches[1] ) ) {
     199                                        foreach ( $matches[1] as $function ) {
     200                                                if ( ! function_exists($function)) {
     201                                                        continue;
     202                                                }
     203                                                $data[] = array(
     204                                                        $function
     205                                                );
     206                                        }
     207                                }
     208
     209                        }
     210
     211                        if ( $test_classes ) {
     212
     213                                preg_match_all( '#^class (\w+)#m', file_get_contents( $file ), $matches );
     214
     215                                if ( ! empty( $matches ) && ! empty( $matches[1] ) ) {
     216
     217                                        foreach ( $matches[1] as $class ) {
     218
     219                                                if ( ! class_exists( $class ) ) {
     220                                                        continue;
     221                                                }
     222
     223                                                $class_ref = new ReflectionClass( $class );
     224
     225                                                foreach ( $class_ref->getMethods() as $method_ref ) {
     226
     227                                                        $data[] = array(
     228                                                                array(
     229                                                                        $class,
     230                                                                        $method_ref->getName(),
     231                                                                ),
     232                                                        );
     233
     234                                                }
     235
     236                                        }
     237                                }
     238
     239                        }
     240
     241                }
     242
     243                return $data;
     244
     245        }
     246
     247}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Context.php

     
     1<?php
     2/**
     3 * phpDocumentor
     4 *
     5 * PHP Version 5.3
     6 *
     7 * @author    Vasil Rangelov <boen.robot@gmail.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
     13namespace phpDocumentor\Reflection\DocBlock;
     14
     15/**
     16 * The context in which a DocBlock occurs.
     17 *
     18 * @author  Vasil Rangelov <boen.robot@gmail.com>
     19 * @license http://www.opensource.org/licenses/mit-license.php MIT
     20 * @link    http://phpdoc.org
     21 */
     22class Context
     23{
     24    /** @var string The current namespace. */
     25    protected $namespace = '';
     26
     27    /** @var array List of namespace aliases => Fully Qualified Namespace. */
     28    protected $namespace_aliases = array();
     29   
     30    /** @var string Name of the structural element, within the namespace. */
     31    protected $lsen = '';
     32   
     33    /**
     34     * Cteates a new context.
     35     * @param string $namespace         The namespace where this DocBlock
     36     *     resides in.
     37     * @param array  $namespace_aliases List of namespace aliases => Fully
     38     *     Qualified Namespace.
     39     * @param string $lsen              Name of the structural element, within
     40     *     the namespace.
     41     */
     42    public function __construct(
     43        $namespace = '',
     44        array $namespace_aliases = array(),
     45        $lsen = ''
     46    ) {
     47        if (!empty($namespace)) {
     48            $this->setNamespace($namespace);
     49        }
     50        $this->setNamespaceAliases($namespace_aliases);
     51        $this->setLSEN($lsen);
     52    }
     53
     54    /**
     55     * @return string The namespace where this DocBlock resides in.
     56     */
     57    public function getNamespace()
     58    {
     59        return $this->namespace;
     60    }
     61
     62    /**
     63     * @return array List of namespace aliases => Fully Qualified Namespace.
     64     */
     65    public function getNamespaceAliases()
     66    {
     67        return $this->namespace_aliases;
     68    }
     69   
     70    /**
     71     * Returns the Local Structural Element Name.
     72     *
     73     * @return string Name of the structural element, within the namespace.
     74     */
     75    public function getLSEN()
     76    {
     77        return $this->lsen;
     78    }
     79   
     80    /**
     81     * Sets a new namespace.
     82     *
     83     * Sets a new namespace for the context. Leading and trailing slashes are
     84     * trimmed, and the keywords "global" and "default" are treated as aliases
     85     * to no namespace.
     86     *
     87     * @param string $namespace The new namespace to set.
     88     *
     89     * @return $this
     90     */
     91    public function setNamespace($namespace)
     92    {
     93        if ('global' !== $namespace
     94            && 'default' !== $namespace
     95        ) {
     96            // Srip leading and trailing slash
     97            $this->namespace = trim((string)$namespace, '\\');
     98        } else {
     99            $this->namespace = '';
     100        }
     101        return $this;
     102    }
     103   
     104    /**
     105     * Sets the namespace aliases, replacing all previous ones.
     106     *
     107     * @param array $namespace_aliases List of namespace aliases => Fully
     108     *     Qualified Namespace.
     109     *
     110     * @return $this
     111     */
     112    public function setNamespaceAliases(array $namespace_aliases)
     113    {
     114        $this->namespace_aliases = array();
     115        foreach ($namespace_aliases as $alias => $fqnn) {
     116            $this->setNamespaceAlias($alias, $fqnn);
     117        }
     118        return $this;
     119    }
     120   
     121    /**
     122     * Adds a namespace alias to the context.
     123     *
     124     * @param string $alias The alias name (the part after "as", or the last
     125     *     part of the Fully Qualified Namespace Name) to add.
     126     * @param string $fqnn  The Fully Qualified Namespace Name for this alias.
     127     *     Any form of leading/trailing slashes are accepted, but what will be
     128     *     stored is a name, prefixed with a slash, and no trailing slash.
     129     *
     130     * @return $this
     131     */
     132    public function setNamespaceAlias($alias, $fqnn)
     133    {
     134        $this->namespace_aliases[$alias] = '\\' . trim((string)$fqnn, '\\');
     135        return $this;
     136    }
     137   
     138    /**
     139     * Sets a new Local Structural Element Name.
     140     *
     141     * Sets a new Local Structural Element Name. A local name also contains
     142     * punctuation determining the kind of structural element (e.g. trailing "("
     143     * and ")" for functions and methods).
     144     *
     145     * @param string $lsen The new local name of a structural element.
     146     *
     147     * @return $this
     148     */
     149    public function setLSEN($lsen)
     150    {
     151        $this->lsen = (string)$lsen;
     152        return $this;
     153    }
     154}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Description.php

     
     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
     13namespace phpDocumentor\Reflection\DocBlock;
     14
     15use phpDocumentor\Reflection\DocBlock;
     16
     17/**
     18 * Parses a Description of a DocBlock or tag.
     19 *
     20 * @author  Mike van Riel <mike.vanriel@naenius.com>
     21 * @license http://www.opensource.org/licenses/mit-license.php MIT
     22 * @link    http://phpdoc.org
     23 */
     24class Description implements \Reflector
     25{
     26    /** @var string */
     27    protected $contents = '';
     28
     29    /** @var array The contents, as an array of strings and Tag objects. */
     30    protected $parsedContents = null;
     31
     32    /** @var DocBlock The DocBlock which this description belongs to. */
     33    protected $docblock = null;
     34
     35    /**
     36     * Populates the fields of a description.
     37     *
     38     * @param string   $content  The description's conetnts.
     39     * @param DocBlock $docblock The DocBlock which this description belongs to.
     40     */
     41    public function __construct($content, DocBlock $docblock = null)
     42    {
     43        $this->setContent($content)->setDocBlock($docblock);
     44    }
     45
     46    /**
     47     * Gets the text of this description.
     48     *
     49     * @return string
     50     */
     51    public function getContents()
     52    {
     53        return $this->contents;
     54    }
     55
     56    /**
     57     * Sets the text of this description.
     58     *
     59     * @param string $content The new text of this description.
     60     *
     61     * @return $this
     62     */
     63    public function setContent($content)
     64    {
     65        $this->contents = trim($content);
     66
     67        $this->parsedContents = null;
     68        return $this;
     69    }
     70
     71    /**
     72     * Returns the parsed text of this description.
     73     *
     74     * @return array An array of strings and tag objects, in the order they
     75     *     occur within the description.
     76     */
     77    public function getParsedContents()
     78    {
     79        if (null === $this->parsedContents) {
     80            $this->parsedContents = preg_split(
     81                '/\{
     82                    # "{@}" is not a valid inline tag. This ensures that
     83                    # we do not treat it as one, but treat it literally.
     84                    (?!@\})
     85                    # We want to capture the whole tag line, but without the
     86                    # inline tag delimiters.
     87                    (\@
     88                        # Match everything up to the next delimiter.
     89                        [^{}]*
     90                        # Nested inline tag content should not be captured, or
     91                        # it will appear in the result separately.
     92                        (?:
     93                            # Match nested inline tags.
     94                            (?:
     95                                # Because we did not catch the tag delimiters
     96                                # earlier, we must be explicit with them here.
     97                                # Notice that this also matches "{}", as a way
     98                                # to later introduce it as an escape sequence.
     99                                \{(?1)?\}
     100                                |
     101                                # Make sure we match hanging "{".
     102                                \{
     103                            )
     104                            # Match content after the nested inline tag.
     105                            [^{}]*
     106                        )* # If there are more inline tags, match them as well.
     107                           # We use "*" since there may not be any nested inline
     108                           # tags.
     109                    )
     110                \}/Sux',
     111                $this->contents,
     112                null,
     113                PREG_SPLIT_DELIM_CAPTURE
     114            );
     115
     116            $count = count($this->parsedContents);
     117            for ($i=1; $i<$count; $i += 2) {
     118                $this->parsedContents[$i] = Tag::createInstance(
     119                    $this->parsedContents[$i],
     120                    $this->docblock
     121                );
     122            }
     123
     124            //In order to allow "literal" inline tags, the otherwise invalid
     125            //sequence "{@}" is changed to "@", and "{}" is changed to "}".
     126            //See unit tests for examples.
     127            for ($i=0; $i<$count; $i += 2) {
     128                $this->parsedContents[$i] = str_replace(
     129                    array('{@}', '{}'),
     130                    array('@', '}'),
     131                    $this->parsedContents[$i]
     132                );
     133            }
     134        }
     135        return $this->parsedContents;
     136    }
     137
     138    /**
     139     * Return a formatted variant of the Long Description using MarkDown.
     140     *
     141     * @todo this should become a more intelligent piece of code where the
     142     *     configuration contains a setting what format long descriptions are.
     143     *
     144     * @codeCoverageIgnore Will be removed soon, in favor of adapters at
     145     *     PhpDocumentor itself that will process text in various formats.
     146     *
     147     * @return string
     148     */
     149    public function getFormattedContents()
     150    {
     151        $result = $this->contents;
     152
     153        // if the long description contains a plain HTML <code> element, surround
     154        // it with a pre element. Please note that we explicitly used str_replace
     155        // and not preg_replace to gain performance
     156        if (strpos($result, '<code>') !== false) {
     157            $result = str_replace(
     158                array('<code>', "<code>\r\n", "<code>\n", "<code>\r", '</code>'),
     159                array('<pre><code>', '<code>', '<code>', '<code>', '</code></pre>'),
     160                $result
     161            );
     162        }
     163
     164        if (class_exists('Parsedown')) {
     165            $markdown = \Parsedown::instance();
     166            $result = $markdown->parse($result);
     167        } elseif (class_exists('dflydev\markdown\MarkdownExtraParser')) {
     168            $markdown = new \dflydev\markdown\MarkdownExtraParser();
     169            $result = $markdown->transformMarkdown($result);
     170        }
     171
     172        return trim($result);
     173    }
     174
     175    /**
     176     * Gets the docblock this tag belongs to.
     177     *
     178     * @return DocBlock The docblock this description belongs to.
     179     */
     180    public function getDocBlock()
     181    {
     182        return $this->docblock;
     183    }
     184
     185    /**
     186     * Sets the docblock this tag belongs to.
     187     *
     188     * @param DocBlock $docblock The new docblock this description belongs to.
     189     *     Setting NULL removes any association.
     190     *
     191     * @return $this
     192     */
     193    public function setDocBlock(DocBlock $docblock = null)
     194    {
     195        $this->docblock = $docblock;
     196
     197        return $this;
     198    }
     199
     200    /**
     201     * Builds a string representation of this object.
     202     *
     203     * @todo determine the exact format as used by PHP Reflection
     204     *     and implement it.
     205     *
     206     * @return void
     207     * @codeCoverageIgnore Not yet implemented
     208     */
     209    public static function export()
     210    {
     211        throw new \Exception('Not yet implemented');
     212    }
     213
     214    /**
     215     * Returns the long description as a string.
     216     *
     217     * @return string
     218     */
     219    public function __toString()
     220    {
     221        return $this->getContents();
     222    }
     223}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Location.php

     
     1<?php
     2/**
     3 * phpDocumentor
     4 *
     5 * PHP Version 5.3
     6 *
     7 * @author    Vasil Rangelov <boen.robot@gmail.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
     13namespace phpDocumentor\Reflection\DocBlock;
     14
     15/**
     16 * The location a DocBlock occurs within a file.
     17 *
     18 * @author  Vasil Rangelov <boen.robot@gmail.com>
     19 * @license http://www.opensource.org/licenses/mit-license.php MIT
     20 * @link    http://phpdoc.org
     21 */
     22class Location
     23{
     24    /** @var int Line where the DocBlock text starts. */
     25    protected $lineNumber = 0;
     26
     27    /** @var int Column where the DocBlock text starts. */
     28    protected $columnNumber = 0;
     29   
     30    public function __construct(
     31        $lineNumber = 0,
     32        $columnNumber = 0
     33    ) {
     34        $this->setLineNumber($lineNumber)->setColumnNumber($columnNumber);
     35    }
     36
     37    /**
     38     * @return int Line where the DocBlock text starts.
     39     */
     40    public function getLineNumber()
     41    {
     42        return $this->lineNumber;
     43    }
     44
     45    /**
     46     *
     47     * @param type $lineNumber
     48     * @return $this
     49     */
     50    public function setLineNumber($lineNumber)
     51    {
     52        $this->lineNumber = (int)$lineNumber;
     53
     54        return $this;
     55    }
     56
     57    /**
     58     * @return int Column where the DocBlock text starts.
     59     */
     60    public function getColumnNumber()
     61    {
     62        return $this->columnNumber;
     63    }
     64
     65    /**
     66     *
     67     * @param int $columnNumber
     68     * @return $this
     69     */
     70    public function setColumnNumber($columnNumber)
     71    {
     72        $this->columnNumber = (int)$columnNumber;
     73
     74        return $this;
     75    }
     76}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Serializer.php

     
     1<?php
     2/**
     3 * phpDocumentor
     4 *
     5 * PHP Version 5.3
     6 *
     7 * @author    Barry vd. Heuvel <barryvdh@gmail.com>
     8 * @copyright 2013 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
     13namespace phpDocumentor\Reflection\DocBlock;
     14
     15use phpDocumentor\Reflection\DocBlock;
     16
     17/**
     18 * Serializes a DocBlock instance.
     19 *
     20 * @author  Barry vd. Heuvel <barryvdh@gmail.com>
     21 * @license http://www.opensource.org/licenses/mit-license.php MIT
     22 * @link    http://phpdoc.org
     23 */
     24class Serializer
     25{
     26
     27    /** @var string The string to indent the comment with. */
     28    protected $indentString = ' ';
     29
     30    /** @var int The number of times the indent string is repeated. */
     31    protected $indent = 0;
     32
     33    /** @var bool Whether to indent the first line. */
     34    protected $isFirstLineIndented = true;
     35
     36    /** @var int|null The max length of a line. */
     37    protected $lineLength = null;
     38
     39    /**
     40     * Create a Serializer instance.
     41     *
     42     * @param int      $indent          The number of times the indent string is
     43     *     repeated.
     44     * @param string   $indentString    The string to indent the comment with.
     45     * @param bool     $indentFirstLine Whether to indent the first line.
     46     * @param int|null $lineLength      The max length of a line or NULL to
     47     *     disable line wrapping.
     48     */
     49    public function __construct(
     50        $indent = 0,
     51        $indentString = ' ',
     52        $indentFirstLine = true,
     53        $lineLength = null
     54    ) {
     55        $this->setIndentationString($indentString);
     56        $this->setIndent($indent);
     57        $this->setIsFirstLineIndented($indentFirstLine);
     58        $this->setLineLength($lineLength);
     59    }
     60
     61    /**
     62     * Sets the string to indent comments with.
     63     *
     64     * @param string $indentationString The string to indent comments with.
     65     *
     66     * @return $this This serializer object.
     67     */
     68    public function setIndentationString($indentString)
     69    {
     70        $this->indentString = (string)$indentString;
     71        return $this;
     72    }
     73
     74    /**
     75     * Gets the string to indent comments with.
     76     *
     77     * @return string The indent string.
     78     */
     79    public function getIndentationString()
     80    {
     81        return $this->indentString;
     82    }
     83
     84    /**
     85     * Sets the number of indents.
     86     *
     87     * @param int $indent The number of times the indent string is repeated.
     88     *
     89     * @return $this This serializer object.
     90     */
     91    public function setIndent($indent)
     92    {
     93        $this->indent = (int)$indent;
     94        return $this;
     95    }
     96
     97    /**
     98     * Gets the number of indents.
     99     *
     100     * @return int The number of times the indent string is repeated.
     101     */
     102    public function getIndent()
     103    {
     104        return $this->indent;
     105    }
     106
     107    /**
     108     * Sets whether or not the first line should be indented.
     109     *
     110     * Sets whether or not the first line (the one with the "/**") should be
     111     * indented.
     112     *
     113     * @param bool $indentFirstLine The new value for this setting.
     114     *
     115     * @return $this This serializer object.
     116     */
     117    public function setIsFirstLineIndented($indentFirstLine)
     118    {
     119        $this->isFirstLineIndented = (bool)$indentFirstLine;
     120        return $this;
     121    }
     122
     123    /**
     124     * Gets whether or not the first line should be indented.
     125     *
     126     * @return bool Whether or not the first line should be indented.
     127     */
     128    public function isFirstLineIndented()
     129    {
     130        return $this->isFirstLineIndented;
     131    }
     132
     133    /**
     134     * Sets the line length.
     135     *
     136     * Sets the length of each line in the serialization. Content will be
     137     * wrapped within this limit.
     138     *
     139     * @param int|null $lineLength The length of each line. NULL to disable line
     140     *     wrapping altogether.
     141     *
     142     * @return $this This serializer object.
     143     */
     144    public function setLineLength($lineLength)
     145    {
     146        $this->lineLength = null === $lineLength ? null : (int)$lineLength;
     147        return $this;
     148    }
     149
     150    /**
     151     * Gets the line length.
     152     *
     153     * @return int|null The length of each line or NULL if line wrapping is
     154     *     disabled.
     155     */
     156    public function getLineLength()
     157    {
     158        return $this->lineLength;
     159    }
     160
     161    /**
     162     * Generate a DocBlock comment.
     163     *
     164     * @param DocBlock The DocBlock to serialize.
     165     *
     166     * @return string The serialized doc block.
     167     */
     168    public function getDocComment(DocBlock $docblock)
     169    {
     170        $indent = str_repeat($this->indentString, $this->indent);
     171        $firstIndent = $this->isFirstLineIndented ? $indent : '';
     172
     173        $text = $docblock->getText();
     174        if ($this->lineLength) {
     175            //3 === strlen(' * ')
     176            $wrapLength = $this->lineLength - strlen($indent) - 3;
     177            $text = wordwrap($text, $wrapLength);
     178        }
     179        $text = str_replace("\n", "\n{$indent} * ", $text);
     180
     181        $comment = "{$firstIndent}/**\n{$indent} * {$text}\n{$indent} *\n";
     182
     183        /** @var Tag $tag */
     184        foreach ($docblock->getTags() as $tag) {
     185            $tagText = (string) $tag;
     186            if ($this->lineLength) {
     187                $tagText = wordwrap($tagText, $wrapLength);
     188            }
     189            $tagText = str_replace("\n", "\n{$indent} * ", $tagText);
     190
     191            $comment .= "{$indent} * {$tagText}\n";
     192        }
     193
     194        $comment .= $indent . ' */';
     195
     196        return $comment;
     197    }
     198}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Tag/AuthorTag.php

     
     1<?php
     2/**
     3 * phpDocumentor
     4 *
     5 * PHP Version 5.3
     6 *
     7 * @author    Vasil Rangelov <boen.robot@gmail.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
     13namespace phpDocumentor\Reflection\DocBlock\Tag;
     14
     15use phpDocumentor\Reflection\DocBlock\Tag;
     16
     17/**
     18 * Reflection class for an @author tag in a Docblock.
     19 *
     20 * @author  Mike van Riel <mike.vanriel@naenius.com>
     21 * @license http://www.opensource.org/licenses/mit-license.php MIT
     22 * @link    http://phpdoc.org
     23 */
     24class AuthorTag extends Tag
     25{
     26    /**
     27     * PCRE regular expression matching any valid value for the name component.
     28     */
     29    const REGEX_AUTHOR_NAME = '[^\<]*';
     30
     31    /**
     32     * PCRE regular expression matching any valid value for the email component.
     33     */
     34    const REGEX_AUTHOR_EMAIL = '[^\>]*';
     35
     36    /** @var string The name of the author */
     37    protected $authorName = '';
     38
     39    /** @var string The email of the author */
     40    protected $authorEmail = '';
     41   
     42    public function getContent()
     43    {
     44        if (null === $this->content) {
     45            $this->content = $this->authorName;
     46            if ('' != $this->authorEmail) {
     47                $this->content .= "<{$this->authorEmail}>";
     48            }
     49        }
     50
     51        return $this->content;
     52    }
     53
     54    /**
     55     * {@inheritdoc}
     56     */
     57    public function setContent($content)
     58    {
     59        parent::setContent($content);
     60        if (preg_match(
     61            '/^(' . self::REGEX_AUTHOR_NAME .
     62            ')(\<(' . self::REGEX_AUTHOR_EMAIL .
     63            ')\>)?$/u',
     64            $this->description,
     65            $matches
     66        )) {
     67            $this->authorName = trim($matches[1]);
     68            if (isset($matches[3])) {
     69                $this->authorEmail = trim($matches[3]);
     70            }
     71        }
     72
     73        return $this;
     74    }
     75
     76    /**
     77     * Gets the author's name.
     78     *
     79     * @return string The author's name.
     80     */
     81    public function getAuthorName()
     82    {
     83        return $this->authorName;
     84    }
     85   
     86    /**
     87     * Sets the author's name.
     88     *
     89     * @param string $authorName The new author name.
     90     *     An invalid value will set an empty string.
     91     *
     92     * @return $this
     93     */
     94    public function setAuthorName($authorName)
     95    {
     96        $this->content = null;
     97        $this->authorName
     98            = preg_match('/^' . self::REGEX_AUTHOR_NAME . '$/u', $authorName)
     99            ? $authorName : '';
     100
     101        return $this;
     102    }
     103
     104    /**
     105     * Gets the author's email.
     106     *
     107     * @return string The author's email.
     108     */
     109    public function getAuthorEmail()
     110    {
     111        return $this->authorEmail;
     112    }
     113   
     114    /**
     115     * Sets the author's email.
     116     *
     117     * @param string $authorEmail The new author email.
     118     *     An invalid value will set an empty string.
     119     *
     120     * @return $this
     121     */
     122    public function setAuthorEmail($authorEmail)
     123    {
     124        $this->authorEmail
     125            = preg_match('/^' . self::REGEX_AUTHOR_EMAIL . '$/u', $authorEmail)
     126            ? $authorEmail : '';
     127
     128        $this->content = null;
     129        return $this;
     130    }
     131}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Tag/CoversTag.php

     
     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
     13namespace phpDocumentor\Reflection\DocBlock\Tag;
     14
     15/**
     16 * Reflection class for a @covers tag in a Docblock.
     17 *
     18 * @author  Mike van Riel <mike.vanriel@naenius.com>
     19 * @license http://www.opensource.org/licenses/mit-license.php MIT
     20 * @link    http://phpdoc.org
     21 */
     22class CoversTag extends SeeTag
     23{
     24}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Tag/DeprecatedTag.php

     
     1<?php
     2/**
     3 * phpDocumentor
     4 *
     5 * PHP Version 5.3
     6 *
     7 * @author    Vasil Rangelov <boen.robot@gmail.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
     13namespace phpDocumentor\Reflection\DocBlock\Tag;
     14
     15use phpDocumentor\Reflection\DocBlock\Tag\VersionTag;
     16
     17/**
     18 * Reflection class for a @deprecated tag in a Docblock.
     19 *
     20 * @author  Vasil Rangelov <boen.robot@gmail.com>
     21 * @license http://www.opensource.org/licenses/mit-license.php MIT
     22 * @link    http://phpdoc.org
     23 */
     24class DeprecatedTag extends VersionTag
     25{
     26}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Tag/ExampleTag.php

     
     1<?php
     2/**
     3 * phpDocumentor
     4 *
     5 * PHP Version 5.3
     6 *
     7 * @author    Vasil Rangelov <boen.robot@gmail.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
     13namespace phpDocumentor\Reflection\DocBlock\Tag;
     14
     15use phpDocumentor\Reflection\DocBlock\Tag;
     16
     17/**
     18 * Reflection class for a @example tag in a Docblock.
     19 *
     20 * @author  Vasil Rangelov <boen.robot@gmail.com>
     21 * @license http://www.opensource.org/licenses/mit-license.php MIT
     22 * @link    http://phpdoc.org
     23 */
     24class ExampleTag extends SourceTag
     25{
     26    /**
     27     * @var string Path to a file to use as an example.
     28     *     May also be an absolute URI.
     29     */
     30    protected $filePath = '';
     31
     32    /**
     33     * @var bool Whether the file path component represents an URI.
     34     *     This determines how the file portion appears at {@link getContent()}.
     35     */
     36    protected $isURI = false;
     37
     38    /**
     39     * {@inheritdoc}
     40     */
     41    public function getContent()
     42    {
     43        if (null === $this->content) {
     44            $filePath = '';
     45            if ($this->isURI) {
     46                if (false === strpos($this->filePath, ':')) {
     47                    $filePath = str_replace(
     48                        '%2F',
     49                        '/',
     50                        rawurlencode($this->filePath)
     51                    );
     52                } else {
     53                    $filePath = $this->filePath;
     54                }
     55            } else {
     56                $filePath = '"' . $this->filePath . '"';
     57            }
     58
     59            $this->content = $filePath . ' ' . parent::getContent();
     60        }
     61
     62        return $this->content;
     63    }
     64    /**
     65     * {@inheritdoc}
     66     */
     67    public function setContent($content)
     68    {
     69        Tag::setContent($content);
     70        if (preg_match(
     71            '/^
     72                # File component
     73                (?:
     74                    # File path in quotes
     75                    \"([^\"]+)\"
     76                    |
     77                    # File URI
     78                    (\S+)
     79                )
     80                # Remaining content (parsed by SourceTag)
     81                (?:\s+(.*))?
     82            $/sux',
     83            $this->description,
     84            $matches
     85        )) {
     86            if ('' !== $matches[1]) {
     87                $this->setFilePath($matches[1]);
     88            } else {
     89                $this->setFileURI($matches[2]);
     90            }
     91
     92            if (isset($matches[3])) {
     93                parent::setContent($matches[3]);
     94            } else {
     95                $this->setDescription('');
     96            }
     97            $this->content = $content;
     98        }
     99
     100        return $this;
     101    }
     102
     103    /**
     104     * Returns the file path.
     105     *
     106     * @return string Path to a file to use as an example.
     107     *     May also be an absolute URI.
     108     */
     109    public function getFilePath()
     110    {
     111        return $this->filePath;
     112    }
     113   
     114    /**
     115     * Sets the file path.
     116     *
     117     * @param string $filePath The new file path to use for the example.
     118     *
     119     * @return $this
     120     */
     121    public function setFilePath($filePath)
     122    {
     123        $this->isURI = false;
     124        $this->filePath = trim($filePath);
     125
     126        $this->content = null;
     127        return $this;
     128    }
     129   
     130    /**
     131     * Sets the file path as an URI.
     132     *
     133     * This function is equivalent to {@link setFilePath()}, except that it
     134     * convers an URI to a file path before that.
     135     *
     136     * There is no getFileURI(), as {@link getFilePath()} is compatible.
     137     *
     138     * @param type $uri The new file URI to use as an example.
     139     */
     140    public function setFileURI($uri)
     141    {
     142        $this->isURI = true;
     143        if (false === strpos($uri, ':')) {
     144            //Relative URL
     145            $this->filePath = rawurldecode(
     146                str_replace(array('/', '\\'), '%2F', $uri)
     147            );
     148        } else {
     149            //Absolute URL or URI.
     150            $this->filePath = $uri;
     151        }
     152
     153        $this->content = null;
     154        return $this;
     155    }
     156}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Tag/LinkTag.php

     
     1<?php
     2/**
     3 * phpDocumentor
     4 *
     5 * PHP Version 5.3
     6 *
     7 * @author    Ben Selby <benmatselby@gmail.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
     13namespace phpDocumentor\Reflection\DocBlock\Tag;
     14
     15use phpDocumentor\Reflection\DocBlock\Tag;
     16
     17/**
     18 * Reflection class for a @link tag in a Docblock.
     19 *
     20 * @author  Ben Selby <benmatselby@gmail.com>
     21 * @license http://www.opensource.org/licenses/mit-license.php MIT
     22 * @link    http://phpdoc.org
     23 */
     24class LinkTag extends Tag
     25{
     26    /** @var string */
     27    protected $link = '';
     28
     29    /**
     30     * {@inheritdoc}
     31     */
     32    public function getContent()
     33    {
     34        if (null === $this->content) {
     35            $this->content = "{$this->link} {$this->description}";
     36        }
     37
     38        return $this->content;
     39    }
     40
     41    /**
     42     * {@inheritdoc}
     43     */
     44    public function setContent($content)
     45    {
     46        parent::setContent($content);
     47        $parts = preg_split('/\s+/Su', $this->description, 2);
     48
     49        $this->link = $parts[0];
     50
     51        $this->setDescription(isset($parts[1]) ? $parts[1] : $parts[0]);
     52
     53        $this->content = $content;
     54        return $this;
     55    }
     56
     57    /**
     58    * Gets the link
     59    *
     60    * @return string
     61    */
     62    public function getLink()
     63    {
     64        return $this->link;
     65    }
     66
     67    /**
     68    * Sets the link
     69    *
     70    * @param string $link The link
     71    *
     72    * @return $this
     73    */
     74    public function setLink($link)
     75    {
     76        $this->link = $link;
     77
     78        $this->content = null;
     79        return $this;
     80    }
     81}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Tag/MethodTag.php

     
     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
     13namespace phpDocumentor\Reflection\DocBlock\Tag;
     14
     15use phpDocumentor\Reflection\DocBlock\Tag;
     16
     17/**
     18 * Reflection class for a @method in a Docblock.
     19 *
     20 * @author  Mike van Riel <mike.vanriel@naenius.com>
     21 * @license http://www.opensource.org/licenses/mit-license.php MIT
     22 * @link    http://phpdoc.org
     23 */
     24class MethodTag extends ReturnTag
     25{
     26
     27    /** @var string */
     28    protected $method_name = '';
     29
     30    /** @var string */
     31    protected $arguments = '';
     32   
     33    /** @var bool */
     34    protected $isStatic = false;
     35
     36    /**
     37     * {@inheritdoc}
     38     */
     39    public function getContent()
     40    {
     41        if (null === $this->content) {
     42            $this->content = '';
     43            if ($this->isStatic) {
     44                $this->content .= 'static ';
     45            }
     46            $this->content .= $this->type .
     47                " {$this->method_name}({$this->arguments}) " .
     48                $this->description;
     49        }
     50
     51        return $this->content;
     52    }
     53
     54    /**
     55     * {@inheritdoc}
     56     */
     57    public function setContent($content)
     58    {
     59        Tag::setContent($content);
     60        // 1. none or more whitespace
     61        // 2. optionally the keyword "static" followed by whitespace
     62        // 3. optionally a word with underscores followed by whitespace : as
     63        //    type for the return value
     64        // 4. then optionally a word with underscores followed by () and
     65        //    whitespace : as method name as used by phpDocumentor
     66        // 5. then a word with underscores, followed by ( and any character
     67        //    until a ) and whitespace : as method name with signature
     68        // 6. any remaining text : as description
     69        if (preg_match(
     70            '/^
     71                # Static keyword
     72                # Declates a static method ONLY if type is also present
     73                (?:
     74                    (static)
     75                    \s+
     76                )?
     77                # Return type
     78                (?:
     79                    ([\w\|_\\\\]+)
     80                    \s+
     81                )?
     82                # Legacy method name (not captured)
     83                (?:
     84                    [\w_]+\(\)\s+
     85                )?
     86                # Method name
     87                ([\w\|_\\\\]+)
     88                # Arguments
     89                \(([^\)]*)\)
     90                \s*
     91                # Description
     92                (.*)
     93            $/sux',
     94            $this->description,
     95            $matches
     96        )) {
     97            list(
     98                ,
     99                $static,
     100                $this->type,
     101                $this->method_name,
     102                $this->arguments,
     103                $this->description
     104            ) = $matches;
     105            if ($static) {
     106                if (!$this->type) {
     107                    $this->type = 'static';
     108                } else {
     109                    $this->isStatic = true;
     110                }
     111            } else {
     112                if (!$this->type) {
     113                    $this->type = 'void';
     114                }
     115            }
     116            $this->parsedDescription = null;
     117        }
     118
     119        return $this;
     120    }
     121
     122    /**
     123     * Sets the name of this method.
     124     *
     125     * @param string $method_name The name of the method.
     126     *
     127     * @return $this
     128     */
     129    public function setMethodName($method_name)
     130    {
     131        $this->method_name = $method_name;
     132
     133        $this->content = null;
     134        return $this;
     135    }
     136
     137    /**
     138     * Retrieves the method name.
     139     *
     140     * @return string
     141     */
     142    public function getMethodName()
     143    {
     144        return $this->method_name;
     145    }
     146
     147    /**
     148     * Sets the arguments for this method.
     149     *
     150     * @param string $arguments A comma-separated arguments line.
     151     *
     152     * @return void
     153     */
     154    public function setArguments($arguments)
     155    {
     156        $this->arguments = $arguments;
     157
     158        $this->content = null;
     159        return $this;
     160    }
     161
     162    /**
     163     * Returns an array containing each argument as array of type and name.
     164     *
     165     * Please note that the argument sub-array may only contain 1 element if no
     166     * type was specified.
     167     *
     168     * @return string[]
     169     */
     170    public function getArguments()
     171    {
     172        if (empty($this->arguments)) {
     173            return array();
     174        }
     175
     176        $arguments = explode(',', $this->arguments);
     177        foreach ($arguments as $key => $value) {
     178            $arguments[$key] = explode(' ', trim($value));
     179        }
     180
     181        return $arguments;
     182    }
     183   
     184    /**
     185     * Checks whether the method tag describes a static method or not.
     186     *
     187     * @return bool TRUE if the method declaration is for a static method, FALSE
     188     *     otherwise.
     189     */
     190    public function isStatic()
     191    {
     192        return $this->isStatic;
     193    }
     194   
     195    /**
     196     * Sets a new value for whether the method is static or not.
     197     *
     198     * @param bool $isStatic The new value to set.
     199     *
     200     * @return $this
     201     */
     202    public function setIsStatic($isStatic)
     203    {
     204        $this->isStatic = $isStatic;
     205
     206        $this->content = null;
     207        return $this;
     208    }
     209}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Tag/ParamTag.php

     
     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
     13namespace phpDocumentor\Reflection\DocBlock\Tag;
     14
     15use phpDocumentor\Reflection\DocBlock\Tag;
     16
     17/**
     18 * Reflection class for a @param tag in a Docblock.
     19 *
     20 * @author  Mike van Riel <mike.vanriel@naenius.com>
     21 * @license http://www.opensource.org/licenses/mit-license.php MIT
     22 * @link    http://phpdoc.org
     23 */
     24class ParamTag extends ReturnTag
     25{
     26    /** @var string */
     27    protected $variableName = '';
     28
     29    /** @var bool determines whether this is a variadic argument */
     30    protected $isVariadic = false;
     31
     32    /**
     33     * {@inheritdoc}
     34     */
     35    public function getContent()
     36    {
     37        if (null === $this->content) {
     38            $this->content
     39                = "{$this->type} {$this->variableName} {$this->description}";
     40        }
     41        return $this->content;
     42    }
     43    /**
     44     * {@inheritdoc}
     45     */
     46    public function setContent($content)
     47    {
     48        Tag::setContent($content);
     49        $parts = preg_split(
     50            '/(\s+)/Su',
     51            $this->description,
     52            3,
     53            PREG_SPLIT_DELIM_CAPTURE
     54        );
     55
     56        // if the first item that is encountered is not a variable; it is a type
     57        if (isset($parts[0])
     58            && (strlen($parts[0]) > 0)
     59            && ($parts[0][0] !== '$')
     60        ) {
     61            $this->type = array_shift($parts);
     62            array_shift($parts);
     63        }
     64
     65        // if the next item starts with a $ or ...$ it must be the variable name
     66        if (isset($parts[0])
     67            && (strlen($parts[0]) > 0)
     68            && ($parts[0][0] == '$' || substr($parts[0], 0, 4) === '...$')
     69        ) {
     70            $this->variableName = array_shift($parts);
     71            array_shift($parts);
     72
     73            if (substr($this->variableName, 0, 3) === '...') {
     74                $this->isVariadic = true;
     75                $this->variableName = substr($this->variableName, 3);
     76            }
     77        }
     78
     79        $this->setDescription(implode('', $parts));
     80
     81        $this->content = $content;
     82        return $this;
     83    }
     84
     85    /**
     86     * Returns the variable's name.
     87     *
     88     * @return string
     89     */
     90    public function getVariableName()
     91    {
     92        return $this->variableName;
     93    }
     94
     95    /**
     96     * Sets the variable's name.
     97     *
     98     * @param string $name The new name for this variable.
     99     *
     100     * @return $this
     101     */
     102    public function setVariableName($name)
     103    {
     104        $this->variableName = $name;
     105
     106        $this->content = null;
     107        return $this;
     108    }
     109
     110    /**
     111     * Returns whether this tag is variadic.
     112     *
     113     * @return boolean
     114     */
     115    public function isVariadic()
     116    {
     117        return $this->isVariadic;
     118    }
     119}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Tag/PropertyReadTag.php

     
     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
     13namespace phpDocumentor\Reflection\DocBlock\Tag;
     14
     15/**
     16 * Reflection class for a @property-read tag in a Docblock.
     17 *
     18 * @author  Mike van Riel <mike.vanriel@naenius.com>
     19 * @license http://www.opensource.org/licenses/mit-license.php MIT
     20 * @link    http://phpdoc.org
     21 */
     22class PropertyReadTag extends PropertyTag
     23{
     24}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Tag/PropertyTag.php

     
     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
     13namespace phpDocumentor\Reflection\DocBlock\Tag;
     14
     15/**
     16 * Reflection class for a @property tag in a Docblock.
     17 *
     18 * @author  Mike van Riel <mike.vanriel@naenius.com>
     19 * @license http://www.opensource.org/licenses/mit-license.php MIT
     20 * @link    http://phpdoc.org
     21 */
     22class PropertyTag extends ParamTag
     23{
     24}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Tag/PropertyWriteTag.php

     
     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
     13namespace phpDocumentor\Reflection\DocBlock\Tag;
     14
     15/**
     16 * Reflection class for a @property-write tag in a Docblock.
     17 *
     18 * @author  Mike van Riel <mike.vanriel@naenius.com>
     19 * @license http://www.opensource.org/licenses/mit-license.php MIT
     20 * @link    http://phpdoc.org
     21 */
     22class PropertyWriteTag extends PropertyTag
     23{
     24}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Tag/ReturnTag.php

     
     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
     13namespace phpDocumentor\Reflection\DocBlock\Tag;
     14
     15use phpDocumentor\Reflection\DocBlock\Tag;
     16use phpDocumentor\Reflection\DocBlock\Type\Collection;
     17
     18/**
     19 * Reflection class for a @return tag in a Docblock.
     20 *
     21 * @author  Mike van Riel <mike.vanriel@naenius.com>
     22 * @license http://www.opensource.org/licenses/mit-license.php MIT
     23 * @link    http://phpdoc.org
     24 */
     25class ReturnTag extends Tag
     26{
     27    /** @var string The raw type component. */
     28    protected $type = '';
     29   
     30    /** @var Collection The parsed type component. */
     31    protected $types = null;
     32
     33    /**
     34     * {@inheritdoc}
     35     */
     36    public function getContent()
     37    {
     38        if (null === $this->content) {
     39            $this->content = "{$this->type} {$this->description}";
     40        }
     41
     42        return $this->content;
     43    }
     44
     45    /**
     46     * {@inheritdoc}
     47     */
     48    public function setContent($content)
     49    {
     50        parent::setContent($content);
     51
     52        $parts = preg_split('/\s+/Su', $this->description, 2);
     53
     54        // any output is considered a type
     55        $this->type = $parts[0];
     56        $this->types = null;
     57
     58        $this->setDescription(isset($parts[1]) ? $parts[1] : '');
     59
     60        $this->content = $content;
     61        return $this;
     62    }
     63
     64    /**
     65     * Returns the unique types of the variable.
     66     *
     67     * @return string[]
     68     */
     69    public function getTypes()
     70    {
     71        return $this->getTypesCollection()->getArrayCopy();
     72    }
     73
     74    /**
     75     * Returns the type section of the variable.
     76     *
     77     * @return string
     78     */
     79    public function getType()
     80    {
     81        return (string) $this->getTypesCollection();
     82    }
     83
     84    /**
     85     * Returns the type collection.
     86     *
     87     * @return void
     88     */
     89    protected function getTypesCollection()
     90    {
     91        if (null === $this->types) {
     92            $this->types = new Collection(
     93                array($this->type),
     94                $this->docblock ? $this->docblock->getContext() : null
     95            );
     96        }
     97        return $this->types;
     98    }
     99}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Tag/SeeTag.php

     
     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
     13namespace phpDocumentor\Reflection\DocBlock\Tag;
     14
     15use phpDocumentor\Reflection\DocBlock\Tag;
     16
     17/**
     18 * Reflection class for a @see tag in a Docblock.
     19 *
     20 * @author  Mike van Riel <mike.vanriel@naenius.com>
     21 * @license http://www.opensource.org/licenses/mit-license.php MIT
     22 * @link    http://phpdoc.org
     23 */
     24class SeeTag extends Tag
     25{
     26    /** @var string */
     27    protected $refers = null;
     28
     29    /**
     30     * {@inheritdoc}
     31     */
     32    public function getContent()
     33    {
     34        if (null === $this->content) {
     35            $this->content = "{$this->refers} {$this->description}";
     36        }
     37        return $this->content;
     38    }
     39
     40    /**
     41     * {@inheritdoc}
     42     */
     43    public function setContent($content)
     44    {
     45        parent::setContent($content);
     46        $parts = preg_split('/\s+/Su', $this->description, 2);
     47
     48        // any output is considered a type
     49        $this->refers = $parts[0];
     50
     51        $this->setDescription(isset($parts[1]) ? $parts[1] : '');
     52
     53        $this->content = $content;
     54        return $this;
     55    }
     56
     57    /**
     58     * Gets the structural element this tag refers to.
     59     *
     60     * @return string
     61     */
     62    public function getReference()
     63    {
     64        return $this->refers;
     65    }
     66
     67    /**
     68     * Sets the structural element this tag refers to.
     69     *
     70     * @param string $refers The new type this tag refers to.
     71     *
     72     * @return $this
     73     */
     74    public function setReference($refers)
     75    {
     76        $this->refers = $refers;
     77
     78        $this->content = null;
     79        return $this;
     80    }
     81}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Tag/SinceTag.php

     
     1<?php
     2/**
     3 * phpDocumentor
     4 *
     5 * PHP Version 5.3
     6 *
     7 * @author    Vasil Rangelov <boen.robot@gmail.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
     13namespace phpDocumentor\Reflection\DocBlock\Tag;
     14
     15use phpDocumentor\Reflection\DocBlock\Tag\VersionTag;
     16
     17/**
     18 * Reflection class for a @since tag in a Docblock.
     19 *
     20 * @author  Vasil Rangelov <boen.robot@gmail.com>
     21 * @license http://www.opensource.org/licenses/mit-license.php MIT
     22 * @link    http://phpdoc.org
     23 */
     24class SinceTag extends VersionTag
     25{
     26}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Tag/SourceTag.php

     
     1<?php
     2/**
     3 * phpDocumentor
     4 *
     5 * PHP Version 5.3
     6 *
     7 * @author    Vasil Rangelov <boen.robot@gmail.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
     13namespace phpDocumentor\Reflection\DocBlock\Tag;
     14
     15use phpDocumentor\Reflection\DocBlock\Tag;
     16
     17/**
     18 * Reflection class for a @source tag in a Docblock.
     19 *
     20 * @author  Vasil Rangelov <boen.robot@gmail.com>
     21 * @license http://www.opensource.org/licenses/mit-license.php MIT
     22 * @link    http://phpdoc.org
     23 */
     24class SourceTag extends Tag
     25{
     26    /**
     27     * @var int The starting line, relative to the structural element's
     28     *     location.
     29     */
     30    protected $startingLine = 1;
     31
     32    /**
     33     * @var int|null The number of lines, relative to the starting line. NULL
     34     *     means "to the end".
     35     */
     36    protected $lineCount = null;
     37
     38    /**
     39     * {@inheritdoc}
     40     */
     41    public function getContent()
     42    {
     43        if (null === $this->content) {
     44            $this->content
     45                = "{$this->startingLine} {$this->lineCount} {$this->description}";
     46        }
     47
     48        return $this->content;
     49    }
     50
     51    /**
     52     * {@inheritdoc}
     53     */
     54    public function setContent($content)
     55    {
     56        parent::setContent($content);
     57        if (preg_match(
     58            '/^
     59                # Starting line
     60                ([1-9]\d*)
     61                \s*
     62                # Number of lines
     63                (?:
     64                    ((?1))
     65                    \s+
     66                )?
     67                # Description
     68                (.*)
     69            $/sux',
     70            $this->description,
     71            $matches
     72        )) {
     73            $this->startingLine = (int)$matches[1];
     74            if (isset($matches[2]) && '' !== $matches[2]) {
     75                $this->lineCount = (int)$matches[2];
     76            }
     77            $this->setDescription($matches[3]);
     78            $this->content = $content;
     79        }
     80
     81        return $this;
     82    }
     83
     84    /**
     85     * Gets the starting line.
     86     *
     87     * @return int The starting line, relative to the structural element's
     88     *     location.
     89     */
     90    public function getStartingLine()
     91    {
     92        return $this->startingLine;
     93    }
     94
     95    /**
     96     * Sets the starting line.
     97     *
     98     * @param int $startingLine The new starting line, relative to the
     99     *     structural element's location.
     100     *
     101     * @return $this
     102     */
     103    public function setStartingLine($startingLine)
     104    {
     105        $this->startingLine = $startingLine;
     106
     107        $this->content = null;
     108        return $this;
     109    }
     110
     111    /**
     112     * Returns the number of lines.
     113     *
     114     * @return int|null The number of lines, relative to the starting line. NULL
     115     *     means "to the end".
     116     */
     117    public function getLineCount()
     118    {
     119        return $this->lineCount;
     120    }
     121
     122    /**
     123     * Sets the number of lines.
     124     *
     125     * @param int|null $lineCount The new number of lines, relative to the
     126     *     starting line. NULL means "to the end".
     127     *
     128     * @return $this
     129     */
     130    public function setLineCount($lineCount)
     131    {
     132        $this->lineCount = $lineCount;
     133
     134        $this->content = null;
     135        return $this;
     136    }
     137}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Tag/ThrowsTag.php

     
     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
     13namespace phpDocumentor\Reflection\DocBlock\Tag;
     14
     15/**
     16 * Reflection class for a @throws tag in a Docblock.
     17 *
     18 * @author  Mike van Riel <mike.vanriel@naenius.com>
     19 * @license http://www.opensource.org/licenses/mit-license.php MIT
     20 * @link    http://phpdoc.org
     21 */
     22class ThrowsTag extends ReturnTag
     23{
     24}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Tag/UsesTag.php

     
     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
     13namespace phpDocumentor\Reflection\DocBlock\Tag;
     14
     15/**
     16 * Reflection class for a @uses tag in a Docblock.
     17 *
     18 * @author  Mike van Riel <mike.vanriel@naenius.com>
     19 * @license http://www.opensource.org/licenses/mit-license.php MIT
     20 * @link    http://phpdoc.org
     21 */
     22class UsesTag extends SeeTag
     23{
     24}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Tag/VarTag.php

     
     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
     13namespace phpDocumentor\Reflection\DocBlock\Tag;
     14
     15/**
     16 * Reflection class for a @var tag in a Docblock.
     17 *
     18 * @author  Mike van Riel <mike.vanriel@naenius.com>
     19 * @license http://www.opensource.org/licenses/mit-license.php MIT
     20 * @link    http://phpdoc.org
     21 */
     22class VarTag extends ParamTag
     23{
     24}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Tag/VersionTag.php

     
     1<?php
     2/**
     3 * phpDocumentor
     4 *
     5 * PHP Version 5.3
     6 *
     7 * @author    Vasil Rangelov <boen.robot@gmail.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
     13namespace phpDocumentor\Reflection\DocBlock\Tag;
     14
     15use phpDocumentor\Reflection\DocBlock\Tag;
     16
     17/**
     18 * Reflection class for a @version tag in a Docblock.
     19 *
     20 * @author  Vasil Rangelov <boen.robot@gmail.com>
     21 * @license http://www.opensource.org/licenses/mit-license.php MIT
     22 * @link    http://phpdoc.org
     23 */
     24class VersionTag extends Tag
     25{
     26    /**
     27     * PCRE regular expression matching a version vector.
     28     * Assumes the "x" modifier.
     29     */
     30    const REGEX_VECTOR = '(?:
     31        # Normal release vectors.
     32        \d\S*
     33        |
     34        # VCS version vectors. Per PHPCS, they are expected to
     35        # follow the form of the VCS name, followed by ":", followed
     36        # by the version vector itself.
     37        # By convention, popular VCSes like CVS, SVN and GIT use "$"
     38        # around the actual version vector.
     39        [^\s\:]+\:\s*\$[^\$]+\$
     40    )';
     41
     42    /** @var string The version vector. */
     43    protected $version = '';
     44   
     45    public function getContent()
     46    {
     47        if (null === $this->content) {
     48            $this->content = "{$this->version} {$this->description}";
     49        }
     50
     51        return $this->content;
     52    }
     53
     54    /**
     55     * {@inheritdoc}
     56     */
     57    public function setContent($content)
     58    {
     59        parent::setContent($content);
     60
     61        if (preg_match(
     62            '/^
     63                # The version vector
     64                (' . self::REGEX_VECTOR . ')
     65                \s*
     66                # The description
     67                (.+)?
     68            $/sux',
     69            $this->description,
     70            $matches
     71        )) {
     72            $this->version = $matches[1];
     73            $this->setDescription(isset($matches[2]) ? $matches[2] : '');
     74            $this->content = $content;
     75        }
     76
     77        return $this;
     78    }
     79
     80    /**
     81     * Gets the version section of the tag.
     82     *
     83     * @return string The version section of the tag.
     84     */
     85    public function getVersion()
     86    {
     87        return $this->version;
     88    }
     89   
     90    /**
     91     * Sets the version section of the tag.
     92     *
     93     * @param string $version The new version section of the tag.
     94     *     An invalid value will set an empty string.
     95     *
     96     * @return $this
     97     */
     98    public function setVersion($version)
     99    {
     100        $this->version
     101            = preg_match('/^' . self::REGEX_VECTOR . '$/ux', $version)
     102            ? $version
     103            : '';
     104
     105        $this->content = null;
     106        return $this;
     107    }
     108}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Tag.php

     
     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
     13namespace phpDocumentor\Reflection\DocBlock;
     14
     15use phpDocumentor\Reflection\DocBlock;
     16
     17/**
     18 * Parses a tag definition for a DocBlock.
     19 *
     20 * @author  Mike van Riel <mike.vanriel@naenius.com>
     21 * @license http://www.opensource.org/licenses/mit-license.php MIT
     22 * @link    http://phpdoc.org
     23 */
     24class Tag implements \Reflector
     25{
     26    /**
     27     * PCRE regular expression matching a tag name.
     28     */
     29    const REGEX_TAGNAME = '[\w\-\_\\\\]+';
     30
     31    /** @var string Name of the tag */
     32    protected $tag = '';
     33
     34    /**
     35     * @var string|null Content of the tag.
     36     *     When set to NULL, it means it needs to be regenerated.
     37     */
     38    protected $content = '';
     39
     40    /** @var string Description of the content of this tag */
     41    protected $description = '';
     42
     43    /**
     44     * @var array|null The description, as an array of strings and Tag objects.
     45     *     When set to NULL, it means it needs to be regenerated.
     46     */
     47    protected $parsedDescription = null;
     48
     49    /** @var Location Location of the tag. */
     50    protected $location = null;
     51
     52    /** @var DocBlock The DocBlock which this tag belongs to. */
     53    protected $docblock = null;
     54   
     55    /**
     56     * @var array An array with a tag as a key, and an FQCN to a class that
     57     *     handles it as an array value. The class is expected to inherit this
     58     *     class.
     59     */
     60    private static $tagHandlerMappings = array(
     61        'author'
     62            => '\phpDocumentor\Reflection\DocBlock\Tag\AuthorTag',
     63        'covers'
     64            => '\phpDocumentor\Reflection\DocBlock\Tag\CoversTag',
     65        'deprecated'
     66            => '\phpDocumentor\Reflection\DocBlock\Tag\DeprecatedTag',
     67        'example'
     68            => '\phpDocumentor\Reflection\DocBlock\Tag\ExampleTag',
     69        'link'
     70            => '\phpDocumentor\Reflection\DocBlock\Tag\LinkTag',
     71        'method'
     72            => '\phpDocumentor\Reflection\DocBlock\Tag\MethodTag',
     73        'param'
     74            => '\phpDocumentor\Reflection\DocBlock\Tag\ParamTag',
     75        'property-read'
     76            => '\phpDocumentor\Reflection\DocBlock\Tag\PropertyReadTag',
     77        'property'
     78            => '\phpDocumentor\Reflection\DocBlock\Tag\PropertyTag',
     79        'property-write'
     80            => '\phpDocumentor\Reflection\DocBlock\Tag\PropertyWriteTag',
     81        'return'
     82            => '\phpDocumentor\Reflection\DocBlock\Tag\ReturnTag',
     83        'see'
     84            => '\phpDocumentor\Reflection\DocBlock\Tag\SeeTag',
     85        'since'
     86            => '\phpDocumentor\Reflection\DocBlock\Tag\SinceTag',
     87        'source'
     88            => '\phpDocumentor\Reflection\DocBlock\Tag\SourceTag',
     89        'throw'
     90            => '\phpDocumentor\Reflection\DocBlock\Tag\ThrowsTag',
     91        'throws'
     92            => '\phpDocumentor\Reflection\DocBlock\Tag\ThrowsTag',
     93        'uses'
     94            => '\phpDocumentor\Reflection\DocBlock\Tag\UsesTag',
     95        'var'
     96            => '\phpDocumentor\Reflection\DocBlock\Tag\VarTag',
     97        'version'
     98            => '\phpDocumentor\Reflection\DocBlock\Tag\VersionTag'
     99    );
     100
     101    /**
     102     * Factory method responsible for instantiating the correct sub type.
     103     *
     104     * @param string   $tag_line The text for this tag, including description.
     105     * @param DocBlock $docblock The DocBlock which this tag belongs to.
     106     * @param Location $location Location of the tag.
     107     *
     108     * @throws \InvalidArgumentException if an invalid tag line was presented.
     109     *
     110     * @return static A new tag object.
     111     */
     112    final public static function createInstance(
     113        $tag_line,
     114        DocBlock $docblock = null,
     115        Location $location = null
     116    ) {
     117        if (!preg_match(
     118            '/^@(' . self::REGEX_TAGNAME . ')(?:\s*([^\s].*)|$)?/us',
     119            $tag_line,
     120            $matches
     121        )) {
     122            throw new \InvalidArgumentException(
     123                'Invalid tag_line detected: ' . $tag_line
     124            );
     125        }
     126
     127        $handler = __CLASS__;
     128        if (isset(self::$tagHandlerMappings[$matches[1]])) {
     129            $handler = self::$tagHandlerMappings[$matches[1]];
     130        } elseif (isset($docblock)) {
     131            $tagName = (string)new Type\Collection(
     132                array($matches[1]),
     133                $docblock->getContext()
     134            );
     135
     136            if (isset(self::$tagHandlerMappings[$tagName])) {
     137                $handler = self::$tagHandlerMappings[$tagName];
     138            }
     139        }
     140
     141        return new $handler(
     142            $matches[1],
     143            isset($matches[2]) ? $matches[2] : '',
     144            $docblock,
     145            $location
     146        );
     147    }
     148
     149    /**
     150     * Registers a handler for tags.
     151     *
     152     * Registers a handler for tags. The class specified is autoloaded if it's
     153     * not available. It must inherit from this class.
     154     *
     155     * @param string      $tag     Name of tag to regiser a handler for. When
     156     *     registering a namespaced tag, the full name, along with a prefixing
     157     *     slash MUST be provided.
     158     * @param string|null $handler FQCN of handler. Specifing NULL removes the
     159     *     handler for the specified tag, if any.
     160     *
     161     * @return bool TRUE on success, FALSE on failure.
     162     */
     163    final public static function registerTagHandler($tag, $handler)
     164    {
     165        $tag = trim((string)$tag);
     166
     167        if (null === $handler) {
     168            unset(self::$tagHandlerMappings[$tag]);
     169            return true;
     170        }
     171
     172        if ('' !== $tag
     173            && class_exists($handler, true)
     174            && is_subclass_of($handler, __CLASS__)
     175            && !strpos($tag, '\\') //Accept no slash, and 1st slash at offset 0.
     176        ) {
     177            self::$tagHandlerMappings[$tag] = $handler;
     178            return true;
     179        }
     180
     181        return false;
     182    }
     183
     184    /**
     185     * Parses a tag and populates the member variables.
     186     *
     187     * @param string   $name     Name of the tag.
     188     * @param string   $content  The contents of the given tag.
     189     * @param DocBlock $docblock The DocBlock which this tag belongs to.
     190     * @param Location $location Location of the tag.
     191     */
     192    public function __construct(
     193        $name,
     194        $content,
     195        DocBlock $docblock = null,
     196        Location $location = null
     197    ) {
     198        $this
     199            ->setName($name)
     200            ->setContent($content)
     201            ->setDocBlock($docblock)
     202            ->setLocation($location);
     203    }
     204
     205    /**
     206     * Gets the name of this tag.
     207     *
     208     * @return string The name of this tag.
     209     */
     210    public function getName()
     211    {
     212        return $this->tag;
     213    }
     214
     215    /**
     216     * Sets the name of this tag.
     217     *
     218     * @param string $name The new name of this tag.
     219     *
     220     * @return $this
     221     * @throws \InvalidArgumentException When an invalid tag name is provided.
     222     */
     223    public function setName($name)
     224    {
     225        if (!preg_match('/^' . self::REGEX_TAGNAME . '$/u', $name)) {
     226            throw new \InvalidArgumentException(
     227                'Invalid tag name supplied: ' . $name
     228            );
     229        }
     230
     231        $this->tag = $name;
     232
     233        return $this;
     234    }
     235
     236    /**
     237     * Gets the content of this tag.
     238     *
     239     * @return string
     240     */
     241    public function getContent()
     242    {
     243        if (null === $this->content) {
     244            $this->content = $this->description;
     245        }
     246
     247        return $this->content;
     248    }
     249
     250    /**
     251     * Sets the content of this tag.
     252     *
     253     * @param string $content The new content of this tag.
     254     *
     255     * @return $this
     256     */
     257    public function setContent($content)
     258    {
     259        $this->setDescription($content);
     260        $this->content = $content;
     261
     262        return $this;
     263    }
     264
     265    /**
     266     * Gets the description component of this tag.
     267     *
     268     * @return string
     269     */
     270    public function getDescription()
     271    {
     272        return $this->description;
     273    }
     274
     275    /**
     276     * Sets the description component of this tag.
     277     *
     278     * @param string $description The new description component of this tag.
     279     *
     280     * @return $this
     281     */
     282    public function setDescription($description)
     283    {
     284        $this->content = null;
     285        $this->parsedDescription = null;
     286        $this->description = trim($description);
     287
     288        return $this;
     289    }
     290
     291    /**
     292     * Gets the parsed text of this description.
     293     *
     294     * @return array An array of strings and tag objects, in the order they
     295     *     occur within the description.
     296     */
     297    public function getParsedDescription()
     298    {
     299        if (null === $this->parsedDescription) {
     300            $description = new Description($this->description, $this->docblock);
     301            $this->parsedDescription = $description->getParsedContents();
     302        }
     303        return $this->parsedDescription;
     304    }
     305
     306    /**
     307     * Gets the docblock this tag belongs to.
     308     *
     309     * @return DocBlock The docblock this tag belongs to.
     310     */
     311    public function getDocBlock()
     312    {
     313        return $this->docblock;
     314    }
     315
     316    /**
     317     * Sets the docblock this tag belongs to.
     318     *
     319     * @param DocBlock $docblock The new docblock this tag belongs to. Setting
     320     *     NULL removes any association.
     321     *
     322     * @return $this
     323     */
     324    public function setDocBlock(DocBlock $docblock = null)
     325    {
     326        $this->docblock = $docblock;
     327
     328        return $this;
     329    }
     330
     331    /**
     332     * Gets the location of the tag.
     333     *
     334     * @return Location The tag's location.
     335     */
     336    public function getLocation()
     337    {
     338        return $this->location;
     339    }
     340   
     341    /**
     342     * Sets the location of the tag.
     343     *
     344     * @param Location $location The new location of the tag.
     345     *
     346     * @return $this
     347     */
     348    public function setLocation(Location $location = null)
     349    {
     350        $this->location = $location;
     351
     352        return $this;
     353    }
     354
     355    /**
     356     * Builds a string representation of this object.
     357     *
     358     * @todo determine the exact format as used by PHP Reflection and implement it.
     359     *
     360     * @return void
     361     * @codeCoverageIgnore Not yet implemented
     362     */
     363    public static function export()
     364    {
     365        throw new \Exception('Not yet implemented');
     366    }
     367
     368    /**
     369     * Returns the tag as a serialized string
     370     *
     371     * @return string
     372     */
     373    public function __toString()
     374    {
     375        return "@{$this->getName()} {$this->getContent()}";
     376    }
     377}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock/Type/Collection.php

     
     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
     13namespace phpDocumentor\Reflection\DocBlock\Type;
     14
     15use phpDocumentor\Reflection\DocBlock\Context;
     16
     17/**
     18 * Collection
     19 *
     20 * @author    Mike van Riel <mike.vanriel@naenius.com>
     21 * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com)
     22 * @license   http://www.opensource.org/licenses/mit-license.php MIT
     23 * @link      http://phpdoc.org
     24 */
     25class Collection extends \ArrayObject
     26{
     27    /** @var string Definition of the OR operator for types */
     28    const OPERATOR_OR = '|';
     29
     30    /** @var string Definition of the ARRAY operator for types */
     31    const OPERATOR_ARRAY = '[]';
     32
     33    /** @var string Definition of the NAMESPACE operator in PHP */
     34    const OPERATOR_NAMESPACE = '\\';
     35
     36    /** @var string[] List of recognized keywords */
     37    protected static $keywords = array(
     38        'string', 'int', 'integer', 'bool', 'boolean', 'float', 'double',
     39        'object', 'mixed', 'array', 'resource', 'void', 'null', 'scalar',
     40        'callback', 'callable', 'false', 'true', 'self', '$this', 'static'
     41    );
     42
     43    /**
     44     * Current invoking location.
     45     *
     46     * This is used to prepend to type with a relative location.
     47     * May also be 'default' or 'global', in which case they are ignored.
     48     *
     49     * @var Context
     50     */
     51    protected $context = null;
     52
     53    /**
     54     * Registers the namespace and aliases; uses that to add and expand the
     55     * given types.
     56     *
     57     * @param string[] $types    Array containing a list of types to add to this
     58     *     container.
     59     * @param Context  $location The current invoking location.
     60     */
     61    public function __construct(
     62        array $types = array(),
     63        Context $context = null
     64    ) {
     65        $this->context = null === $context ? new Context() : $context;
     66
     67        foreach ($types as $type) {
     68            $this->add($type);
     69        }
     70    }
     71
     72    /**
     73     * Returns the current invoking location.
     74     *
     75     * @return Context
     76     */
     77    public function getContext()
     78    {
     79        return $this->context;
     80    }
     81
     82    /**
     83     * Adds a new type to the collection and expands it if it contains a
     84     * relative namespace.
     85     *
     86     * If a class in the type contains a relative namespace than this collection
     87     * will try to expand that into a FQCN.
     88     *
     89     * @param string $type A 'Type' as defined in the phpDocumentor
     90     *     documentation.
     91     *
     92     * @throws \InvalidArgumentException if a non-string argument is passed.
     93     *
     94     * @see http://phpdoc.org/docs/latest/for-users/types.html for the
     95     *     definition of a type.
     96     *
     97     * @return void
     98     */
     99    public function add($type)
     100    {
     101        if (!is_string($type)) {
     102            throw new \InvalidArgumentException(
     103                'A type should be represented by a string, received: '
     104                .var_export($type, true)
     105            );
     106        }
     107
     108        // separate the type by the OR operator
     109        $type_parts = explode(self::OPERATOR_OR, $type);
     110        foreach ($type_parts as $part) {
     111            $expanded_type = $this->expand($part);
     112            if ($expanded_type) {
     113                $this[] = $expanded_type;
     114            }
     115        }
     116    }
     117   
     118    /**
     119     * Returns a string representation of the collection.
     120     *
     121     * @return string The resolved types across the collection, separated with
     122     *     {@link self::OPERATOR_OR}.
     123     */
     124    public function __toString()
     125    {
     126        return implode(self::OPERATOR_OR, $this->getArrayCopy());
     127    }
     128
     129    /**
     130     * Analyzes the given type and returns the FQCN variant.
     131     *
     132     * When a type is provided this method checks whether it is not a keyword or
     133     * Fully Qualified Class Name. If so it will use the given namespace and
     134     * aliases to expand the type to a FQCN representation.
     135     *
     136     * This method only works as expected if the namespace and aliases are set;
     137     * no dynamic reflection is being performed here.
     138     *
     139     * @param string $type The relative or absolute type.
     140     *
     141     * @uses getNamespace to determine with what to prefix the type name.
     142     * @uses getNamespaceAliases to check whether the first part of the relative
     143     *     type name should not be replaced with another namespace.
     144     *
     145     * @return string
     146     */
     147    protected function expand($type)
     148    {
     149        $type = trim($type);
     150        if (!$type) {
     151            return '';
     152        }
     153
     154        if ($this->isTypeAnArray($type)) {
     155            return $this->expand(substr($type, 0, -2)) . self::OPERATOR_ARRAY;
     156        }
     157
     158        if ($this->isRelativeType($type) && !$this->isTypeAKeyword($type)) {
     159            $type_parts = explode(self::OPERATOR_NAMESPACE, $type, 2);
     160
     161            $namespace_aliases = $this->context->getNamespaceAliases();
     162            // if the first segment is not an alias; prepend namespace name and
     163            // return
     164            if (!isset($namespace_aliases[$type_parts[0]])) {
     165                $namespace = $this->context->getNamespace();
     166                if ('' !== $namespace) {
     167                    $namespace .= self::OPERATOR_NAMESPACE;
     168                }
     169                return self::OPERATOR_NAMESPACE . $namespace . $type;
     170            }
     171
     172            $type_parts[0] = $namespace_aliases[$type_parts[0]];
     173            $type = implode(self::OPERATOR_NAMESPACE, $type_parts);
     174        }
     175
     176        return $type;
     177    }
     178
     179    /**
     180     * Detects whether the given type represents an array.
     181     *
     182     * @param string $type A relative or absolute type as defined in the
     183     *     phpDocumentor documentation.
     184     *
     185     * @return bool
     186     */
     187    protected function isTypeAnArray($type)
     188    {
     189        return substr($type, -2) === self::OPERATOR_ARRAY;
     190    }
     191
     192    /**
     193     * Detects whether the given type represents a PHPDoc keyword.
     194     *
     195     * @param string $type A relative or absolute type as defined in the
     196     *     phpDocumentor documentation.
     197     *
     198     * @return bool
     199     */
     200    protected function isTypeAKeyword($type)
     201    {
     202        return in_array(strtolower($type), static::$keywords, true);
     203    }
     204
     205    /**
     206     * Detects whether the given type represents a relative or absolute path.
     207     *
     208     * This method will detect keywords as being absolute; even though they are
     209     * not preceeded by a namespace separator.
     210     *
     211     * @param string $type A relative or absolute type as defined in the
     212     *     phpDocumentor documentation.
     213     *
     214     * @return bool
     215     */
     216    protected function isRelativeType($type)
     217    {
     218        return ($type[0] !== self::OPERATOR_NAMESPACE)
     219            || $this->isTypeAKeyword($type);
     220    }
     221}
  • tests/phpunit/includes/phpDocumentor/Reflection/DocBlock.php

     
     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
     13namespace phpDocumentor\Reflection;
     14
     15use phpDocumentor\Reflection\DocBlock\Tag;
     16use phpDocumentor\Reflection\DocBlock\Context;
     17use 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 */
     26class 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}