Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.96% covered (success)
98.96%
95 / 96
96.15% covered (success)
96.15%
25 / 26
CRAP
0.00% covered (danger)
0.00%
0 / 1
DocblockGenerator
98.96% covered (success)
98.96%
95 / 96
96.15% covered (success)
96.15%
25 / 26
61
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setDesc
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getDesc
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasDesc
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addTag
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addTags
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getTag
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasTag
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasTags
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 addParam
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addParams
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 getParam
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
3
 hasParam
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 findParam
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 removeParam
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 setReturn
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getReturn
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasReturn
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setThrows
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getThrows
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasThrows
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 render
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
5.01
 formatTags
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
100.00%
1 / 1
15
 getTagLength
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getParamLength
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <dev@noladev.com>
8 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Code\Generator;
16
17/**
18 * Abstract generator class
19 *
20 * @category   Pop
21 * @package    Pop\Code
22 * @author     Nick Sagona, III <dev@noladev.com>
23 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
24 * @license    https://www.popphp.org/license     New BSD License
25 * @version    6.0.0
26 */
27class DocblockGenerator extends AbstractGenerator
28{
29
30    /**
31     * Docblock description
32     * @var ?string
33     */
34    protected ?string $desc = null;
35
36    /**
37     * Docblock tags
38     * @var array
39     */
40    protected array $tags = ['param' => []];
41
42    /**
43     * Constructor
44     *
45     * Instantiate the docblock generator object
46     *
47     * @param ?string $desc
48     * @param int     $indent
49     */
50    public function __construct(?string $desc = null, int $indent = 4)
51    {
52        $this->setDesc($desc);
53        $this->setIndent($indent);
54    }
55
56    /**
57     * Set the docblock description
58     *
59     * @param  ?string $desc
60     * @return DocblockGenerator
61     */
62    public function setDesc(?string $desc = null): DocblockGenerator
63    {
64        $this->desc = $desc;
65        return $this;
66    }
67
68    /**
69     * Get the docblock description
70     *
71     * @return string|null
72     */
73    public function getDesc(): string|null
74    {
75        return $this->desc;
76    }
77
78    /**
79     * Has a docblock description
80     *
81     * @return bool
82     */
83    public function hasDesc(): bool
84    {
85        return ($this->desc !== null);
86    }
87
88    /**
89     * Add a basic tag
90     *
91     * @param  string  $name
92     * @param  ?string $desc
93     * @return DocblockGenerator
94     */
95    public function addTag(string $name, ?string $desc = null): DocblockGenerator
96    {
97        $this->tags[$name] = $desc;
98        return $this;
99    }
100
101    /**
102     * Add basic tags
103     *
104     * @param  array $tags
105     * @return DocblockGenerator
106     */
107    public function addTags(array $tags): DocblockGenerator
108    {
109        foreach ($tags as $name => $desc) {
110            $this->tags[$name] = $desc;
111        }
112        return $this;
113    }
114
115    /**
116     * Get a tag
117     *
118     * @param  string $name
119     * @return string|null
120     */
121    public function getTag(string $name): string|null
122    {
123        return $this->tags[$name] ?? null;
124    }
125
126    /**
127     * Has a tag
128     *
129     * @param  string $name
130     * @return bool
131     */
132    public function hasTag(string $name): bool
133    {
134        return (isset($this->tags[$name]));
135    }
136
137    /**
138     * Has tags
139     *
140     * @return bool
141     */
142    public function hasTags(): bool
143    {
144        return ((count($this->tags) > 1) || (count($this->tags['param']) > 0));
145    }
146
147    /**
148     * Add a param tag
149     *
150     * @param  ?string $type
151     * @param  ?string $var
152     * @param  ?string $desc
153     * @return DocblockGenerator
154     */
155    public function addParam(?string $type = null, ?string $var = null, ?string $desc = null): DocblockGenerator
156    {
157        $this->tags['param'][] = ['type' => $type, 'var' => $var, 'desc' => $desc];
158        return $this;
159    }
160
161    /**
162     * Add a param tag
163     *
164     * @param  array $params
165     * @return DocblockGenerator
166     */
167    public function addParams(array $params): DocblockGenerator
168    {
169        $params = (isset($params[0]) && is_array($params[0])) ? $params : [$params];
170        foreach ($params as $param) {
171            $this->tags['param'][] = $param;
172        }
173        return $this;
174    }
175
176    /**
177     * Get a param
178     *
179     * @param  int $index
180     * @return array|null
181     */
182    public function getParam(int $index): array|null
183    {
184        return (isset($this->tags['param']) && isset($this->tags['param'][$index])) ? $this->tags['param'][$index] : null;
185    }
186
187    /**
188     * Has a param
189     *
190     * @param  int $index
191     * @return bool
192     */
193    public function hasParam(int $index): bool
194    {
195        return (isset($this->tags['param']) && isset($this->tags['param'][$index]));
196    }
197
198    /**
199     * Find a param tag by its variable name (e.g. '$foo'), not by index -- returns the first
200     * match's full ['type' => ..., 'var' => ..., 'desc' => ...] array, or null if none matches.
201     * Used to look up (and preserve) an existing param's description before removeParam()
202     * discards it, e.g. when re-syncing a param entry that already carries a hand-written
203     * description from a reflected docblock.
204     *
205     * @param  string $var
206     * @return array|null
207     */
208    public function findParam(string $var): array|null
209    {
210        foreach ($this->tags['param'] as $param) {
211            if (($param['var'] ?? null) === $var) {
212                return $param;
213            }
214        }
215        return null;
216    }
217
218    /**
219     * Remove a param tag by its variable name (e.g. '$foo'), not by index -- removes the first
220     * match, if any, and re-indexes the remaining params. A no-op if no param has that variable
221     * name. Used to replace a stale @param entry when a caller re-adds a parameter of the same
222     * name with a different type (params are otherwise append-only via addParam()/addParams()).
223     *
224     * @param  string $var
225     * @return DocblockGenerator
226     */
227    public function removeParam(string $var): DocblockGenerator
228    {
229        foreach ($this->tags['param'] as $key => $param) {
230            if (($param['var'] ?? null) === $var) {
231                unset($this->tags['param'][$key]);
232                $this->tags['param'] = array_values($this->tags['param']);
233                break;
234            }
235        }
236        return $this;
237    }
238
239    /**
240     * Add a return tag
241     *
242     * @param  string  $type
243     * @param  ?string $desc
244     * @return DocblockGenerator
245     */
246    public function setReturn(string $type, ?string $desc = null): DocblockGenerator
247    {
248        $this->tags['return'] = ['type' => $type, 'desc' => $desc];
249        return $this;
250    }
251
252    /**
253     * Get the return
254     *
255     * @return array|null
256     */
257    public function getReturn(): array|null
258    {
259        return $this->tags['return'] ?? null;
260    }
261
262    /**
263     * Has a return
264     *
265     * @return bool
266     */
267    public function hasReturn(): bool
268    {
269        return (isset($this->tags['return']));
270    }
271
272    /**
273     * Add a throws tag
274     *
275     * @param  string  $type
276     * @param  ?string $desc
277     * @return DocblockGenerator
278     */
279    public function setThrows(string $type, ?string $desc = null): DocblockGenerator
280    {
281        $this->tags['throws'] = ['type' => $type, 'desc' => $desc];
282        return $this;
283    }
284
285    /**
286     * Get the throws
287     *
288     * @return array|null
289     */
290    public function getThrows(): array|null
291    {
292        return $this->tags['throws'] ?? null;
293    }
294
295    /**
296     * Has a throws
297     *
298     * @return bool
299     */
300    public function hasThrows(): bool
301    {
302        return (isset($this->tags['throws']));
303    }
304
305    /**
306     * Render docblock
307     *
308     * @return string
309     */
310    public function render(): string
311    {
312        $this->output = $this->printIndent() . '/**' . PHP_EOL;
313
314        if (!empty($this->desc)) {
315            $desc    = trim($this->desc);
316            $descAry = explode("\n", $desc);
317            $i = 0;
318            foreach ($descAry as $d) {
319                $i++;
320                $this->output .= $this->printIndent() . ' * ' . wordwrap($d, 70, PHP_EOL . $this->printIndent() . " * ") . PHP_EOL;
321                if ($i < count($descAry)) {
322                    $this->output .= $this->printIndent() . ' * ' . PHP_EOL;
323                }
324            }
325        }
326
327        if ($this->hasTags()) {
328            $this->output .= $this->formatTags();
329        }
330        $this->output .= $this->printIndent() . ' */' . PHP_EOL;
331
332        return $this->output;
333    }
334
335    /**
336     * Format the docblock tags
337     *
338     * @return string
339     */
340    protected function formatTags(): string
341    {
342        $tags      = null;
343        $tagLength = $this->getTagLength();
344
345        // Format basic tags
346        foreach ($this->tags as $tag => $desc) {
347            if (($tag != 'param') && ($tag != 'return') && ($tag != 'throws')) {
348                $tags .= $this->printIndent() . ' * @' . $tag .
349                    str_repeat(' ', $tagLength - strlen($tag) + 1) .
350                    $desc . PHP_EOL;
351            }
352        }
353
354        // Format param tags
355        $paramLength = $this->getParamLength();
356        foreach ($this->tags['param'] as $param) {
357            $tags .= $this->printIndent() . ' * @param';
358
359            if (!empty($param['type'])) {
360                $tags .= str_repeat(' ', $tagLength - 4) . $param['type'] .
361                    str_repeat(' ', $paramLength - strlen($param['type']) + 1);
362            }
363            if (!empty($param['var'])) {
364                $tags .= ' ' . $param['var'];
365            }
366            $tags .= ($param['desc'] !== null) ? ' ' . $param['desc'] . PHP_EOL : PHP_EOL;
367        }
368
369        // Format throw tag
370        if (array_key_exists('throws', $this->tags)) {
371            $throws = $this->tags['throws']['type'];
372            if (!empty($this->tags['throws']['desc'])) {
373                $throws .= ' ' . $this->tags['throws']['desc'];
374            }
375            $tags .= $this->printIndent() . ' * @throws' .
376                str_repeat(' ', $tagLength - 5) .
377                $throws . PHP_EOL;
378        }
379
380        // Format return tag
381        if (array_key_exists('return', $this->tags)) {
382            $tags .= $this->printIndent() . ' * @return' .
383                str_repeat(' ', $tagLength - 5) .
384                $this->tags['return']['type'];
385            if ($this->tags['return']['desc'] !== null) {
386                $tags .= ' ' . $this->tags['return']['desc'] . PHP_EOL;
387            } else {
388                $tags .= PHP_EOL;
389            }
390        }
391
392        return (($tags !== null) && ($this->desc !== null)) ? $this->printIndent() . ' * ' . PHP_EOL . $tags : $tags;
393    }
394
395    /**
396     * Get the longest tag length
397     *
398     * @return int
399     */
400    protected function getTagLength(): int
401    {
402        $length = 0;
403
404        foreach ($this->tags as $key => $value) {
405            if (strlen($key) > $length) {
406                $length = strlen($key);
407            }
408        }
409
410        return $length;
411    }
412
413    /**
414     * Get the longest param type length
415     *
416     * @return int
417     */
418    protected function getParamLength(): int
419    {
420        $length = 0;
421
422        foreach ($this->tags['param'] as $param) {
423            if (!empty($param['type']) && (strlen($param['type']) > $length)) {
424                $length = strlen($param['type']);
425            }
426        }
427
428        return $length;
429    }
430
431    /**
432     * Print docblock
433     *
434     * @return string
435     */
436    public function __toString(): string
437    {
438        return $this->render();
439    }
440
441}