Passed
Pull Request — master (#47)
by Pascal
15:06 queued 27s
created

DocBlock::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
c 1
b 0
f 0
dl 0
loc 18
rs 10
cc 1
nc 1
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of PHPUnit.
7
 *
8
 * (c) Sebastian Bergmann <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace FriendsOfPhpSpec\PhpSpec\CodeCoverage\Annotation;
15
16
use Exception;
17
use ReflectionClass;
18
use ReflectionMethod;
19
use Reflector;
20
21
use function array_map;
22
use function array_merge;
23
use function array_slice;
24
use function array_values;
25
use function count;
26
use function file;
27
use function preg_match;
28
use function preg_match_all;
29
use function strtolower;
30
use function substr;
31
32
/**
33
 * This is an abstraction around a PHPUnit-specific docBlock,
34
 * allowing us to ask meaningful questions about a specific
35
 * reflection symbol.
36
 *
37
 * @internal This class is not covered by the backward compatibility promise for PHPUnit
38
 */
39
final class DocBlock
40
{
41
    /**
42
     * @var string
43
     */
44
    private $className;
45
46
    /**
47
     * @var string
48
     */
49
    private $docComment;
50
51
    /**
52
     * @var int
53
     */
54
    private $endLine;
55
56
    /**
57
     * @var string
58
     */
59
    private $fileName;
60
61
    /**
62
     * @var bool
63
     */
64
    private $isMethod;
65
66
    /**
67
     * @var string
68
     */
69
    private $name;
70
71
    /**
72
     * @var int
73
     */
74
    private $startLine;
75
76
    /**
77
     * @var array<string, array<int, string>> pre-parsed annotations indexed by name and occurrence index
78
     */
79
    private $symbolAnnotations;
80
81
    /**
82
     * Note: we do not preserve an instance of the reflection object, since it cannot be safely (de-)serialized.
83
     *
84
     * @param array<string, array<int, string>> $symbolAnnotations
85
     */
86
    private function __construct(
87
        string $docComment,
88
        bool $isMethod,
89
        array $symbolAnnotations,
90
        int $startLine,
91
        int $endLine,
92
        string $fileName,
93
        string $name,
94
        string $className
95
    ) {
96
        $this->docComment = $docComment;
97
        $this->isMethod = $isMethod;
98
        $this->symbolAnnotations = $symbolAnnotations;
99
        $this->startLine = $startLine;
100
        $this->endLine = $endLine;
101
        $this->fileName = $fileName;
102
        $this->name = $name;
103
        $this->className = $className;
104
    }
105
106
    /**
107
     * @throws Exception
108
     *
109
     * @return array<string, array>
110
     */
111
    public function getInlineAnnotations(): array
112
    {
113
        if (false === $code = file($this->fileName)) {
114
            throw new Exception(sprintf('Could not read file `%s`', $this->fileName));
115
        }
116
117
        $lineNumber = $this->startLine;
118
        $startLine = $this->startLine - 1;
119
        $endLine = $this->endLine - 1;
120
        $codeLines = array_slice($code, $startLine, $endLine - $startLine + 1);
121
        $annotations = [];
122
        $pattern = '#/\*\*?\s*@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?\*/$#m';
123
124
        foreach ($codeLines as $line) {
125
            if (preg_match($pattern, $line, $matches)) {
126
                $annotations[strtolower($matches['name'])] = [
127
                    'line' => $lineNumber,
128
                    'value' => $matches['value'],
129
                ];
130
            }
131
132
            ++$lineNumber;
133
        }
134
135
        return $annotations;
136
    }
137
138
    /**
139
     * @param ReflectionClass<object> $class
140
     *
141
     * @throws Exception
142
     *
143
     * @return static
144
     */
145
    public static function ofClass(ReflectionClass $class): self
146
    {
147
        $className = $class->getName();
148
149
        $startLine = $class->getStartLine();
150
        $endLine = $class->getEndLine();
151
        $fileName = $class->getFileName();
152
153
        if (false === $startLine || false === $endLine || false === $fileName) {
154
            throw new Exception('Could not get required information from class');
155
        }
156
157
        return new self(
158
            (string) $class->getDocComment(),
159
            false,
160
            self::extractAnnotationsFromReflector($class),
161
            $startLine,
162
            $endLine,
163
            $fileName,
164
            $className,
165
            $className
166
        );
167
    }
168
169
    /**
170
     * @throws Exception
171
     *
172
     * @return static
173
     */
174
    public static function ofMethod(ReflectionMethod $method, string $classNameInHierarchy): self
175
    {
176
        $startLine = $method->getStartLine();
177
        $endLine = $method->getEndLine();
178
        $fileName = $method->getFileName();
179
180
        if (false === $startLine || false === $endLine || false === $fileName) {
181
            throw new Exception('Could not get required information from class');
182
        }
183
184
        return new self(
185
            (string) $method->getDocComment(),
186
            true,
187
            self::extractAnnotationsFromReflector($method),
188
            $startLine,
189
            $endLine,
190
            $fileName,
191
            $method->getName(),
192
            $classNameInHierarchy
193
        );
194
    }
195
196
    /**
197
     * @return array<string, array<int, string>>
198
     */
199
    public function symbolAnnotations(): array
200
    {
201
        return $this->symbolAnnotations;
202
    }
203
204
    /**
205
     * @return array<string, array>
206
     */
207
    private static function extractAnnotationsFromReflector(Reflector $reflector): array
208
    {
209
        $annotations = [];
210
211
        if ($reflector instanceof ReflectionClass) {
212
            $annotations = array_merge(
213
                $annotations,
214
                ...array_map(
215
                    static function (ReflectionClass $trait): array {
216
                        return self::parseDocBlock((string) $trait->getDocComment());
217
                    },
218
                    array_values($reflector->getTraits())
219
                )
220
            );
221
        }
222
223
        if (!$reflector instanceof ReflectionClass && !$reflector instanceof ReflectionMethod) {
224
            return $annotations;
225
        }
226
227
        return array_merge(
228
            $annotations,
229
            self::parseDocBlock((string) $reflector->getDocComment())
230
        );
231
    }
232
233
    /**
234
     * @return array<array<string>>
235
     */
236
    private static function parseDocBlock(string $docBlock): array
237
    {
238
        // Strip away the docblock header and footer to ease parsing of one line annotations
239
        $docBlock = (string) substr($docBlock, 3, -2);
240
        $annotations = [];
241
242
        if (preg_match_all('/@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?[ \t]*\r?$/m', $docBlock, $matches)) {
243
            $numMatches = count($matches[0]);
244
245
            for ($i = 0; $i < $numMatches; ++$i) {
246
                $annotations[$matches['name'][$i]][] = (string) $matches['value'][$i];
247
            }
248
        }
249
250
        return $annotations;
251
    }
252
}
253