1<?php
2/**
3 * This file is part of phpDocumentor.
4 *
5 * For the full copyright and license information, please view the LICENSE
6 * file that was distributed with this source code.
7 *
8 * @copyright 2010-2015 Mike van Riel<mike@phpdoc.org>
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;
16use Webmozart\Assert\Assert;
17
18/**
19 * Converts a DocBlock back from an object to a complete DocComment including Asterisks.
20 */
21class Serializer
22{
23    /** @var string The string to indent the comment with. */
24    protected $indentString = ' ';
25
26    /** @var int The number of times the indent string is repeated. */
27    protected $indent = 0;
28
29    /** @var bool Whether to indent the first line with the given indent amount and string. */
30    protected $isFirstLineIndented = true;
31
32    /** @var int|null The max length of a line. */
33    protected $lineLength = null;
34
35    /** @var DocBlock\Tags\Formatter A custom tag formatter. */
36    protected $tagFormatter = null;
37
38    /**
39     * Create a Serializer instance.
40     *
41     * @param int $indent The number of times the indent string is repeated.
42     * @param string   $indentString    The string to indent the comment with.
43     * @param bool     $indentFirstLine Whether to indent the first line.
44     * @param int|null $lineLength The max length of a line or NULL to disable line wrapping.
45     * @param DocBlock\Tags\Formatter $tagFormatter A custom tag formatter, defaults to PassthroughFormatter.
46     */
47    public function __construct($indent = 0, $indentString = ' ', $indentFirstLine = true, $lineLength = null, $tagFormatter = null)
48    {
49        Assert::integer($indent);
50        Assert::string($indentString);
51        Assert::boolean($indentFirstLine);
52        Assert::nullOrInteger($lineLength);
53        Assert::nullOrIsInstanceOf($tagFormatter, 'phpDocumentor\Reflection\DocBlock\Tags\Formatter');
54
55        $this->indent = $indent;
56        $this->indentString = $indentString;
57        $this->isFirstLineIndented = $indentFirstLine;
58        $this->lineLength = $lineLength;
59        $this->tagFormatter = $tagFormatter ?: new DocBlock\Tags\Formatter\PassthroughFormatter();
60    }
61
62    /**
63     * Generate a DocBlock comment.
64     *
65     * @param DocBlock $docblock The DocBlock to serialize.
66     *
67     * @return string The serialized doc block.
68     */
69    public function getDocComment(DocBlock $docblock)
70    {
71        $indent = str_repeat($this->indentString, $this->indent);
72        $firstIndent = $this->isFirstLineIndented ? $indent : '';
73        // 3 === strlen(' * ')
74        $wrapLength = $this->lineLength ? $this->lineLength - strlen($indent) - 3 : null;
75
76        $text = $this->removeTrailingSpaces(
77            $indent,
78            $this->addAsterisksForEachLine(
79                $indent,
80                $this->getSummaryAndDescriptionTextBlock($docblock, $wrapLength)
81            )
82        );
83
84        $comment = "{$firstIndent}/**\n";
85        if ($text) {
86            $comment .= "{$indent} * {$text}\n";
87            $comment .= "{$indent} *\n";
88        }
89
90        $comment = $this->addTagBlock($docblock, $wrapLength, $indent, $comment);
91        $comment .= $indent . ' */';
92
93        return $comment;
94    }
95
96    /**
97     * @param $indent
98     * @param $text
99     * @return mixed
100     */
101    private function removeTrailingSpaces($indent, $text)
102    {
103        return str_replace("\n{$indent} * \n", "\n{$indent} *\n", $text);
104    }
105
106    /**
107     * @param $indent
108     * @param $text
109     * @return mixed
110     */
111    private function addAsterisksForEachLine($indent, $text)
112    {
113        return str_replace("\n", "\n{$indent} * ", $text);
114    }
115
116    /**
117     * @param DocBlock $docblock
118     * @param $wrapLength
119     * @return string
120     */
121    private function getSummaryAndDescriptionTextBlock(DocBlock $docblock, $wrapLength)
122    {
123        $text = $docblock->getSummary() . ((string)$docblock->getDescription() ? "\n\n" . $docblock->getDescription()
124                : '');
125        if ($wrapLength !== null) {
126            $text = wordwrap($text, $wrapLength);
127            return $text;
128        }
129
130        return $text;
131    }
132
133    /**
134     * @param DocBlock $docblock
135     * @param $wrapLength
136     * @param $indent
137     * @param $comment
138     * @return string
139     */
140    private function addTagBlock(DocBlock $docblock, $wrapLength, $indent, $comment)
141    {
142        foreach ($docblock->getTags() as $tag) {
143            $tagText = $this->tagFormatter->format($tag);
144            if ($wrapLength !== null) {
145                $tagText = wordwrap($tagText, $wrapLength);
146            }
147
148            $tagText = str_replace("\n", "\n{$indent} * ", $tagText);
149
150            $comment .= "{$indent} * {$tagText}\n";
151        }
152
153        return $comment;
154    }
155}
156